检测这些文件描述符对应的读写缓冲区的状态:
读缓冲区:检测里边有没有数据,如果有数据该缓冲区对应的文件描述符就绪
写缓冲区:检测写缓冲区是否可以写 (容量),如果有容量可以写,缓冲区对应的文件描述符就绪
读写异常:检测读写缓冲区是否有异常,如果有该缓冲区对应的文件描述符就绪
这个函数的函数原型:
#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;
}
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。