文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2023-06-21 22:31

关注

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

前言

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

多路复用的原理

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

基本概念

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

与多进程和多线程技术相比,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 <stdio.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#include <fcntl.h>#include <netdb.h>#include <signal.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <arpa/inet.h>#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;    }}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(&current);    FD_SET(listen_fd, &current);    FD_SET(0, &current);    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, &current);                    max_fds = new_fd > max_fds ? new_fd : max_fds;                } else {                    // 新的连接描述符可读  recv                    ret = client_handler(i);                    if (ret <= 0) {                        // 收尾处理                        close(i);                        FD_CLR(i, &current);                    }                }            }        }//        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 <stdio.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#include <fcntl.h>#include <netdb.h>#include <signal.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netinet/ip.h>#include <arpa/inet.h>#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;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多路复用如何实现简易聊天室

客户端Ⅰ

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

客户端Ⅱ

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

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

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-人工智能
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯