select的优点是跨平台的,缺点是因为是轮询查询的,相对效率不高
使用 select 同时监听多个文件描述符, 将监控的操作交给内核去处理,当有监控操作时返回。select可以完成一个进程对多个客户端的处理
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds: 最大的文件描述符+1
readfds: 读文件描述符集合, 是一个传入传出参数,用于设置监听某些文件描述符的读操作,和返回有读操作的文件描述符集合。
传入: 指的是告诉内核哪些文件描述符需要监控
传出: 指的是内核告诉应用程序哪些文件描述符发生了变化
writefds: 写文件描述符集合(传入传出参数),同读文件描述符集合
execptfds: 异常文件描述符集合(传入传出参数),同读文件描述符集合
timeout:
NULL--表示永久阻塞, 直到有事件发生
0 --表示不阻塞, 立刻返回, 不管是否有监控的事件发生
>0--到指定事件或者有事件发生了就返回
The time structures involved are defined in <sys/time.h> and look like
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
}
返回值: 成功返回发生变化的文件描述符的个数
失败返回-1, 并设置errno值.
//初始化文件文件描述符集合
void FD_ZERO(fd_set *set);
//添加文件描述符到集合
void FD_SET(int fd, fd_set *set);
//将文件描述符从集合中删除
void FD_CLR(int fd, fd_set *set);
//判断文件描述符是否在集合中
int FD_ISSET(int fd, fd_set *set);
使用select处理多个客户端网络代码:
#include "socketwrap.h"
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
int main()
{
int sfd = Socket(AF_INET, SOCK_STREAM, 0);
// 设置端口复用
int opt = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
struct sockaddr_in soaddr;
bzero(&soaddr, sizeof(soaddr));
soaddr.sin_family = AF_INET;
soaddr.sin_port = htons(9999);
soaddr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(sfd, (struct sockaddr *)&soaddr, sizeof(soaddr));
// 监听-listen
Listen(sfd, 128);
int connfd[FD_SETSIZE]; // 有效的文件描述符数组
fd_set tmpfds, rdfds; // 要监控的文件描述符集
int maxfd; // 当前需要监听的最大的文件描述符
int nready; // 返回的需要处理的个数
int cfd; // 通信描述符
int i, maxi = 0; // 通信描述符数组下标,及最大下标
struct sockaddr_in clientsocket;
socklen_t clilen;
char buff[64]; // 通信数据
FD_ZERO(&tmpfds);
FD_ZERO(&rdfds);
FD_SET(sfd, &rdfds);
// 初始化有效的文件描述符数组
for (int i = 0; i < FD_SETSIZE; i++)
{
connfd[i] = -1;
}
maxfd = sfd;
while (1)
{
i = 0;
clilen = sizeof(clientsocket);
bzero(&clientsocket, clilen);
tmpfds = rdfds;
nready = select(maxfd + 1, &tmpfds, NULL, NULL, NULL);
if (nready > 0)
{
if (FD_ISSET(sfd, &tmpfds))
{
cfd = Accept(sfd, (struct sockaddr *)&clientsocket, &clilen);
if (cfd < 0)
{
break;
}
// 先找位置, 然后将新的连接的文件描述符保存到connfd数组中
for (i = 0; i < FD_SETSIZE; i++)
{
if (connfd[i] == -1)
{
connfd[i] = cfd;
break;
}
}
// 若连接总数达到了最大值,则关闭该连接
if (i == FD_SETSIZE)
{
close(cfd);
printf("too many clients, i==[%d]\n", i);
// exit(1);
continue;
}
// 确保connfd中maxi保存的是最后一个文件描述符的下标
if (i > maxi)
{
maxi = i;
}
// 打印客户端的IP和PORT
char sIP[16];
memset(sIP, 0x00, sizeof(sIP));
printf("client [%s:%d] connect\n", inet_ntop(AF_INET, &clientsocket.sin_addr.s_addr, sIP, sizeof(sIP)), htons(clientsocket.sin_port));
FD_SET(cfd, &rdfds);
if (maxfd < cfd)
{
maxfd = cfd;
}
// 若没有其他变化的文件描述符,则无需执行后续代码
if (--nready <= 0)
{
continue;
}
}
for (i = 0; i <= maxi; i++)
{
int sockfd = connfd[i];
if (sockfd == -1)
{
continue;
}
int n;
if (FD_ISSET(sockfd, &tmpfds))
{
memset(buff, 0x00, sizeof(buff));
n = Read(sockfd, buff, sizeof(buff));
if (n < 0)
{
perror("read over");
close(sockfd);
FD_CLR(sockfd, &rdfds);
connfd[i] = -1; // 将connfd[i]置为-1,表示该位置可用
}
else if (n == 0)
{
// printf("client is closed\n");
close(sockfd);
FD_CLR(sockfd, &rdfds);
connfd[i] = -1; // 将connfd[i]置为-1,表示该位置可用
}
else
{
printf("[%d]:[%s]\n", n, buff);
for (i = 0; i < n; i++)
{
buff[i] = toupper(buff[i]);
}
Write(sockfd, buff, n);
}
if (--nready <= 0)
{
break; // 注意这里是break,而不是continue, 应该是从最外层的while继续循环
}
}
}
}
}
close(sfd);
return 0;
}