网络模型-简单版

检测这些文件描述符对应的读写缓冲区的状态:

读缓冲区:检测里边有没有数据,如果有数据该缓冲区对应的文件描述符就绪
写缓冲区:检测写缓冲区是否可以写 (容量),如果有容量可以写,缓冲区对应的文件描述符就绪
读写异常:检测读写缓冲区是否有异常,如果有该缓冲区对应的文件描述符就绪

 这个函数的函数原型:

#include <sys/select.h>
struct timeval {
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};

-------------------------------------------------------------------------------------------------------------------

int select(int nfds, fd_set *readfds, fd_set *writefds,   fd_set *exceptfds, struct timeval * timeout);

 函数参数:
nfds:委托内核检测的这三个集合中最大的文件描述符 + 1内核需要线性遍历这些集合中的文件描述符,这个值是循环结束的条件在 Window 中这个参数是无效的,指定为 - 1 即可readfds:文件描述符的集合,内核只检测这个集合中文件描述符对应的读缓冲区传入传出参数,读集合一般情况下都是需要检测的,这样才知道通过哪个文件描述符接收数据
writefds:文件描述符的集合,内核只检测这个集合中文件描述符对应的写缓冲区传入传出参数,如果不需要使用这个参数可以指定为 NULL
exceptfds:文件描述符的集合,内核检测集合中文件描述符是否有异常状态传入传出参数,如果不需要使用这个参数可以指定为 NULL
timeout:超时时长,用来强制解除 select () 函数的阻塞的NULL:函数检测不到就绪的文件描述符会一直阻塞。等待固定时长(秒):函数检测不到就绪的文件描述符,在指定时长之后强制解除阻塞,函数返回 0不等待:函数不会阻塞,直接将该参数对应的结构体初始化为 0 即可。
函数返回值:
大于 0:成功,返回集合中已就绪的文件描述符的总个数
等于 - 1:函数调用失败
等于 0:超时,没有检测到就绪的文件描述符

-------------------------------------------------------------------------------------------------------------------

// 将文件描述符fd从set集合中删除 == 将fd对应的标志位设置为0        
void FD_CLR(int fd, fd_set *set);
// 判断文件描述符fd是否在set集合中 == 读一下fd对应的标志位到底是0还是1
int  FD_ISSET(int fd, fd_set *set);
// 将文件描述符fd添加到set集合中 == 将fd对应的标志位设置为1
void FD_SET(int fd, fd_set *set);
// 将set集合中, 所有文件文件描述符对应的标志位设置为0, 集合中没有添加任何文件描述符
void FD_ZERO(fd_set *set);

 -------------------------------------------------------------------------------------------------------------------

处理流程如下:

1创建监听的套接字 lfd = socket ();
2将监听的套接字和本地的 IP 和端口绑定 bind ()
3给监听的套接字设置监听 listen ()
4创建一个文件描述符集合 fd_set,用于存储需要检测读事件的所有的文件描述符
通过 FD_ZERO () 初始化
通过 FD_SET () 将监听的文件描述符放入检测的读集合中
5循环调用 select (),周期性的对所有的文件描述符进行检测
6select () 解除阻塞返回,得到内核传出的满足条件的就绪的文件描述符集合
通过 FD_ISSET () 判断集合中的标志位是否为 1
如果这个文件描述符是监听的文件描述符,调用 accept () 和客户端建立连接
将得到的新的通信的文件描述符,通过 FD_SET () 放入到检测集合中
如果这个文件描述符是通信的文件描述符,调用通信函数和客户端通信
如果客户端和服务器断开了连接,使用 FD_CLR () 将这个文件描述符从检测集合中删除
如果没有断开连接,正常通信即可
7重复第 6 步

 -------------------------------------------------------------------------------------------------------------------

// s

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.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;
}

//c

#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");
        exit(0);
    }

    // 2. 连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;     // ipv4
    addr.sin_port = htons(9999);   // 服务器监听的端口, 字节序应该是网络字节序
    inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
    int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("connect");
        exit(0);
    }

    // 通信
    while(1)
    {
        // 读数据
        char recvBuf[1024];
        // 写数据
        // sprintf(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;
}

 

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值