iis服务器助手广告广告
返回顶部
首页 > 资讯 > 操作系统 >Linux下Select多路复用如何实现简易聊天室
  • 384
分享到

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

2023-06-21 22:06:22 384人浏览 独家记忆
摘要

这篇文章主要介绍“linux下Select多路复用如何实现简易聊天室”,在日常操作中,相信很多人在Linux下Select多路复用如何实现简易聊天室问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Linux下S

这篇文章主要介绍“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多路复用如何实现简易聊天室”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: Linux下Select多路复用如何实现简易聊天室

本文链接: https://www.lsjlt.com/news/301107.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Linux下Select多路复用如何实现简易聊天室
    这篇文章主要介绍“Linux下Select多路复用如何实现简易聊天室”,在日常操作中,相信很多人在Linux下Select多路复用如何实现简易聊天室问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Linux下S...
    99+
    2023-06-21
  • Linux下Select多路复用实现简易聊天室示例
    目录前言多路复用的原理基本概念selectfd_set服务器Code客户端Code效果演示select服务器客户端Ⅰ客户端Ⅱ前言 和之前的udp聊天室有异曲同工之处,这次我们客户端s...
    99+
    2022-11-12
  • 如何用python实现简易聊天室
    本篇内容主要讲解“如何用python实现简易聊天室”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何用python实现简易聊天室”吧!1.功能:类似qq群聊功能有人进入聊天室需要输入姓名,姓名不...
    99+
    2023-06-20
  • C++如何实现简易UDP网络聊天室
    小编给大家分享一下C++如何实现简易UDP网络聊天室,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!工程名:NetSrvNetSrv.cpp//服务器端#inclu...
    99+
    2023-06-20
  • C#如何使用Socket实现本地多人聊天室
    这篇文章主要介绍C#如何使用Socket实现本地多人聊天室,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!【脚本一:Server端】使用本机地址:127.0.0.1完整代码using System;using...
    99+
    2023-06-29
  • 如何利用C++实现一个简单的聊天室程序?
    如何利用C++实现一个简单的聊天室程序?在信息时代,人们越来越注重网络交流。而聊天室作为一种常见的沟通工具,具有实时性和交互性的特点,被广泛应用于各个领域。本文将介绍如何利用C++语言实现一个简单的聊天室程序。首先,我们需要建立一个基于客户...
    99+
    2023-11-04
    C++ 实现 聊天室程序
  • 如何使用MySQL和Java实现一个简单的聊天室功能
    要使用MySQL和Java实现一个简单的聊天室功能,你需要进行以下步骤:1. 创建数据库和表:使用MySQL创建一个数据库,并在该数...
    99+
    2023-10-10
    MySQL
  • 如何使用C++基于socket多线程实现网络聊天室
    这篇文章主要介绍了如何使用C++基于socket多线程实现网络聊天室,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。具体内容如下1. 实现图解2. 聊天室服务端:TCP_Ser...
    99+
    2023-06-20
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作