1、例如:
刚开一家公司,一个老板和四个员工,老板管理员工。假如员工要请假,领取工资等之类的事情,是直接找老板请假,拿工资。随着时间的发展,公司的规模不断地扩大,员工的人数增加到几百号人甚至是几千号人,这时候老板管理员工是不是很麻烦,手忙脚乱,指不定哪天炸裂掉。
这时候呢,要解决这样繁琐的事情,老板要雇佣一个秘书小姐,以后的员工管理琐事交给秘书,老板只负责管理自己的秘书,这样老板就很轻松,才能感受到老板的位置真爽。
来一份简单的草图:
老板就相当于sever ,秘书小姐就相当于select,员工就相当于客户端。
以前的sever是在accept等待客户端主动连接服务器,假如客户端一直不理服务器,那么服务器就一直在那么等待(阻),那么sever(老板)肯定是吃饱撑着。现在呢,有了一个select(秘书小姐),那就将这些等待的事情交给select(秘书小姐)来处理,而sever(老板)呢,该度假度假,该陪媳妇陪媳妇,该干嘛干嘛,这样就减轻了工作量。重点是这个select(秘书小姐)也不傻,也不是干等着客户端,直接留给客户端(员工)留个电话号码,客户端有事情呢,直接打电话(标志位)给select,那么这个select收到(标志位)就来搞一个 accept 给这个客户端处理事情,这样的效率比会更高。但是重大的工程最终还是要sever老板来处理,不要认为select来取代server的位置。小秘书这个标志位也是老板创建的,小秘书拿着这个(lfd)来监听。监听到客户端,秘书就在accept返回cfd,用来和客户端通信监听,那么这个秘书就有两个监听,是否有客服连接,连接的客户是否来进行数据交流。一旦lfd监听到有客户端,select就会报告给sever,sever就会accepot个cfd,与客户端交流。
2、select函数的参数简介
/* According to earlier standards */
#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);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
头文件不说了,大家都懂。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
int nfds: 监听到的所有(客户端)文件描述符当中的最大文件描述符+1。原理是在select内的传进来的参数要做循环,循环的参数是做for循环的上限,所以要+1;例如循环10次,循环的上限就是11,这就是它为什么要+1的原因。注意:select这个函数的上限是1024个文件描述符,因此在监听的时候,最多监听1024个文件描述符,这个该函数的弊端,不可更改。
fd_set *readfds, fd_set *writefds,fd_set *exceptfds:这三个参数都是传入传出参数,都是文件描述符指针类型,文件描述符指针集合。
readfds:select监视的可读文件句柄集合。
writefds: select监视的可写文件句柄集合。
exceptfds:select监视的异常文件句柄集合。
对于传入传出参数,以第一个参数为例,草图如下:
假如,传入 lfd,fd1, fd2, fd3,4个文件描述符到reade集合中,经过判断,满足条件的只有2个,lfd和fd3,实际传入来的个数是2,这就是双向参数的特点。
struct timeval *timeout:本次select()的超时结束时间。超时时长;对应的结构体如下:
一个是秒,一个是微秒。
详细如下图:
大概可分为如下步骤,有些参数暂时不加,下面是举例子,方便理解:
1、首先建立集合,fd_set xxx;
2、将新建的集合清空;FD_AERO(&xxx);
3、将客户端时间加入到集合中:FD_SET(fd1,&xxx);
4、启动select函数,返回实际捕捉到满足条件的客户句柄总数
5、然后再进行遍历,找出满足时间的句柄;例如:
for(int i ;i<nfds;i++){
if(FD_ISSET(fd,&xxx)){
...........
}
}
注意:每当一个客户端连接,它都要遍历一次,这样就显得很麻烦,因此,就需要自己建建立一个数组,将满足条件的fd进行存储。
另外一个是:监听集合和满足条件的监听集合是统一个集合,但每次有新的fd来的时候监听,集合都要对其进行初始化,因此要将该集合进行备份。
3、select函数实现:
服务器端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <pthread.h>
#include <stdlib.h>
#include "common.h"
struct thread_msg {
int cli_socket;
pthread_t tid;
};
/*
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
原理: 把很多socket加入到一个集合set中,使用select函数监控(阻塞式)这个集合
当集合中任何一个socket送来数据,select立马返回,找出这个scoket,处理它
回去继续监控
fds: fd set,文件描述符的
一个文件fd,大概会有三种事件 可读--文件突然变得可读 可写--文件突然变得可写
异常事件--文件突然出错
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds: 是所有文件描述符中数值,最大的哪一个+1
readfds: 它们都是双向参数
传参的时候,你把所有需要监控的fd统统放入集合
当select发现某几个socket有事件,则会 清空该集合
然后只吧发生事件的fd存入集合中.
writefds:
exceptfds:
struct timeval *timeout: 超时时间,如果select在指定的时间内都没有等来客户端数据,则放弃
struct timeval {
long tv_sec; seconds
long tv_usec; microseconds
};
注意,他也是双向参数, 每次传递前都要赋值
返回值:
正数:发生事件的fd的个数
0: 超时了
-1: errno
四个辅助的函数: 用于 将fd添加/删除 到某个集合中
void FD_CLR(int fd, fd_set *set); 将fd从集合中删除
int FD_ISSET(int fd, fd_set *set); 判断fd是否在集合中
void FD_SET(int fd, fd_set *set); 将fd加入到该集合
void FD_ZERO(fd_set *set); 清空集合
*/
fd_set readfds,readfds_backup;
int max_fd;
struct timeval timeout;
int cli_socket_arr[24];
int cli_socket_pos = 0;
void do_serv_for_client(int socket)
{
int ret;
struct msg_struct serv_msg;
memset(&serv_msg,0,sizeof(serv_msg));
ret = recv(socket,&serv_msg,sizeof(serv_msg),0);
if(ret<0){
perror("recv err"); close(socket); return ;
}else if(ret == 0){
printf("serv found peer shutdwon\n"); close(socket);
return ;
}
printf("serv got msg:temp%d %d %d cnt%d des:%s\n",\
serv_msg.temp,serv_msg.humidity,serv_msg.wind,\
serv_msg.sendcnt,serv_msg.des);
serv_msg.serv_response++;
ret = send(socket,&serv_msg,sizeof(serv_msg),0);
if(ret<0){
perror("send err"); return ;
}
return ;
}
int main()
{
/*清空读集合*/
FD_ZERO(&readfds);
int socket_main = socket(AF_INET,SOCK_STREAM, 0 );
if(socket_main <0){
perror("create main socket err");
return -3;
}
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(10086);
serv_addr.sin_addr.s_addr = INADDR_ANY; //inet_addr("192.168.3.197");
int ret = bind(socket_main,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret<0){
perror("bind err");
return -34;
}
ret = listen(socket_main,5);
if(ret<0){
perror("listen err");
return -3;
}
struct sockaddr_in cli_addr;
socklen_t addrlen=sizeof(cli_addr);
int socket_new;
/*
将第一个fd加入到读集合中
*/
FD_SET(socket_main,&readfds);
max_fd = socket_main ;
loop:
readfds_backup = readfds; //因为select会修改传参,所以我们提前准备备胎
timeout.tv_sec = 5;
timeout.tv_usec=0;
ret = select(max_fd + 1,&readfds_backup,NULL,NULL, &timeout);
if(ret<0){
perror("select err");
return -3;
}else if(ret == 0 ){
printf("found select timeout\n");
void do_something(void);
goto loop;
}
//readfds_backup是一个黑盒子,里面存放了发生事件的fd
//我们没有办法,只能拿着 所有的socket,依次去判断是否存在fd中
if( FD_ISSET(socket_main,&readfds_backup) ){
//第一步判断 主socket是否在集合,如果在 接听
socket_new = accept(socket_main ,(struct sockaddr *)&cli_addr, &addrlen );
if(socket_new<0){
perror("accept err");
return -3;
}
printf("serv accept client ip=%s port=%d\n",\
inet_ntoa (cli_addr.sin_addr ), ntohs(cli_addr.sin_port) );
FD_SET(socket_new,&readfds);
max_fd = max_fd < socket_new ? socket_new : max_fd;
//记录这次的新socket,方便以后 遍历集合
cli_socket_arr[ cli_socket_pos ] = socket_new;
cli_socket_pos++;
}
for(int i=0;i< cli_socket_pos;i++){
int socket_cli = cli_socket_arr[i];
if( FD_ISSET( socket_cli,&readfds_backup) ){
do_serv_for_client(socket_cli);
}
}
goto loop;
close(socket_main); //一般最后才能关闭
return 0;
}
客户端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include "common.h"
int main()
{
int socket_cli = socket(AF_INET,SOCK_STREAM, 0 );
if(socket_cli <0){
perror("create client socket err");
return -3;
}
/*
//int connect(int socket_cli, serv_ip,serv_port);
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
struct sockaddr_in {
sa_family_t sin_family; 永远等于AF_INET. address family: AF_INET
in_port_t sin_port; 端口号以大端方式存放的 port in network byte order
struct in_addr sin_addr; internet address
};
Internet address.
struct in_addr {
uint32_t s_addr; IP地址,以大端方式存放 address in network byte order
};
成功返回 0
失败 -1,errno
*/
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(10086);
serv_addr.sin_addr.s_addr = inet_addr("192.168.3.197");
int ret = connect(socket_cli,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret <0){
perror("connect err");
return -3;
}
struct msg_struct cli_msg;
while(1){
cli_msg.temp=45;
cli_msg.humidity=40;
cli_msg.wind=10;
strcpy(cli_msg.des,"today sun day");
ret = send(socket_cli,&cli_msg,sizeof(cli_msg),0);
if(ret<0){
perror("send err");
return -34;
}
memset(&cli_msg,0,sizeof(cli_msg));
ret = recv(socket_cli,&cli_msg,sizeof(cli_msg),0);
if(ret<0){
perror("recv err");
return -34;
}else if(ret ==0){
printf("cli found serv shutdown\n");
return 0;
}
printf("cli got msg:response=%d\n",cli_msg.serv_response);
sleep(1);
}
}
自定义头文件端:
#ifndef COMM_HH
#define COMM_HH
struct msg_struct {
char temp;
int humidity;
int wind;
int sendcnt;
int serv_response;
char des[32];
};
#endif