当前位置: 首页 > news >正文

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

Part 1.实现聊天室功能

1.service.c

#include <myhead.h>#define SER_PORT 8888
#define SER_IP "192.168.109.31"
#define MAX_CLIENTS 100typedef 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);// 更新maxfdif (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.牛客网刷题

http://www.lryc.cn/news/625603.html

相关文章:

  • Java -- 用户线程和守护线程--线程同步机制
  • Java开发过程中实用的技术点(一)
  • LIA-X - 一张照片生成任意表情肖像动画视频 精准操控面部动作 支持50系显卡 一键整合包下载
  • 免费dll修复?缺少xxx.dll?【图文详解】Visual C++运行库安装?无法定位程序输入点于动态链接库?
  • VulKan笔记(九)-着色器
  • 机器学习--决策树2
  • 力扣57:插入区间
  • 决策树二-泰坦尼克号幸存者
  • 决策树(2)
  • FPGA入门-多路选择器
  • 决策树1.1
  • 机器学习(决策树2)
  • Leetcode 深度优先搜索 (7)
  • Python爬虫第二课:爬取HTML静态网页之《某某小说》 小说章节和内容完整版
  • 【LeetCode】3655. 区间乘法查询后的异或 II (差分/商分 + 根号算法)
  • Mybatis执行SQL流程(四)之MyBatis中JDK动态代理
  • 【HTML】3D动态凯旋门
  • Leetcode 343. 整数拆分 动态规划
  • C++入门自学Day14-- Stack和Queue的自实现(适配器)
  • 神经网络中的那些关键设计:从输入输出到参数更新
  • 面试题储备-MQ篇 3-说说你对Kafka的理解
  • 图论\dp 两题
  • 设计模式笔记_行为型_命令模式
  • 【React】事件绑定和组件基础使用
  • 从线性回归到神经网络到自注意力机制 —— 激活函数与参数的演进
  • java基础(十二)redis 日志机制以及常见问题
  • 2025年12大AI测试自动化工具
  • 多模态大模型应用落地:从图文生成到音视频交互的技术选型与实践
  • 【模块系列】STM32W25Q64
  • TDengine IDMP 运维指南(4. 使用 Docker 部署)