select
select/poll

// 绑定监听符
bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
// 执行循环阻塞式监听与读取
while (1)
{
clin_len = sizeof(clin_addr);
cfd = accept(lfd, (struct sockaddr *)&clin_addr, &clin_len);
while (len = read(cfd, recvbuf, BUFSIZE))
{
write(STDOUT_FILENO, recvbuf, len); //把客户端输入的内容输出在终端
// 只有当客户端输入 stop 就停止当前客户端的连接
if (strncasecmp(recvbuf, "stop", 4) == 0)
{
close(cfd);
break;
}
}
}
编译运行之后,开启两个终端使用命令nc 10.211.55.4 8031
stop
加回车,那么第二个客户端输入任何内容都不会被客户端接收。
输入abc
的是先连接上的,在其输入stop
之前,后面连接上的客户端输入123
并不会被服务端收到。也就是说一直阻塞在第一个客户端那里。当第一个客户端输入stop
之后,服务端才收到第二个客户端的发送过来的数据。
函数分析
select(int nfds, fd_set *r, fd_set *w, fd_set *e, struct timeval *timeout)
-
maxfdp1
表示该进程中描述符的总数。 -
fd_set
则是配合select
模型的重点数据结构,用来存放描述符的集合。 -
timeout
表示select
返回需要等待的时间。
对于
由于存在这些问题,于是人们对
poll(struct pollfd *fds, int nfds, int timeout)
struct pollfd {
int fd;
short events;
short revents;
}
处理逻辑
总的来说,
**pollwait
readset
,并且将服务端监听的描述符添加到 readset
中去。
select
阻塞等待 readset
集合中是否有描述符可读。
accept
接收客户端的数据,并且将客户端描述符添加到一个数组client
中,以便二次遍历的时候使用。
for
循环把client
中的有效的描述符都添加到readset
中去。
select
再次阻塞等待readset
集合中是否有描述符可读。
while (1)
{
// 每次循环开始时,都初始化 read_set
read_set = read_set_init;
// 因为上一步 read_set 已经重置,所以需要已连接上的客户端 fd (由上次循环后产生)重新添加进 read_set
for (i = 0; i < FD_SET_SIZE; ++i)
{
if (client[i] > 0)
{
FD_SET(client[i], &read_set);
}
}
...
// 这里会阻塞,直到 read_set 中某一个 fd 有数据可读才返回,注意 read_set 中除了客户端 fd 还有服务端监听的 fd
retval = select(maxfd + 1, &read_set, NULL, NULL, NULL);
...
// 用 FD_ISSET 来判断 lfd (服务端监听的fd)是否可读。只有当新的客户端连接时,lfd 才可读
if (FD_ISSET(lfd, &read_set))
{
...
}
for (i = 0; i < maxi; ++i)
{
if (client[i] < 0)
{
continue;
}
// 如果客户端 fd 中有数据可读,则进行读取
if (FD_ISSET(client[i], &read_set))
{
// 注意:这里没有使用 while 循环读取,如果使用 while 循环读取,则有阻塞在一个客户端了。
// 可能你会想到如果一次读取不完怎么办?
// 读取不完时,在循环到 select 时 由于未读完的 fd 还有数据可读,那么立即返回,然后到这里继续读取,原来的 while 循环读取直接提到最外层的 while(1) + select 来判断是否有数据继续可读
...
}
}
}