<>select的接口介绍:
select 系统调用的用途是:在一段指定的时间内,监听用户感兴趣的文件描述符的可读、可写和异常等事件
<>select使用案例:
使用select处理多个TCP的客户端,完成TCP服务端的并发处理
服务器端:
#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <string.h>
#include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include
<arpa/inet.h> #include <sys/select.h> #include <sys/types.h> #define DATALENTH
1024 #define MAX_FD 128 //初始化服务器端套接字 int socket_init() { int sockfd=socket(
AF_INET,SOCK_STREAM,0); if(sockfd==-1) { return -1; } struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr)); saddr.sin_family=AF_INET; saddr.sin_port=htons(
7000); saddr.sin_addr.s_addr=inet_addr("127.0.0.1"); int res=bind(sockfd,(struct
sockaddr*)&saddr,sizeof(saddr)); if(res==-1) { return -1; } res=listen(sockfd,5)
; assert(res!=-1); return sockfd; } //初始化服务器的套接字数组 void InitFds(int fds[],int n)
{ int i=0; for(;i<n;i++) { fds[i]=-1; } } //向服务器的套接字数组中增添描述符 void AddFds(int fds
[],int fd,int n) { int i=0; for(;i<n;i++) { if(fds[i]==-1) { fds[i]=fd; break; }
} } //从服务器的套接字数组中删除指定的描述符 void DelFds(int fds[],int fd,int n) { int i=0; for(;i<
n;i++) { if(fds[i]==fd) { fds[i]=-1; break; } } }
//将数组中的套接字描述符设置到fd_set变量中,并返回当前最大的文件描述符 int SetFdToFdset(fd_set* fdset,int fds[]
,int n) { FD_ZERO(fdset); int i=0,maxfd=fds[0]; for(;i<n;i++) { if(fds[i]!=-1) {
FD_SET(fds[i],fdset); if(fds[i]>maxfd) { maxfd=fds[i]; } } } return maxfd; }
//获取客户端的连接 void GetClientLink(int sockfd,int fds[],int n) { struct sockaddr_in
caddr; memset(&caddr,0,sizeof(caddr)); int len=sizeof(caddr); int c=accept(
sockfd,(struct sockaddr*)&caddr,&len); if(c<0) { return ; } printf("A client
connect successful\n"); AddFds(fds,c,n); } //处理客户端数据 void DealClientData(int fds
[],int n,int c) { char buff[DATALENTH]={0}; int num=recv(c,buff,DATALENTH-1,0);
if(num<=0)//如果接收内容失败,则将这个描述符从fds里面删除 { DelFds(fds,c,n); close(c); printf("A
Client disconnection\n"); } else//接收成功,则打印出相应的数据 { printf("buff(%d):%s\n",c,buff
); send(c,"ok",2,0); } } //处理select返回的就绪事件 void DealReadyEvent(int fds[],int n,
int sockfd,fd_set* fdset) { int i=0; for(;i<n;i++) { if(fds[i]!=-1&&FD_ISSET(fds
[i],fdset)) { if(fds[i]==sockfd) { GetClientLink(sockfd,fds,n); } else {
DealClientData(fds,n,fds[i]); } } } } int main() { int sockfd=socket_init(); if(
sockfd==-1) { return -1; } fd_set readfds; int fds[MAX_FD]; InitFds(fds,MAX_FD);
AddFds(fds,sockfd,MAX_FD); while(1) { int maxfd=SetFdToFdset(&readfds,fds,MAX_FD
); struct timeval time; time.tv_sec=5;//5秒 time.tv_usec=0;//微秒 int n=select(
maxfd+1,&readfds,NULL,NULL,&time); if(n<0)//失败 { printf("select error\n"); break
; } else if(n==0)//超时 { printf("time out\n"); continue; } DealReadyEvent(fds,
MAX_FD,sockfd,&readfds); } exit(0); }
客户端:
#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include
<netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #
include <assert.h> int main() { int sockfd=socket(AF_INET,SOCK_STREAM,0); assert
(sockfd!=-1); struct sockaddr_in saddr; memset(&saddr,0,sizeof(saddr)); saddr.
sin_family=AF_INET; saddr.sin_port=htons(7000); saddr.sin_addr.s_addr=inet_addr(
"127.0.0.1"); int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1); while(1) { printf("please input:\n"); char buff[1024]={0};
fgets(buff,1024,stdin); if(strncmp(buff,"end",3)==0) { break; } int n=send(
sockfd,buff,strlen(buff),0); if(n<=0) { printf("send error\n"); break; } memset(
buff,0,1024); n=recv(sockfd,buff,1024,0); if(n<=0) { printf("recv error\n"); }
printf("buff=%s\n",buff); } close(sockfd); exit(0); }
运行结果:
<>注意问题:
如果接收端一次只能接收一个字符,那么假如客户端发送“hello”,服务器端会收到一个“h”,问题:剩余的字符“ello”会不会被接收到呢?
答案是:
服务器端:select会不断的提醒服务器端去接收缓冲区的内容,因此剩余的"ello"会一次被接收,当缓冲区的内容被接受完毕,再去处理下一个套接字的内容。
客户端:根据代码,服务器端接收一个次,就会向客户端发送一次“ok”,
当服务器端接收一个“h”,向客户端发送一个“ok”。
后续的“ello”服务器也会接收,它向客户端发送的剩余5个“ok"在客户端接收缓冲区中,等待客户端下一次接收的时候就会将上一次的5个“ok”全部接收。