Linux Socket 编程 --多线程 与 IO多路复用(select)

回顾

上一节 介绍了最简单的 Server 端 与 Client 端的代码。
但是实际应用中并不是单个client 端去连接 Server 端。Server 端需要同时处理来自多个Client 端 的 请求。
方案1:
多线程处理
方案2:
使用IO 多路复用的 Select

代码

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <pthread.h>
#include <time.h>

#define BUFFER_LENGTH   128
#define  BACKLOG  10

void *routine(void *arg)
{
    int clientfd = *(int *)arg;
    while(1)
    {
        unsigned char buffer[BUFFER_LENGTH] ={0};
        int ret = recv(clientfd,buffer,BUFFER_LENGTH,0);
        if(ret == 0 ) 
        {
            close(clientfd);
            break;
        }
        printf("buffer: %s,ret:%d\n",buffer,ret);
        ret = send(clientfd,buffer,ret,0);
    }
}

int main()
{
    int listenfd;//server 端 需要监听的 fd
    int clientfd; // 连接进来的 客户端 fd
    struct sockaddr_in servaddr;//IPV4的 socket 地址结构
    struct sockaddr_in client;//IPV4的 socket 地址结构
    socklen_t len;//地址结构的长度
    int ret;//返回值
    unsigned char buffer[BUFFER_LENGTH] = {0};


    listenfd = socket(AF_INET,SOCK_STREAM,0);// 创建server 端的socket
    if(listenfd == -1) 
    {
        printf("create socket error\n"); // 使用 perror最好
        return -1;
    }
   

    //组装socket 地址结构
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);
    // 绑定 IP 端口 这样 client 端就 知道 IP 端口信息就可以连接了
    if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))==-1) 
    {
        
        return -2;
    }

#if 0 //设置阻塞非阻塞
    int flag = fcntl(listenfd,F_GETFL,0);
    flag |= O_NONBLOCK;
    fcntl(listenfd,F_SETFL,flag);
    
#endif
    listen(listenfd,BACKLOG);//监听 等待 client连接
    //
#if 0   //单个连接
    len = sizeof(client);
    clientfd = accept(listenfd,(struct sockaddr* )&client,&len);
    ret = recv(clientfd,buffer,BUFFER_LENGTH,0);
    if(ret == 0 ) 
    {
            close(clientfd);
    }
    printf("buffer: %s,ret:%d\n",buffer,ret);
    ret = send(clientfd,buffer,ret,0);
  
#elif  0 // 循环处理
   while(1)
   {
       len = sizeof(client);
       clientfd = accept(listenfd,(struct sockaddr* )&client,&len);
       printf("clientfd:%d\n",clientfd);
       ret= recv(clientfd,buffer,BUFFER_LENGTH,0);
       printf("buffer:%s\n",buffer);
       send(clientfd,buffer,ret,0);
       pthread_t threadid;
       pthread_create(&threadid,NULL,routine,&clientfd);
   }
#else  // select 处理
    fd_set rfds, wfds, rset, wset;
    // 如何标识一个 io 的事件.
    FD_ZERO(&rfds);
    FD_SET(listenfd,&rfds);
    FD_ZERO(&wfds);
    int maxfd = listenfd;
    while(1){
        rset = rfds;
        wset = wfds;
        int nready = select(maxfd+1,&rset,&wset,NULL,NULL);
        printf("nready:%d\n",nready);
        if(FD_ISSET(listenfd,&rset))
        {
            printf("listenfd-->%d\n",listenfd);
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int clientfd = accept(listenfd,(struct sockaddr*)& client,&len);
            printf("clientfd-->%d\n",clientfd);
            FD_SET(clientfd,&rfds);
            if(clientfd > maxfd)  maxfd = clientfd;
            printf("maxfd:%d\n",maxfd);

        }
        for( int i = listenfd + 1; i <= maxfd; i++)
        {
            printf("====Enter For Loop fd:%d\n",i);
            if(FD_ISSET(i,&rset)){
                printf("%d:read set\n",i);
                ret = recv(i,buffer,BUFFER_LENGTH,0);
                if(ret == 0 ) 
                {
                   printf("received 0 close and clear\n");
                   close(i);
                   FD_CLR(i,&rfds);
                }else if(ret > 0)
                {
                    printf("buffer: %s,ret:%d\n",buffer,ret);
                    FD_SET(i,&wfds);
                }

            }
            if(FD_ISSET(i,&wset)){
                    printf("%d:write set\n",i);
                    ret = send(i,buffer,ret,0);
                    FD_CLR(i,&wfds);
                    FD_SET(i,&rfds);
            }
            printf("=====exit for loop fd:%d\n",i);
        }
        //sleep(10);
    }
#endif
   return 0;
}

注意点

  1. 代码中使用了 readfdset 和 writefdset 的副本
  2. rset/wset 供 select 和FD_ISSET 读取使用
  3. rfds/wfds 作为 FD_CLR 和 FD_SET 写使用
  4. 一次while 循环后再重新赋值
  5. 通过检测 listenfd 的 readset ,然后accept 获取clientfd ,再把新获取的 clientfd 加入 select 的 readset 里面。
  6. for 循环遍历新加入的clientfd 检查 rset 和 wset

调试记录

### 一开始 server 启动,阻塞在select 处
nready:1
listenfd-->3
clientfd-->4
maxfd:4
====Enter For Loop fd:4
=====exit for loop fd:4
## 以上是客户端连接上的打印信息 accept 获取新的Clientfd 4
## 然后进入for 循环 rset 和 wset 都没有消息.
## 下面 客户端 发送了消息 select 检测到消息返回 1
# 检查到 clientfd=4 有数据,进入到if 分支
nready:1
====Enter For Loop fd:4
4:read set
buffer: http://www.cmsoft.cn QQ:10865600,ret:32
=====exit for loop fd:4
##### 读取了消息,然后 设置了 wfds,继续循环
### 进入到 write set 中,发送消息
nready:1
====Enter For Loop fd:4
4:write set
=====exit for loop fd:4
######  清空写wfds,set 读 rfds
### 检测到listenfd 可以读取了
nready:1
listenfd-->3
clientfd-->5
maxfd:5
====Enter For Loop fd:4
=====exit for loop fd:4
====Enter For Loop fd:5
=====exit for loop fd:5
## 通过accept 获取了新的 clientfd=5,for 循环了 fd=4,fd=5
# fd=5 客户端发来消息了.
nready:1
====Enter For Loop fd:4
=====exit for loop fd:4
====Enter For Loop fd:5
5:read set
buffer: http://www.cmsoft.cn QQ:10865600,ret:32
=====exit for loop fd:5
nready:1
====Enter For Loop fd:4
=====exit for loop fd:4
====Enter For Loop fd:5
5:write set
=====exit for loop fd:5
### 客户端5 退出
nready:1
====Enter For Loop fd:4
=====exit for loop fd:4
====Enter For Loop fd:5
5:read set
received 0 close and clear
=====exit for loop fd:5
# 客户端4 退出
nready:1
====Enter For Loop fd:4
4:read set
received 0 close and clear
=====exit for loop fd:4
====Enter For Loop fd:5
=====exit for loop fd:5

问题

  1. 同时接收客户端消息的时候,永远是 先连接 的客户端 最先响应
    可以把 结合多线程??

文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:
服务器高级架构体系:
https://ke.qq.com/course/417774?flowToken=1010783

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值