基于select实现并发处理

处理流程

如果在服务器基于select实现并发,其处理流程如下:

1.创建监听的套接字 lfd = socket(); 2.将监听的套接字和本地的IP和端口绑定 bind() 3.给监听的套接字设置监听 listen() 4.创建一个文件描述符集合 fd_set,用于存储需要检测读事件的所有的文件描述符

    通过 FD_ZERO() 初始化
    通过 FD_SET() 将监听的文件描述符放入检测的读集合中

5.循环调用select(),周期性的对所有的文件描述符进行检测 6.select() 解除阻塞返回,得到内核传出的满足条件的就绪的文件描述符集合

    通过FD_ISSET() 判断集合中的标志位是否为 1
        如果这个文件描述符是监听的文件描述符,调用 accept() 和客户端建立连接
            将得到的新的通信的文件描述符,通过FD_SET() 放入到检测集合中
        如果这个文件描述符是通信的文件描述符,调用通信函数和客户端通信
            如果客户端和服务器断开了连接,使用FD_CLR()将这个文件描述符从检测集合中删除
            如果没有断开连接,正常通信即可

7.重复第6步

20250118141127

2.2.2服务端通信代码
//基于select实现并发的服务器
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/select.h>
​
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(9999);
    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中是委托内核检测的所有文件被描述符
        rdtemp = rdset;
        int num = select(maxfd + 1, &rdtemp, NULL, NULL, NULL);
        //rdset中的数据被内核改写了,只保留了发生变化的文件描述的标志位上面的1,没变化的改为0
        //只要rdset中的fd对应的标志位为1->缓冲区就是有数据
        //判断->有没有新连接
        if(FD_ISSET(lfd,&rdtemp))
        {
            //接受连接请求,这个调用不堵塞
            struct sockaddr_in cliaddr;
            int cliLen = sizeof(cliaddr);
            int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &cliLen);
​
            //得到了有效的文件描述符
            //通信的文件描述符添加到读集合
            //在下一轮select检测的时候,就可以获得缓冲区的状态
            FD_SET(cfd, &rdset);
            //重新设置最大的文件描述符
            maxfd = cfd > maxfd ? cfd : maxfd;
        }
    //检测没有新连接就进行通信
        for (int i = 0; i < maxfd + 1;++i)
        {
            //因为这里是需要检测读缓冲区是否有数据
            //并不是监听,所有需要排除掉用于监听的文件描述符
// 判断从监听的文件描述符之后到maxfd这个范围内的文件描述符是否读缓冲区有数据
            if(i!=lfd && FD_ISSET(i,&rdtemp))
            {
                //接收数据
                char buf[10] = {0};
                // 一次只能接收10个字节, 客户端一次发送100个字节
                // 一次是接收不完的, 文件描述符对应的读缓冲区中还有数据
                // 下一轮select检测的时候, 内核还会标记这个文件描述符缓冲区有数据 -> 再读一次
                //  循环会一直持续, 直到缓冲区数据被读完位置
                int len = read(i, buf, sizeof(buf));
                if(len == 0)
                {
                    printf("客户端断开了连接.....\n");
                    //将检测的文件描述符从读集合中删除
                    FD_CLR(i, &rdset);
                    close(i);
                }
                else if(len > 0)
                {
                    //收到了数据
                    //发送数据
                    write(i, buf, strlen(buf) + 1);
                }
                else{
                    //异常
                    perror("read");
                }
            }
        }
    }
    return 0;
}
在上面的代码中,创建了两个fd_set变量,用于保存要检测的读集合:

// 初始化检测的读集合

fd_set rdset;
fd_set rdtemp;

rdset用于保存要检测的原始数据,这个变量不能作为参数传递给select函数,因为在函数内部这个变量中的值会被内核修改,函数调用完毕返回之后,里边就不是原始数据了,大部分情况下是值为1的标志位变少了,不可能每一轮检测,所有的文件描述符都是就行的状态。因此需要通过rdtemp变量将原始数据传递给内核,select() 调用完毕之后再将内核数据传出,这两个变量的功能是不一样的。

2.2.3客户端通信代码
//select实现客户端代码
// 客户端不需要使用IO多路转接进行处理,
// 因为客户端和服务器的对应关系是 1:N.
// 也就是说客户端是比较专一的,只能和一个连接成功的服务器通信。
​
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
​
int main()
{
    //1.创建用于通信的套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1)
    {
        perror("socket error");
        exit(0);
    }
​
    //连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;//ipv4
    addr.sin_port = htons(9999);//服务器监听的端口,字节序应该是网络字节序
    inet_pton(AF_INET, "192.168.239.255", &addr.sin_addr.s_addr);
    int ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("connect error");
        exit(0);
    }
//通信
    while(1)
    {
        //读数据
        char recvBuf[1024];
    //写数据
        // srpintf(recvBuf,"data:%d\n",i++);
        fgets(recvBuf, sizeof(recvBuf), stdin);
        write(fd, recvBuf, strlen(recvBuf) + 1);
//如果客户端没有发送数据,默认堵塞?
        read(fd, recvBuf, sizeof(recvBuf));
        printf("recv buf:%s\n", recvBuf);
        sleep(1);
    }
    //释放资源
    close(fd);
    return 0;
}

客户端不需要使用IO多路转接进行处理,因为客户端和服务器的对应关系是 1:N,也就是说客户端是比较专一的,只能和一个连接成功的服务器通信。

虽然使用select这种IO多路转接技术可以降低系统开销,提高程序效率,但是它也有局限性:

待检测集合(第2、3、4个参数)需要频繁的在用户区和内核区之间进行数据的拷贝,效率低 内核对于select传递进来的待检测集合的检测方式是线性的 如果集合内待检测的文件描述符很多,检测效率会比较低 如果集合内待检测的文件描述符相对较少,检测效率会比较高 使用select能够检测的最大文件描述符个数有上限,默认是1024,这是在内核中被写死了的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值