0819 使用IP多路复用实现TCP并发服务器

发布于:2025-08-20 ⋅ 阅读:(14) ⋅ 点赞:(0)

Part 1.实现聊天室功能

1.service.c

#include <myhead.h>

#define SER_PORT 8888
#define SER_IP "192.168.109.31"
#define MAX_CLIENTS 100

typedef struct clientmsg {
    int fd; // 客户端的文件描述符
    char username[20];
    struct sockaddr_in cin;
    struct clientmsg *next;
} *ClientNode;

// 创建客户端节点
ClientNode create_client_node(int fd, const char *username, struct sockaddr_in cin) 
{
    ClientNode node = (ClientNode)malloc(sizeof(struct clientmsg));
    if (node == NULL) return NULL;
    
    node->fd = fd;
    strncpy(node->username, username, sizeof(node->username)-1);
    node->cin = cin;
    node->next = NULL;
    
    return node;
}

// 添加客户端到链表
void add_client(ClientNode *head, ClientNode new_client) 
{
    if (*head == NULL)
	{
        *head = new_client;
    } 
	else 
	{
        ClientNode temp = *head;
        while (temp->next != NULL) 
		{
            temp = temp->next;
        }
        temp->next = new_client;
    }
}

// 从链表中移除客户端
void remove_client(ClientNode *head, int fd) 
{
    if (*head == NULL) return;
    
    ClientNode current = *head;
    ClientNode prev = NULL;
    
    while (current != NULL) 
	{
        if (current->fd == fd) 
		{
            if (prev == NULL) 
			{
                *head = current->next;
            } 
			else 
			{
                prev->next = current->next;
            }
            free(current);
            return;
        }
        prev = current;
        current = current->next;
    }
}

// 广播消息给所有客户端(除了发送者)
void broadcast_message(ClientNode head, int sender_fd, const char *message) 
{
    ClientNode current = head;
    while (current != NULL) 
	{
        if (current->fd != sender_fd)
		{
            send(current->fd, message, strlen(message), 0);
        }
        current = current->next;
    }
}

// 获取客户端用户名
const char* get_client_username(ClientNode head, int fd) 
{
    ClientNode current = head;
    while (current != NULL) 
	{
        if (current->fd == fd) 
		{
            return current->username;
        }
        current = current->next;
    }
    return "Unknown";
}

int main(int argc, const char *argv[]) 
{
    // 创建服务器端套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd == -1)
        ERR_MSG("socket error");

    // 设置地址重用
    int opt = 1;
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 创建服务器端地址信息结构体并且绑定
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);
    sin.sin_addr.s_addr = inet_addr(SER_IP);
    
    if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
        ERR_MSG("bind error");

    // 开启监听
    if (listen(sfd, 128) == -1)
        ERR_MSG("listen error");

    printf("服务器启动成功,等待客户端连接...\n");

    // 客户端链表头指针
    ClientNode clients = NULL;
    
    // 创建文件描述符集合
    fd_set readfds, tempfds;
    FD_ZERO(&readfds);
    FD_SET(sfd, &readfds);  // 添加监听套接字
    FD_SET(0, &readfds);    // 添加标准输入
    
    int maxfd = sfd;

    while (1)
	{
        tempfds = readfds;
        
        // 使用select监听文件描述符
        int res = select(maxfd + 1, &tempfds, NULL, NULL, NULL);
        if (res == -1)
		{
            ERR_MSG("select error");
        }
        
        // 检查是否有新的连接请求
        if (FD_ISSET(sfd, &tempfds))
		{
            struct sockaddr_in cin;
            socklen_t addrlen = sizeof(cin);
            
            int new_fd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
            if (new_fd == -1) 
			{
                perror("accept error");
                continue;
            }
            
            // 接收客户端发送的用户名
            char username[20] = "";
            int username_len = recv(new_fd, username, sizeof(username)-1, 0);
            if (username_len <= 0) 
			{
                close(new_fd);
                continue;
            }
            username[username_len] = '\0';
            
            // 创建客户端节点并添加到链表
            ClientNode new_client = create_client_node(new_fd, username, cin);
            if (new_client == NULL)
			{
                close(new_fd);
                continue;
            }
            
            add_client(&clients, new_client);
            
            // 将新客户端的文件描述符添加到select监听集合
            FD_SET(new_fd, &readfds);
            if (new_fd > maxfd)
			{
                maxfd = new_fd;
            }
            
            printf("%s [%s:%d] 加入聊天室\n", 
                   username, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
            
            // 广播欢迎消息
            char welcome_msg[128];
            snprintf(welcome_msg, sizeof(welcome_msg), 
                     "--------- %s 加入聊天室----------", username);
            broadcast_message(clients, new_fd, welcome_msg);
        }
        
        // 检查服务器终端输入
        if (FD_ISSET(0, &tempfds)) 
		{
            char buf[128];
            if (fgets(buf, sizeof(buf), stdin) == NULL) continue;
            
            buf[strcspn(buf, "\n")] = '\0'; // 移除换行符
            
            if (strlen(buf) > 0) 
			{
                // 广播服务器消息
                char server_msg[150];
                snprintf(server_msg, sizeof(server_msg), "服务器: %s", buf);
                broadcast_message(clients, -1, server_msg);
            }
        }
        
        // 检查所有客户端是否有数据可读
        ClientNode current = clients;
        while (current != NULL) 
		{
            int client_fd = current->fd;
            ClientNode next = current->next; // 保存下一个指针,因为当前节点可能被删除
            
            if (FD_ISSET(client_fd, &tempfds))
			{
                char rbuf[128] = "";
                int res = recv(client_fd, rbuf, sizeof(rbuf)-1, 0);
                
                if (res <= 0)
				{
                    // 客户端断开连接
                    printf("%s 已退出聊天室\n", current->username);
                    
                    // 广播退出消息
                    char quit_msg[128];
                    snprintf(quit_msg, sizeof(quit_msg), 
                             "----------%s 已退出聊天室----------", current->username);
                    broadcast_message(clients, client_fd, quit_msg);
                    
                    // 清理资源
                    close(client_fd);
                    FD_CLR(client_fd, &readfds);
                    remove_client(&clients, client_fd);
                    
                    // 更新maxfd
                    if (client_fd == maxfd) 
					{
                        maxfd = sfd;
                        ClientNode temp = clients;
                        while (temp != NULL) 
						{
                            if (temp->fd > maxfd) 
							{
                                maxfd = temp->fd;
                            }
                            temp = temp->next;
                        }
                    }
                } else 
				{
                    rbuf[res] = '\0';
                    
                    // 广播客户端消息
                    char broadcast_msg[256];
                    snprintf(broadcast_msg, sizeof(broadcast_msg), 
                             "%s: %s", current->username, rbuf);
                    broadcast_message(clients, client_fd, broadcast_msg);
                    
                    printf("%s: %s\n", current->username, rbuf);
                }
            }
            
            current = next;
        }
    }
     
    close(sfd);
    return 0;
}

2.cilent.c

#include<myhead.h>

#define SER_PORT 8888
#define SER_IP "192.168.109.31"

int main(int argc, const char *argv[])
{
	int cfd = socket(AF_INET,SOCK_STREAM,0);
	if(-1 == cfd)
		ERR_MSG("socket error");

	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SER_PORT);
	sin.sin_addr.s_addr = inet_addr(SER_IP);
	if(-1 == connect(cfd,(struct sockaddr *)&sin,sizeof(sin)))   
		ERR_MSG("bind error");
	printf("请输入用户名>>>\n");
	char namebuf[20] = "";
	scanf(" %s",namebuf);
	send(cfd,namebuf,strlen(namebuf),0);
		
	pid_t pid = fork();
	while(1)
	{
		if(pid > 0)
		{
			while(1)
			{
				char rbuf[128] = "";
				int ret = recv(cfd,rbuf,sizeof(rbuf),0);
				if(ret < 0)
					break;
				printf("%s\n",rbuf);
			}
			close(cfd);
			kill(pid,SIGKILL);
			wait(NULL);
		}
		else if(pid == 0)
        {

            //与服务器端进行通信
            while(1)
            {
                //从套接字中读取消息
                char wbuf[128] = "";  
				fgets(wbuf,sizeof(wbuf),stdin);
				wbuf[strlen(wbuf)-1] = 0;
				if(strcmp(wbuf,"quit") == 0)
					break;
				send(cfd,wbuf,strlen(wbuf),0);		           
			}
			close(cfd);
			exit(0);
        }	
	}
	return 0;
}

Part 2.牛客网刷题


网站公告

今日签到

点亮在社区的每一天
去签到