文章目录
一、select 的第一个参数(监视的范围)
这个参数用来表示监视的位图的大小,也就是select 监视的 socket 的范围。我们知道 FD_SET 这个集合可以存放 1024 个socket,但是我们并不是都将这个集合放满了。这个集合就是存放了一些socket,因此在监视的时候,只要监视那个范围就行了,这样就提高了效率。或者说,这个值是监视的位图的范围,扫描位图的范围。
这个值固定为1024也行,只不过效率太低。
这个就有点像去放羊,有一个草原,放养的时候,我只是把羊集中放到草原中的一块区域。然后我监视羊的时候不是监视整个草原,只是监视那一片区域,这样就提高了效率。
二、第二个参数
select 可以监听多个集合,这个是读集合。当有事件发生,就从这个集合将事件读取出来;比如有新的客户端连接上来或者客户端有报文发送过来。
三、第三个参数
这个参数是写的集合,如果我们要往socket写数据,也可以用 select,但是一般比较少用。一般填空
往 socket 写数据的话,我们直接调用写函数来完成。为什呢?目前我们没有听说过 write 函数会被阻塞 ,等待。就是速度比较快
其实这个函数也会产生阻塞,当数据量很大的时候 。但是内核读取数据交给网卡的速度很快,基本上看不到等待,就算产生等待了也可以忽略。
四、第四个参数
产生异常的集合,一般不需要。主要是与外围设备有关,一般这网络服务开发中填空。
五、第五个参数(超时的机制)
这个是超时的机制。
六、函数返回值
1.成功的返回
1.1 成功的返回的含义
成功的话,返回值的是监视的集合中,发生事件的socket的个数。如果监视的是多个集合,不管是哪个集合发生了事件,都返回。
1.2 FD_ISSET() 判断是哪个socket发生事件
注意一点:select 成功的返回值是发生事件的socket 的个数,并不是具体告诉你是哪个socket 发生了事件。所以我们一般要去遍历集合,找出是哪个socket 发生的事件,使用FD_ISSET() 函数。
1.3 FD_ISSET() 的返回值
返回值只有0和1,是这个socket发生事件了就返回1.
2.错误的返回值
当发生错误时,返回-1;返回-1的情况,一般是系统内存满了;给了错误的集合,错误的集合在开发的时候可能会遇到;捕捉到了信号
3.演示错误的集合
我们知道,程序一开始会将监听的 socket 放到集合中,也就是读的集合,因为当有客户连接上来,要读取出来。
3.1 正常的情况
正常的情况是下面这样的,运行没有错误
3.2 错误的情况
当我们将一个随便的值(20)放到读集合中,但是这个值肯定是非法的。
运行程序:
4.捕捉到了信号
select函数会阻塞,如果给它发送一个信号,就是说select函数能捕捉信号。
(1)运行程序
(2)发送信号
(3)查看结果
5.返回0的情况(超时)
返回0,就是超时了。
(1)设置超时的时间为5s,如果5秒后没有客户端连接上来,或者说没有事件发生,就返回0.这里我们用监听的socket测试,所以是客户端连接的问题。
(2)启动程序,5s后超时了
七、利用select函数来实现tcp的超时机制
通常是讲tcp中的通信的socket,放到select函数中,从而到达tcp的超时机制。
八、pselect 函数
select 函数会被信号中断,所以就将select 函数改进了一下,增加了一个参数(sigset),这个参数就是函数在运行的时候可以屏蔽的信号。
但这个函数不常用。
九、select 服务端代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/fcntl.h>
// 初始化服务端的监听端口。
int initserver(int port);
int main(int argc,char *argv[])
{
if (argc != 2)
{
printf("usage: ./tcpselect port\n"); return -1;
}
// 初始化服务端用于监听的socket。
int listensock = initserver(atoi(argv[1]));
printf("listensock=%d\n",listensock);
if (listensock < 0)
{
printf("initserver() failed.\n"); return -1;
}
fd_set readfdset; // 读事件的集合,包括监听socket和客户端连接上来的socket。
int maxfd; // readfdset中socket的最大值。
// 初始化结构体,把listensock添加到集合中。
FD_ZERO(&readfdset);
FD_SET(listensock,&readfdset);
maxfd = listensock;
while (1)
{
// 调用select函数时,会改变socket集合的内容,所以要把socket集合保存下来,传一个临时的给select。
fd_set tmpfdset = readfdset;
int infds = select(maxfd+1,&tmpfdset,NULL,NULL,NULL);
// printf("select infds=%d\n",infds);
// 返回失败。
if (infds < 0)
{
printf("select() failed.\n"); perror("select()"); break;
}
// 超时,在本程序中,select函数最后一个参数为空,不存在超时的情况,但以下代码还是留着。
if (infds == 0)
{
printf("select() timeout.\n"); continue;
}
// 检查有事情发生的socket,包括监听和客户端连接的socket。
// 这里是客户端的socket事件,每次都要遍历整个集合,因为可能有多个socket有事件。
for (int eventfd=0; eventfd <= maxfd; eventfd++)
{
if (FD_ISSET(eventfd,&tmpfdset)<=0) continue;
if (eventfd==listensock)
{
// 如果发生事件的是listensock,表示有新的客户端连上来。
struct sockaddr_in client;
socklen_t len = sizeof(client);
int clientsock = accept(listensock,(struct sockaddr*)&client,&len);
if (clientsock < 0)
{
printf("accept() failed.\n"); continue;
}
printf ("client(socket=%d) connected ok.\n",clientsock);
// 把新的客户端socket加入集合。
FD_SET(clientsock,&readfdset);
if (maxfd < clientsock) maxfd = clientsock;
continue;
}
else
{
// 客户端有数据过来或客户端的socket连接被断开。
char buffer[1024];
memset(buffer,0,sizeof(buffer));
// 读取客户端的数据。
ssize_t isize=read(eventfd,buffer,sizeof(buffer));
// 发生了错误或socket被对方关闭。
if (isize <=0)
{
printf("client(eventfd=%d) disconnected.\n",eventfd);
close(eventfd); // 关闭客户端的socket。
FD_CLR(eventfd,&readfdset); // 从集合中移去客户端的socket。
// 重新计算maxfd的值,注意,只有当eventfd==maxfd时才需要计算。
if (eventfd == maxfd)
{
for (int ii=maxfd;ii>0;ii--)
{
if (FD_ISSET(ii,&readfdset))
{
maxfd = ii; break;
}
}
printf("maxfd=%d\n",maxfd);
}
continue;
}
printf("recv(eventfd=%d,size=%d):%s\n",eventfd,isize,buffer);
// 把收到的报文发回给客户端。
write(eventfd,buffer,strlen(buffer));
}
}
}
return 0;
}
// 初始化服务端的监听端口。
int initserver(int port)
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if (sock < 0)
{
printf("socket() failed.\n"); return -1;
}
// Linux如下
int opt = 1; unsigned int len = sizeof(opt);
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);
setsockopt(sock,SOL_SOCKET,SO_KEEPALIVE,&opt,len);
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if (bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0 )
{
printf("bind() failed.\n"); close(sock); return -1;
}
if (listen(sock,5) != 0 )
{
printf("listen() failed.\n"); close(sock); return -1;
}
return sock;
}