学习记录, 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);
}
}
}
}