socket编程之select

Linux编程之select:

select作用是:在一段指定的时间内,监听用户感兴趣的文件描述符上可读、可写和异常等事件。

 

1、socket阻塞模式

通常在socket编程中,我们习惯于写connect、accept、recv、recvfrom这样的阻塞程序。如果事件不发生,程序就一直阻塞在那里,无法返回。

 

2、socket非阻塞模式:select

用select就可以完成非阻塞,进程或线程执行到此函数时,不必非要等待事件的发生,执行到这里之后,会根据select返回结果来反映执行情况。如果事件发生,则根据发生时逻辑执行,如果没发生,会继续执行。它主要时监视我们需要监视的文件描述符的变化情况——读写或是异常。

 

相关API:

参数一:被监听的文件描述符的总数,它比所有文件描述符集合中的文件描述符的最大值大1,因为文件描述符是从0开始计数的。

参数二:可读文件描述符集合

参数三:可写文件描述符集合

参数四:异常事件文件描述符集合

参数五:用于设置超时时间。NULL表示无限等待,类似于阻塞。

返回值:0:超时;-1:失败;成功返回大于0的整数,这个整数就是就绪描述符的数目。

#include 
#include 
#include 
#include 
int select(int maxfdp,fd_set *readset,fd_set *writeset,fd_set *exceptset,struct timeval *timeout);

 

fd_set可以理解为一个集合,这个集合中存放了需要监控的文件描述符。可以通过FD_ZERO,FD_SET,FD_CLR,FD_ISSET来进行操作。

 

struct timeval结构体:

struct timeval
{
    long tv_sec; /*秒 */
    long tv_usec; /*微秒 */
};

 

操作fd_set的几个宏:

#include 
int FD_ZERO(int fd, fd_set *fdset); //一个 fd_set类型变量的所有位都设为 0
int FD_CLR(int fd, fd_set *fdset); //清除某个位时可以使用
int FD_SET(int fd, fd_set *fd_set); //设置变量的某个位置位
int FD_ISSET(int fd, fd_set *fdset); //测试某个位是否被置位

 

使用范例:

fd_set rset;
int fd;
FD_ZERO(&rset);
FD_SET(fd, &rset);

 

然后使用select函数:

select(fd+1, &rset, NULL, NULL, NULL);

 

select返回后,用FD_ISSET来测试是否置位:

if(FD_ISSET(fd, &rset))
{
    ...
    //do something
}

 

原理解析:

服务端将需要进行IO操作的socket添加到select中,然后等待select系统调用返回。当数据到达时,socket被激活,select函数返回。服务端进行数据读取。

可以看到,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作。降低了效率。但使用select后,服务端可以在一个线程内同时处理多个socket请求。服务端注册好socket之后,可以通过select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。如果是在同步阻塞线程中,必须通过多线程来实现。

 

server.c

#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include 
#define LINTEN_QUEUE 5
#define PORT 8888
#define MAXLEN 1024

int socket_bind_listen()
{
    struct sockaddr_in server_address;
    int server_sockfd;
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(PORT);

    bind(server_sockfd, (struct sockaddr*) & server_address, sizeof(server_address));
    listen(server_sockfd, LINTEN_QUEUE); 

    return server_sockfd;
}
void socket_select(int server_sockfd)
{
    fd_set readfds, testfds;
    int client_sockfd;
    struct sockaddr_in client_address;
    int client_len;
    FD_ZERO(&readfds);
    FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中

    struct timeval tv;
    while (1)
    {
	tv.tv_sec = 5;
	tv.tv_usec = 0;

	int fd;
	int nread;
	testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量 

	int result = select(FD_SETSIZE, &testfds, (fd_set*)0, (fd_set*)0, NULL); //FD_SETSIZE:系统默认的最大文件描述符
	if (result < 0)
	{
    	    perror("server selelct error");
	    exit(1);
	}
	else if (result == 0)
	{
	    printf("time out\n");
	    //continue;
	}

	/*扫描所有的文件描述符*/
	for (fd = 0; fd < FD_SETSIZE; fd++)
	{
	    /*找到相关文件描述符*/
	    if (FD_ISSET(fd, &testfds))
	    {
	        /*判断是否为服务器套接字,是则表示为客户请求连接。*/
	        if (fd == server_sockfd)
		{
		    client_len = sizeof(client_address);
		    client_sockfd = accept(server_sockfd, (struct sockaddr*) & client_address, &client_len );

		    FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中
		    printf("adding client on fd %d\n", client_sockfd);
		}
		/*客户端socket中有数据请求时*/
		else
		{
		    ioctl(fd, FIONREAD, &nread);//取得数据量交给nread

		    /*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */
	            if (nread == 0)
	     	    {
	                close(fd);
			FD_CLR(fd, &readfds); //去掉关闭的fd
			printf("removing client on fd %d\n", fd);
		    }
		    /*处理客户数据请求*/
		    else
		    {
			char buf[MAXLEN] = "";
			recv(fd, buf, MAXLEN, 0);
			printf("buf:%s\n", buf);
			printf("serving client on fd %d\n", fd);
		    }
		}
	    }
	}
    }
}
int main() 
{ 
    int server_sockfd; 
    
    server_sockfd = socket_bind_listen();

    socket_select(server_sockfd);
    
    return 0;
}

client.c

#include  
#include  
#include  
#include  
#include  
#include  
#include 
#include 
#include 
int main() 
{ 
    int client_sockfd; 
    int len; 
    struct sockaddr_in address;//服务器端网络地址结构体 
    int result; 

    client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket 
    address.sin_family = AF_INET; 
    address.sin_addr.s_addr = inet_addr("127.0.0.1");
    address.sin_port = htons(8888); 

    len = sizeof(address); 
    result = connect(client_sockfd, (struct sockaddr *)&address, len); 

    if(result == -1) 
    { 
         perror("oops: client2"); 
         exit(1); 
    } 
    char buf[1024] = "hello";
    send(client_sockfd, buf, strlen(buf), 0);
    close(client_sockfd); 

    return 0; 
}

 

参考:

https://blog.csdn.net/weixin_41010318/article/details/80257177

 

https://www.cnblogs.com/skyfsm/p/7079458.html

https://blog.csdn.net/piaojun_pj/article/details/5991968

你可能感兴趣的:(C)