网络编程(四)TCP IO多路转接服务器编程(select)多线程

网络编程(四)TCP IO多路转接服务器编程(select)多线程,第1张

一,select多路转接服务器

网络编程(三)TCP IO多路转接服务器编程(select)

二,select多路转接服务器(多线程)

前面介绍的select多路转接服务器已经可以实现处理多个客户端的服务器,为了进一步提高效率,我们还可以将上面的select多路转接服务器改写为多线程的形式。


与前面普通的select多路转接服务器相比,要修改为多线程主要是要在主线程的while(1)的循环中做些处理:

在select检测完读集合过后,
1,如果检查到有监听文件描述符准备就绪,则开一个子线程让其去处理该次的连接;
2,如果检测到有通信文件描述符准备就绪,则开一个子线程让其去处理该次的通信。


注意:本次使用多线程改造该服务器的多线程方法仍然是C++11标准的跨平台方法。


同时,需注意:
1,在监听文件描述符就绪以后连接客户端过程中,要添加accept成功以后返回的客户端进入待检测集合中,方便下一次的检测,所以需要传入的fd_set参数为指针类型,且该子线程与主线程detach以后,子线程与主线程在运行过程中可能同时访问该fd_set数据,所以要互斥访问。


maxfd这个变量同理。


也要互斥访问。



2,在处理客户端通信的子线程中,同样子线程与主线程detach,所以要注意该子线程中的fd_set数据也可能与主线程对fd_set的访问发生冲突,所以也要注意互斥访问。



关于C++11跨平台多线程编程的方法与互斥量的应用在我前面的博客中有介绍:
1)C++新特性(六)多线程(1)线程启动、结束,创建线程、join,detach,线程传参详解
2)C++新特性(六)多线程(2)线程中互斥量的使用,mutex对象使用,lock_guard类模板,死锁的解决

下面是多线程select多路转接服务器一个例子代码:
服务端

//server_thread.cpp
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

mutex fdset_mutex;
mutex maxfd_mutex;
void Connection(int lfd,fd_set*rdset,int*maxfd)//监听文件描述符,待检测的读集合,集合中的最大文件描述符
{
      // 接受连接请求, 这个调用不阻塞
        struct sockaddr_in cliaddr;
        int cliLen = sizeof(cliaddr);
        int cfd = accept(lfd, (struct sockaddr*)&cliaddr, (socklen_t*)&cliLen);

        // 得到了有效的文件描述符
        // 通信的文件描述符添加到读集合
        // 在下一轮select检测的时候, 就能得到缓冲区的状态

        fdset_mutex.lock();
        FD_SET(cfd, rdset);
        fdset_mutex.unlock();


        // 重置最大的文件描述符
        maxfd_mutex.lock();
        *maxfd = cfd > *maxfd ? cfd : *maxfd;
        maxfd_mutex.unlock();
}

void Communication(int cfd,fd_set *rdset)
{
    // 接收数据
    char buf[1024];
    // 该例子中客户端与服务端都是1024字节容量,客户端一次发多少,服务端一次能读多少。


//但是也有可能服务端容量只有10,而客户端容量有100,服务端一次只能接收10个字节, 客户端一次发送100个字节 // 一次是接收不完的, 文件描述符对应的读缓冲区中还有数据 // 下一轮select检测的时候, 内核还会标记这个文件描述符缓冲区有数据 -> 再读一次 // 循环会一直持续, 知道缓冲区数据被读完位置 memset(buf,0,sizeof(buf)); int len = read(cfd, buf, sizeof(buf)); if(len == 0) { printf("客户端关闭了连接...\n"); // 将检测的文件描述符从读集合中删除 fdset_mutex.lock(); FD_CLR(cfd, rdset); fdset_mutex.unlock(); close(cfd); } else if(len > 0) { // 收到了数据 // 发送数据 printf("客户端say:%s\n",buf); write(cfd, buf, len); } else { // 异常 perror("read"); } } int main() { // 1. 创建监听的fd int lfd = socket(AF_INET, SOCK_STREAM, 0); // 2. 绑定 struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(5000); addr.sin_addr.s_addr = INADDR_ANY; bind(lfd, (struct sockaddr*)&addr, sizeof(addr)); // 3. 设置监听 listen(lfd, 128); // 将监听的fd的状态检测委托给内核检测 int maxfd = lfd; // 初始化检测的读集合 fd_set rdset; fd_set rdtemp; // 清零 FD_ZERO(&rdset); // 将监听的lfd设置到检测的读集合中 FD_SET(lfd, &rdset); // 通过select委托内核检测读集合中的文件描述符状态, 检测read缓冲区有没有数据 // 如果有数据, select解除阻塞返回 // 应该让内核持续检测 while(1) { // 默认阻塞 // rdset 中是委托内核检测的所有的文件描述符 fdset_mutex.lock();//主线程与子线程互斥访问rdset rdtemp = rdset; fdset_mutex.unlock(); maxfd_mutex.lock(); int num = select(maxfd+1, &rdtemp, NULL, NULL, NULL); maxfd_mutex.unlock(); // rdset中的数据被内核改写了, 只保留了发生变化的文件描述的标志位上的1, 没变化的改为0 // 只要rdset中的fd对应的标志位为1 -> 缓冲区有数据了 // 判断 // 有没有新连接 fdset_mutex.lock(); if(FD_ISSET(lfd, &rdtemp)) { // 接受连接请求, 这个调用不阻塞 fdset_mutex.unlock(); thread conn(Connection,lfd,&rdset,&maxfd); conn.detach(); } else fdset_mutex.unlock(); // 没有新连接, 通信 maxfd_mutex.lock(); for(int i=0; i<maxfd+1; ++i) { maxfd_mutex.unlock(); // 判断从监听的文件描述符之后到maxfd这个范围内的文件描述符是否读缓冲区有数据 fdset_mutex.lock(); if(i != lfd && FD_ISSET(i, &rdtemp)) { fdset_mutex.unlock(); thread comm(Communication,i,&rdset); comm.detach(); } else fdset_mutex.unlock(); } } return 0; }

和我前面介绍的普通TCP多线程服务器一样,要编译这个c++文件,也需要加上两个参数
-std=c++11
-lpthread
同时在vscode中,如果不识别c++11标准地话,也要设置vsdode环境为c++11标准。


即在编译出红线的地方,点击该红线,编辑"includePath”设置,设置c的标准为c11,c++标准为c++11。



客户端

// client.cpp
#include 
#include 
#include 
#include 
#include 

int main()
{
    // 1. 创建通信的套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1)
    {
        perror("socket");
        exit(0);
    }

    // 2. 连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(5000);   // 大端端口
    inet_pton(AF_INET, "39.108.179.82", &addr.sin_addr.s_addr);

    int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("connect");
        exit(0);
    }

    // 3. 和服务器端通信
    int number = 0;
    while(1)
    {
        // 发送数据
        char buf[1024];
        sprintf(buf, "你好, 服务器...%d\n", number++);
        write(fd, buf, strlen(buf)+1);
        
        // 接收数据
        memset(buf, 0, sizeof(buf));
        int len = read(fd, buf, sizeof(buf));
        if(len > 0)
        {
            printf("服务器say: %s\n", buf);
        }
        else if(len  == 0)
        {
            printf("服务器断开了连接...\n");
            break;
        }
        else
        {
            perror("read");
            break;
        }
        sleep(1);   // 每隔1s发送一条数据
    }

    close(fd);

    return 0;
}


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/562805.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-02
下一篇 2022-04-02

发表评论

登录后才能评论

评论列表(0条)

保存