网络异步IO编程: Select

学习记录, select方法网络IO通信 服务器端
select方法的缺点:
1、每次调用都需要重新设置监视set。select中布置监视任务的位置和返回监视的结果位置存放在同一空间,一旦监视行为发生,函数返回,其它集合会被清空。
2、select函数为系统调用函数,需要频繁的从用户态切换内核态对监视数组进行拷贝,效率不高。
3、底层采用轮询机制,大量连接下效率很低。
4、select 支持监听的fd有限。

一、监听端口

//创建socket,返回文件描述符
int socketfd = socket(AF_INET, SOCK_STREAM, 0);

//定义地址
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(sockaddr_in));

server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

if(bind(socketfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))==-1){
    perror("bind error");
    return -1;
}

listen(socketfd, 10);

二、相关结构体类型和函数解析

1.fd_set解析

首先我们来看fd_set在源码中的定义:

typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#ifdef __USE_XOPEN
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
  } fd_set;

简化后:

#define __FD_SETSIZE 1024
#define __NFDBITS (8 * (int) sizeof (__fd_mask))
typedef struct{
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
} fd_set;

其实fd_set就是long类型数组。由于每个比特位可以表示一个文件描述符,所以fd_set最多可以存储1024个文件描述符(由于源码定义了Size为1024)。

2.Select函数

select 函数是一个系统调用函数,它是一种 I/O 多路复用机制,它可以同时监视多个文件描述符的可读、可写和异常等事件。当任意一个文件描述符就绪时,select 函数将通知服务器进行相应的处理。通过使用 select 函数,服务器可以实现并发处理多个客户端请求,提高系统的效率。

select函数在源码中的定义:

extern int select (int __nfds, fd_set *__restrict __readfds,
		   fd_set *__restrict __writefds,
		   fd_set *__restrict __exceptfds,
		   struct timeval *__restrict __timeout);

其中参数的含义:

int __nfds :文件描述符的范围。注意到传入的参数应该是最大文件描述符+1。因为遍历逻辑为 i < __nfds
fd_set *__restrict __readfds : 指向fd_set结构的指针,这个集合中是要监控的读类型的文件的描述符
fd_set *__restrict __writefds: 指向fd_set结构的指针,这个集合是要监控的写类型的文件的描述符
fd_set *__restrict __exceptfds: 指向fd_set结构的指针,它是用来监视文件错误异常的文件描述符的集合
struct timeval *__restrict __timeout: select函数的超时时间

__restrict__timeout决定select函数的处理方式。
1,若传入NULL,select会一直置于阻塞状态,直到监控到文件描述符集合中某个文件描述符发生变化为止。此时为阻塞IO
2,若传入的时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值
3,传入的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后返回0

3. FD_ZERO、FD_SET、FD_ISSET、FD_CLR函数

FD_ZERO(fd_set *fdset) 			//将集合清空,使其不含任何文件描述符
FD_SET(int fd, fd_set *fdset); 	//将fd加入set集合
FD_CLR(int fd, fd_set *fdset); 	//将fd从set集合中清除
FD_ISSET(int fd, fd_set *fdset);//检测fd是否在set集合中,不在则返回0

三、定义fd_set并初始化置零

定义已打开的socket集合和就绪的socket集合

fd_set fdset, in_set;//已打开的socket, 就绪的socket
FD_ZERO(&fdset);
FD_SET(socket_fd, &in_set);
//标志最大文件描述符
int maxfd=socket_fd;

四、while循环select并处理就绪的监听socket和客户端通信socket

以下为while(1){}循环体中的内容:

1.重置in_set,将已连接的集合赋给in_set(监视的socket集合,同时也是select返回的就绪数组,就绪的socket标志为1)

in_set = fdset;

2.select找到就绪集合并修改in_set

printf("select loop\n");
select(maxfd + 1, &in_set, NULL, NULL, NULL);
printf("exist ready\n");

3.处理监听socket
如果监听进程就绪,就新建一个socket与客户端连接,并将新socket添加到已连接的集合中fdset,同时更新最大的文件描述符

if(FD_ISSET(socket_fd, &in_set)){
	struct sockaddr_in client_addr;
	socklen_t len = sizeof(client_addr);
	
	int new_socket=accept(socket_fd, (struct sockaddr*)&client_addr, &len);
	printf("accept new socket connect, index: %d\n", new_socket);
	
	FD_SET(new_socket, &fdset);
	
	maxfd = new_socket;
}

4.处理收到信息的socket
遍历所有文件描述符,如果是就绪的,就进行处理

for(int i = socket_fd + 1; i < maxfd+1; i++){
	if(FD_ISSET(i, &in_set)){
		char message[64] = {0};
		if(0 == recv(i, message, 64, 0)){
			printf("client index %d disconnect.\n", i);

			close(i);
			FD_CLR(i, &fdset);

			continue;
		}

		printf("received message from client index %d, context: %s\n", i, message);
		strcpy(message, "Hello");
		send(i, message, 64, 0);
	}
}

五、完整代码实现

#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <pthread.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <sys/select.h>

int main() {

	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(struct sockaddr_in));

	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons(8888);

	if (-1 == bind(socket_fd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))) {
		perror("bind");
		return -1;
	}

	listen(socket_fd, 10);
	

	//定义fd_set实例,最大文件描述符
	fd_set fdset, in_set;
	FD_ZERO(&fdset);
	FD_SET(socket_fd, &fdset);

	int maxfd=socket_fd;

	while(1){
		in_set = fdset;

		printf("select loop\n");
		select(maxfd + 1, &in_set, NULL, NULL, NULL);
		printf("exist ready\n");

		if(FD_ISSET(socket_fd, &in_set)){
			struct sockaddr_in client_addr;
			socklen_t len = sizeof(client_addr);

			int new_socket=accept(socket_fd, (struct sockaddr*)&client_addr, &len);
			printf("accept new socket connect, index: %d\n", new_socket);

			FD_SET(new_socket, &fdset);

			maxfd = new_socket;
		}

		for(int i = socket_fd + 1; i < maxfd+1; i++){
			if(FD_ISSET(i, &in_set)){
				char message[64] = {0};
				if(0 == recv(i, message, 64, 0)){
					printf("client index %d disconnect.\n", i);

					close(i);
					FD_CLR(i, &fdset);

					continue;
				}

				printf("received message from client index %d, context: %s\n", i, message);
				strcpy(message, "Hello");
				send(i, message, 64, 0);
			}
		}

	}

}

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值