一、项目目的
- 本篇文章介绍使用select来接收普通数据与带外数据,其中:
- 普通数据我们放在fd_set集合的可读集合中
- 带外数据放在fs_set集合的异常集合中
二、编码实现
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define LISTEN_NUM 10
int main(int argc,char *argv[])
{
char *ip=argv[1];
int port=atoi(argv[2]);
int recvLen;
int connFd,acceptFd;
int selectMaxFd,selectRetValue;
socklen_t cliAddrLen;
char cliAddrBuf[24];
char commBuff[1024];
struct timeval waitTimeValue;
struct sockaddr_in serAddr;
struct sockaddr_in cliAddr;
if((connFd=socket(AF_INET,SOCK_STREAM,0))==-1){
perror("socket");
exit(EXIT_FAILURE);
}
bzero(&serAddr,sizeof(serAddr));
serAddr.sin_family=AF_INET;
serAddr.sin_port=htons(port);
if(inet_pton(AF_INET,ip,&serAddr.sin_addr.s_addr)==-1){
perror("inet_pton");
exit(EXIT_FAILURE);
}
if(bind(connFd,(struct sockaddr*)&serAddr,sizeof(serAddr))==-1){
perror("inet_pton");
exit(EXIT_FAILURE);
}
if(listen(connFd,LISTEN_NUM)==-1){
perror("listen");
exit(EXIT_FAILURE);
}
fd_set readSet;
fd_set errorSet;
bzero(&cliAddr,sizeof(cliAddr));
cliAddrLen=sizeof(cliAddr);
bzero(&waitTimeValue,sizeof(waitTimeValue));
waitTimeValue.tv_sec=0;
waitTimeValue.tv_usec=0;
if((acceptFd=accept(connFd,(struct sockaddr*)&cliAddr,&cliAddrLen))==-1){
if(errno==EINTR){
printf("accept:catch signal...\n");
exit(EXIT_SUCCESS);
}
}else{
bzero(cliAddrBuf,sizeof(cliAddrBuf));
if(inet_ntop(AF_INET,&cliAddr.sin_addr.s_addr,cliAddrBuf,sizeof(cliAddrBuf))==NULL){
printf("Get Connect: an unrecognized client address\n");
}else{
printf("Get Connect: %s:%d\n",cliAddrBuf,ntohl(cliAddr.sin_port));
}
}
while(1)
{
bzero(commBuff,sizeof(commBuff));
FD_ZERO(&readSet);
FD_ZERO(&errorSet);
FD_SET(acceptFd,&readSet);
FD_SET(acceptFd,&errorSet);
selectMaxFd=acceptFd+1;
//printf("selecting...\n");
switch(selectRetValue=select(selectMaxFd,&readSet,NULL,&errorSet,&waitTimeValue))
{
case -1:
if(errno==EINTR)
printf("select:catch signal...\n");
else
perror("select");
continue;
case 0:
printf("select:time out...\n");
continue;
default:
if(FD_ISSET(acceptFd,&readSet)){
if((recvLen=recv(acceptFd,commBuff,sizeof(commBuff),0))==-1){
perror("recv");
continue;
}else if(recvLen==0){
printf("client close..");
close(connFd);
exit(EXIT_SUCCESS);
}else{
printf("Get normal data of client::%s\n",commBuff);
break;
}
}
if(FD_ISSET(acceptFd,&errorSet)){
if((recvLen=recv(acceptFd,commBuff,sizeof(commBuff),MSG_OOB))==-1){
perror("recv");
continue;
}else if(recvLen==0){
printf("client close..");
close(connFd);
exit(EXIT_SUCCESS);
}else{
printf("Get oob data of client:%s\n",commBuff);
break;
}
}
break;
}
}
exit(EXIT_SUCCESS);
}
获取用户信息
- 当我们的客户端连接之后,控制台打印客户的地址和端口,随即开始select
![](https://i-blog.csdnimg.cn/blog_migrate/2d327a57c9e0ccc294ddad9c0cf7383f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a8cf2287fc87dbe18075b342f7ed6e2f.png)
select阻塞1秒
- 我们的select设置为阻塞1秒等到是否有描述符就绪
![](https://i-blog.csdnimg.cn/blog_migrate/89d6392403ef8c748ff08fc779c5d613.png)
获取客户正常数据
- 我们通过客户端工具向程序发送数据,可以看到程序打印客户端工具发送的消息,并且继续开始select监听
![](https://i-blog.csdnimg.cn/blog_migrate/10c1a9490c5c4146c52cb8add8e12e17.png)
客户断开连接
- 客户端断开之后,客户端的描述符会在读字符集中被设置为可读状态,此时我们的select函数执行返回,然后执行到recv函数,如果客户端连接关闭,那么recv就返回0,于是就打印如下的信息
![](https://i-blog.csdnimg.cn/blog_migrate/54944f1d84cdc17632695a8d73585a8a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b9356a6a373315f64de840ce8f66fdb1.png)
三、程序总结
- ①每次重新进行select都要初始化字符集和把描述符添加进字符集。如下图
![](https://i-blog.csdnimg.cn/blog_migrate/cc7e81d9aa8261a28257680c455e71e1.png)
- ②坑点:如果select是阻塞的,那么每次select之前都要重新设置struct timeval结构体的内容。否则select的最后一个参数就失效(变为非阻塞的了)。上面编程的时候我们把waitTimeValue放在了while的外面,发现select根本不阻塞,原因就是这个
![](https://i-blog.csdnimg.cn/blog_migrate/9014e2278a8bfd00fcc4798da2bf3a91.png)
- ④处理客户端带外数据,recv的最后一个选项设置为MSG_OOB
- ③客户端关闭连接时,其文件描述符在select中变为可读状态。并且服务端的recv函数会返回0
- ④坑点:绑定服务端地址的时候,用htonl出错了,要用htons函数转换才行