千家信息网

Linux下Select多路复用如何实现简易聊天室

发表于:2026-11-03 作者:千家信息网编辑
千家信息网最后更新 2026年11月03日,这篇文章主要介绍"Linux下Select多路复用如何实现简易聊天室",在日常操作中,相信很多人在Linux下Select多路复用如何实现简易聊天室问题上存在疑惑,小编查阅了各式资料,整理出简单好用的
千家信息网最后更新 2026年11月03日Linux下Select多路复用如何实现简易聊天室

这篇文章主要介绍"Linux下Select多路复用如何实现简易聊天室",在日常操作中,相信很多人在Linux下Select多路复用如何实现简易聊天室问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"Linux下Select多路复用如何实现简易聊天室"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

前言

和之前的udp聊天室有异曲同工之处,这次我们客户端send的是一个封装好了的数据包,recv的是一个字符串,服务器recv的是一个数据包,send的是一个字符串,在用户连接的时候发送一个login请求,然后服务器端处理,并广播到其他客户端去

多路复用的原理

基本概念

多路复用指的是:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。其实就是一种异步处理的操作,等待可运行的描述符。

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

多路复用大体有三种实现方式分别是:

select

poll

epoll

本次代码主要是展示select的用法:

select

int select(int nfds, fd_set *readfds, fd_set *writefds,                  fd_set *exceptfds, struct timeval *timeout);

这个是Linux的man手册给出的select的声明

第一个参数ndfs

第一个参数是nfds表示的是文件描述集合中的最大文件描述符+1,因为select的遍历使用是[0,nfds)的

第二个参数readfds

readfds表示的是读事件的集合

第三个参数writefds

writefds表示的是读事件的集合

第四个参数exceptfds

exceptfds表示的是异常参数的集合

第五个参数timeout

表示的是超时时间,timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。

struct timeval{  long tv_sec;    //second  long tv_usec;   //microseconds  }

fd_set

fd_set结构体的定义实际包含的是fds_bits位数组,该数组的每个元素的每一位标记一个文件描述符其大小固定,由FD_SETSIZE指定,一般而言FD_SETSIZE的大小为1024

我们只用关心怎么使用即可:

下面几个函数就是操作fd_set的函数

void FD_ZERO(fd_set *fdset);           //清空集合void FD_SET(int fd, fd_set *fdset);   //将一个给定的文件描述符加入集合之中void FD_CLR(int fd, fd_set *fdset);   //将一个给定的文件描述符从集合中删除int FD_ISSET(int fd, fd_set *fdset);   // 检查集合中指定的文件描述符是否可以读写

服务器Code

实现的功能是:

客户端连接到客户端时,服务器向其他客户端进行广播上线

向服务器发送消息,然后服务器向其他客户端广播上线

客户端退出,服务器向其他客户端广播

#include #include #include #include #include #include #include #include #include #include #include #include #define N 1024int fd[FD_SETSIZE];//用户集合,最大承受量typedef struct Msg{//消息的结构    char type;//消息类型    char name[20];    char text[N];//消息内容}MSG;typedef struct User{    int fd;    struct User *next;}USE;USE *head;USE *init() {    USE *p = (USE *)malloc(sizeof(USE));    memset(p,0,sizeof(USE));    p->next = NULL;    return p;}void Link(int new_fd) {//将新连接加入用户列表里面    USE *p = head;    while(p->next) {        p=p->next;    }    USE *k = (USE*)malloc(sizeof(USE));    k->fd = new_fd;    k->next = NULL;    p->next = k;}void login(int fd,MSG msg) {    USE *p = head;    char buf[N+30];    strcpy(buf,msg.name);    strcat(buf,"上线啦!快来找我玩叭!");    printf("fd = %d  %s\n",fd,buf);    while(p->next) {//给其他用户发上线信息        if(fd != p->next->fd)            send(p->next->fd,&buf,sizeof(buf),0);        p = p->next;    }//    puts("Over login");}void chat(int fd,MSG msg) {//    printf("%d\n",msg.text[0]);    if(strcmp(msg.text,"\n") == 0) return;    USE *p = head;    char buf[N+30];    strcpy(buf,msg.name);    strcat(buf,": ");    strcat(buf,msg.text);    printf("%s\n",buf);    while(p->next) {//给其他用户发信息        if(fd != p->next->fd)            send(p->next->fd,&buf,sizeof(buf),0);        p = p->next;    }}void quit(int fd,MSG msg) {    USE *p = head;    char buf[N+30];    strcpy(buf,msg.name);    strcat(buf,"伤心的退出群聊!");    printf("%s\n",buf);    while(p->next) {//给其他用户发上线信息        if(fd != p->next->fd)            send(p->next->fd,&buf,sizeof(buf),0);        p = p->next;    }}/* * 初始化TCP服务器,返回服务器的socket描述符 * */int init_tcp_server(unsigned short port) {    int ret;    int opt;    int listen_fd;    struct sockaddr_in self;        // 监听描述符    listen_fd = socket(AF_INET, SOCK_STREAM, 0);    if (listen_fd < 0) {        perror("socket");        return -1;    }    // 配置监听描述符地址复用属性    opt = 1;    ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));    if (ret < 0) {        perror("set socket opt");        return -1;    }    // 填充服务器开放接口和端口号信息    memset(&self, 0, sizeof(self));    self.sin_family = AF_INET;    self.sin_port = htons(port);    self.sin_addr.s_addr = htonl(INADDR_ANY);    ret = bind(listen_fd, (struct sockaddr *)&self, sizeof(self));    if (ret == -1) {        perror("bind");        return -1;    }    // 默认socket是双向,配置成监听模式    listen(listen_fd, 5);    return listen_fd;}// 监听处理器int listen_handler(int listen_fd) {    int new_fd;    new_fd = accept(listen_fd, NULL, NULL);    if (new_fd < 0) {        perror("accpet");        return -1;    }    return new_fd;}// 客户端处理器int client_handler(int fd) {    int ret;    MSG msg;    // 读一次    ret = recv(fd, &msg, sizeof(MSG), 0);//读取消息//    printf("name = %s\n",msg.name);    if (ret < 0) {        perror("recv");        return -1;    } else if (ret == 0) {//断开连接        quit(fd,msg);        return 0;    } else {//数据处理        if(msg.type == 'L') {//登陆处理            login(fd,msg);        }        else if(msg.type == 'C') {//聊天处理            chat(fd,msg);        }        else if(msg.type == 'Q') {//退出处理            quit(fd,msg);        }    }//    puts("Over client_handler");    return ret;}// 标准输入处理器int input_handler(int fd) {    char buf[1024];    fgets(buf, sizeof(buf), stdin);    buf[strlen(buf) - 1] = 0;    printf("user input: %s\n",buf);    return 0;}void main_loop(int listen_fd) {    fd_set current, bak_fds;    int max_fds;    int new_fd;    int ret;    // 把监听描述符、标准输入描述符添加到集合    FD_ZERO(¤t);    FD_SET(listen_fd, ¤t);    FD_SET(0, ¤t);    max_fds = listen_fd;    while (1) {        bak_fds = current;      // 备份集合        ret = select(max_fds+1, &bak_fds, NULL, NULL, NULL);        if (ret < 0) {            perror("select");            break;        }        // 判断内核通知哪些描述符可读,分别处理        for (int i = 0; i <= max_fds; ++i) {            if (FD_ISSET(i, &bak_fds)) {                if (i == 0) {//服务器的输入端,可以做成广播                    // 标准输入可读 fgets                    input_handler(i);                } else if (i == listen_fd) {//新连接,也就是有用户上线                    // 监听描述符可读  accept                    new_fd = listen_handler(i);                    if (new_fd < 0) {                        fprintf(stderr, "listen handler error!\n");                        return;                    }                    if(new_fd >= FD_SETSIZE) {                        printf("客户端连接过多!");                        close(new_fd);                        continue;                    }                    // 正常连接更新系统的集合,更新系统的通信录                    Link(new_fd);//将新的连接描述符放进链表里面                    FD_SET(new_fd, ¤t);                    max_fds = new_fd > max_fds ? new_fd : max_fds;                } else {                    // 新的连接描述符可读  recv                    ret = client_handler(i);                    if (ret <= 0) {                        // 收尾处理                        close(i);                        FD_CLR(i, ¤t);                    }                }            }        }//        puts("over loop!\n");    }}int main(){    int listen_fd;    head = init();    listen_fd = init_tcp_server(6666);    if (listen_fd < 0) {        fprintf(stderr, "init tcp server failed!\n");        return -1;    }    printf("等待连接中...\n");    main_loop(listen_fd);    close(listen_fd);    return 0;}

客户端Code

创建了 一个父子进程,父进程用于接受信息并打印到屏幕,子进程用于输入并发送信息

//// Created by Mangata on 2021/11/30.//#include #include #include #include #include #include #include #include #include #include #include #include #define N 1024char *ip = "192.168.200.130"; //106.52.247.33int port = 6666;char name[20];typedef struct Msg{//消息的结构    char type;//消息类型    char name[20];    char text[N];//消息内容}MSG;/* * 初始化TCP客户端,返回客户端的socket描述符 * */int init_tcp_client(const char *host) {    int tcp_socket;    int ret;    struct sockaddr_in dest;    tcp_socket = socket(AF_INET, SOCK_STREAM, 0);    if (tcp_socket == -1) {        perror("socket");        return -1;    }    memset(&dest, 0, sizeof(dest));    dest.sin_family = AF_INET;    dest.sin_port = htons(port);    dest.sin_addr.s_addr = inet_addr(host);    ret = connect(tcp_socket, (struct sockaddr *)&dest, sizeof(dest));    if (ret < 0) {        perror("connect");        return -1;    }//    int flags = fcntl(tcp_socket, F_GETFL, 0);       //获取建立的sockfd的当前状态(非阻塞)//    fcntl(tcp_socket, F_SETFL, flags | O_NONBLOCK);  //将当前sockfd设置为非阻塞    printf("connect %s success!\n", host);    return tcp_socket;}void login(int fd) {    MSG msg;    fputs("请输入您的名字: ",stdout);    scanf("%s",msg.name);    strcpy(name,msg.name);    msg.type = 'L';    send(fd,&msg,sizeof(MSG),0);}void chat_handler(int client_fd) {    int ret;    char buf[N+30];    pid_t pid = fork();    if(pid == 0) {        MSG msg;        strcpy(msg.name,name);        while (fgets(buf, sizeof(buf), stdin)) {            if (strncmp(buf, "quit", 4) == 0) {// 客户端不聊天了,准备退出                msg.type = 'q';                send(client_fd,&msg,sizeof(MSG),0);                exit(1);            }            strcpy(msg.text,buf);            msg.type = 'C';            // 发送字符串,不发送'\0'数据            ret = send(client_fd, &msg, sizeof(MSG), 0);            if (ret < 0) {                perror("send");                break;            }            printf("send %d bytes success!\n", ret);        }    }    else {        while(1){            int rrt = recv(client_fd,&buf,sizeof(buf),0);            printf("rrt = %d\n",rrt);            if(rrt <= 0) {                printf("断开服务器!\n");                break;            }            fprintf(stdout,"%s\n",buf);        }    }}int main(int argc,char *argv[]){    int client_socket;    client_socket = init_tcp_client(ip);    if (client_socket < 0) {        fprintf(stderr, "init tcp client failed!\n");        return -1;    }    login(client_socket);    chat_handler(client_socket);    close(client_socket);    return 0;}

效果演示

select服务器

客户端Ⅰ

客户端Ⅱ

到此,关于"Linux下Select多路复用如何实现简易聊天室"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

客户 客户端 服务器 服务 处理 复用 多路 消息 参数 用户 文件 进程 监听 输入 聊天室 信息 系统 广播 简易 数据 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 重生之互联网科技帝国 A股互联网金融科技公司 云电互联网科技 上海致骋互联网科技 火传媒上海互联网科技 科技互联网创业计划书 老年人脱离互联网科技的作文 中国互联网有什么科技含量 小米互联网科技园设置 受益于互联网科技的高速发展 岳阳华臻互联网科技有限公司 深圳市优智互联网科技有限公司 威龙国际互联网科技公司 世界互联网领先科技成果获奖 互联网科技如何创业 富国互联网金融科技混合基金 工业互联网科技成果转化 中国新注册互联网科技 同城互联网络科技 东风日产互联网科技公司叫什么 上海喔噻互联网科技有限公司 圆梦互联网科技 互联网与科技发展 银优互联网科技有限公司 壹账通互联网科技 互联网高科技股票 洛阳昕诺飞互联网科技有限公司 洛阳共同富裕互联网科技有限公司 互联网界的黑科技 恒生科技互联网指数十大成分股
0