今天跟大家介绍一个函数,这个函数在Linux编程里边特别重要,很多地方都用到。
select()函数
函数原型
int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);
参数分析:nfds+1,在其后的读、写、异常、超时找模式运行
函数说明
select()用来等待文件描述词状态的改变。参数n代表最大的文件描述词加1,参数readfds、writefds 和exceptfds 称为描述词组,是用来回传该描述词的读,写或例外的状况。底下的宏提供了处理这三种描述词组的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set); 用来清除描述词组set的全部位
结构体说明
先说明两个结构体:
struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。
fd_set集合可以通过一些宏由人为来操作,比如
清空集合FD_ZERO(fd_set *);
将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set*);
将一个给定的文件描述符从集合中删除FD_CLR(int,fd_set*);
检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。
struct timeval是一个常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。如下所示:
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
错误代码
执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。
EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足
具体参数说明:
( 1) int n:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
( 2) fd_set*readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
(3) fd_set*writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
( 4) fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。
( 5)struct timeval *timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
(1)、recv、send、select......都是阻塞函数
但是在这里用阻塞函数-------->解决非阻塞问题;
(2)、当可读事件发生时,区别两种情况:
a、请求与服务器的连接; b、已经连接好了,直接进行通信;
(3)、每次都要重置,只留下一个客户端即可。
(4)、select()是轮询模式,走访所有的套接字;时间设置为0,不阻塞,直接返回。
4、I/O复用的本质
对其返回值(select().....)需要特别注意,< == >按不同的情况进行处理;
I/O复用只关心:服务器在跟哪个客户打交道;
1、Linux I/O多路复用
之前:我们的处理是,每到来一个客户端,都为其开辟一个新的进/线程,对其进行一对一的服务,这是VIP的模式;在高并发情况下,将造成资源消耗过大。
现在,对应高并发:一个线程为多个客户服务;
同一个时刻,只能为一个客户服务(作用排队);
模型分析
此时就会产生select()、poll()、epoll()模式
5、代码实现
(1)、utili.h
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8787
#define LISTEN_QUEUE 5
#define SIZE 10
#define BUFFER_SIZE 256
(2)、ser.c
#include"../utili.h"
typedef struct server_context_st
{
int cli_cnt; //有多少个客户端
int clifds[SIZE]; //客户端套接字集合
fd_set allfds; //套接字集合
int maxfd; //套接字中最大的一个}server_context_st;static server_context_st *s_srv_ctx = NULL;static void server_uninit(){
if(s_srv_ctx)
{
free(s_srv_ctx);
s_srv_ctx = NULL;
}
}
static void server_init()
{
int i;
s_srv_ctx = (server_context_st*)malloc(sizeof(server_context_st));
assert(s_srv_ctx != NULL);
memset(s_srv_ctx, 0, sizeof(server_context_st));
for(i=0; i
s_srv_ctx->clifds[i] = -1;
}
}s
tatic int create_server_proc(const char *ip, int port)
{
printf("ip>%s\n",ip);
printf("port:>%d\n",port);
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addrSer;
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(port);
addrSer.sin_addr.s_addr = inet_addr(ip);
socklen_t len = sizeof(struct sockaddr);
int yes = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
bind(fd, (struct sockaddr*)&addrSer, len);
listen(fd, LISTEN_QUEUE);
return fd;}static int accept_client_proc(int srvfd){
struct sockaddr_in cliaddr;
socklen_t len = sizeof(struct sockaddr);
int clifd = accept(srvfd, (struct sockaddr*)&cliaddr, &len);
printf("Server Accept Client Connect OK.\n");
int i;
for(i=0; i
if(s_srv_ctx->clifds[i] == -1)
{
s_srv_ctx->clifds[i] = clifd;
s_srv_ctx->cli_cnt++;
break;
}
}
if(i == SIZE)
{
printf("too many client.\n");
}
}
static void handle_client_msg(int fd, char *buf)
{
printf("recv buffer :>%s\n",buf);
send(fd, buf, strlen(buf)+1, 0);}static void recv_client_msg(fd_set *readfds){
int clifd;
char buffer[BUFFER_SIZE];
int i;
for(i=0; i<=s_srv_ctx->cli_cnt; ++i)
{
clifd = s_srv_ctx->clifds[i];
if(clifd < 0)
{
continue;
}
if(FD_ISSET(clifd, readfds))
{
recv(clifd, buffer, BUFFER_SIZE, 0);
handle_client_msg(clifd, buffer);
}
}
}
static void handle_client_proc(int srvfd)
{
int clifd = -1;
fd_set *readfds = &s_srv_ctx->allfds;
int retval;
int i;
struct timeval tv;
while(1)
{
FD_ZERO(readfds);
FD_SET(srvfd, readfds);
s_srv_ctx->maxfd = srvfd;
for(i=0; i
{
clifd = s_srv_ctx->clifds[i];
FD_SET(clifd, readfds);
s_srv_ctx->maxfd = (clifd > s_srv_ctx->maxfd ? clifd : s_srv_ctx->maxfd);
}
//retval =select(maxfd+1, NULL, NULL, readfds)
tv.tv_sec = 0;
tv.tv_usec = 0;
retval = select(s_srv_ctx->maxfd+1, readfds, NULL, NULL, &tv);
if(retval == -1){ //错误返回
perror("select");
return ;
}
if(retval == 0){ //处理超时
printf("Server Wait Time Out.\n");
continue;
}
if(FD_ISSET(srvfd, readfds)){
accept_client_proc(srvfd); //处理客户端的连接
}else{
recv_client_msg(readfds); //服务器接收客户端的消息
}
}
}
int main(int argc, char *argv[])
{
server_init();
int srvfd = create_server_proc(SERVER_IP, SERVER_PORT);
handle_client_proc(srvfd);
return 0;
}
(3)、cli.c
#include"../utili.h"
static void handle_connection(int sockfd)
{
fd_set readfds;
int retval = 0;
char buffer[BUFFER_SIZE];
int maxfd;
while(1)
{
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);
maxfd = sockfd;
retval = select(maxfd+1, &readfds, NULL, NULL, NULL);
if(retval == -1)
{
perror("select");
return;
}
if(FD_ISSET(sockfd, &readfds))
{
recv(sockfd, buffer, BUFFER_SIZE, 0);
printf("client recv self msg:> %s\n",buffer);
//sleep(1);
printf("Msg:>");
scanf("%s",buffer);
send(sockfd, buffer, strlen(buffer)+1, 0);
}
}
}
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addrSer;
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(SERVER_PORT);
addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);
int retval = connect(sockfd, (struct sockaddr*)&addrSer, sizeof(struct sockaddr));
if(retval == -1)
{
perror("connect");
return -1;
}
else
{
printf("Client Connect Server OK.\n");
}
send(sockfd, "hello server.", strlen("hello server")+1, 0);
handle_connection(sockfd);
return 0;
}
运行结果
服务器端:一直在等待客户端的连接,比较快,图不好截取;
客户端1
客户端2
推荐阅读:
线程池网络服务
多线程网络服务
Socket网络编程
线程高级操作
Linux多线程编程
线程
欢本文的朋友们,欢迎长按下图关注订阅号编程小兔崽,收看更多精彩内容
每天进步一点点,如果有用给小编点个赞