文章目录
- 一、概念
- 二、实现方式
- (一) 使用select实现超时检测
- 1. select函数补充说明:
- 2. 使用示例
- 3. 输出结果
- (二) 使用setsockopt函数
- 1. 函数定义
- 2. 获取发送缓冲区和接收缓冲区的大小
- 3. 端口复用
- 4. 设置超时时间
- (三) alarm
- 1. 相关概念
- 2. sigaction
一、概念
介于阻塞和非阻塞之间,可以自己设置一个时间,在设置的时间内,如果没数据就阻塞等待,如果设置的时间到了还没数据,则立即切换为非阻塞。
二、实现方式
(一) 使用select实现超时检测
1. select函数补充说明:
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);其中 最后一个参数 timeout 就是超时时间NULL 永久阻塞 直到就绪为止也可以使用下面的结构体指定超时时间struct timeval {long tv_sec; /* 秒 */long tv_usec; /* 微秒 */};如果结构体的两个成员都为 0 则表示非阻塞返回值:成功 就绪的文件描述符个数失败 -1 重置错误码超时 0
- 注:
- select采用倒计时方式计时,如果在循环中使用,需要每次重置时间
- 超时时,select会返回0
2. 使用示例
#include <my_head.h>int main(int argc, char const *argv[])
{if(3 != argc){printf("Usage:%s Ipv4 port\n",argv[0]);exit(-1);}//创建套接字int sockfd=0;if(-1 ==(sockfd = socket(AF_INET,SOCK_STREAM,0))){ERR_LOG("socket error");}//填充结构体struct sockaddr_in serveraddr;serveraddr.sin_family=AF_INET;serveraddr.sin_addr.s_addr=inet_addr(argv[1]);serveraddr.sin_port=htons(atoi(argv[2]));socklen_t serverlen = sizeof(serveraddr);//绑定if(-1 == bind(sockfd,(struct sockaddr *)&serveraddr,serverlen)){ERR_LOG("bind error");}//开启监听if(-1 ==listen(sockfd,5)){ERR_LOG("listen error");}//创建集合fd_set readfds;FD_ZERO(&readfds);fd_set tempfds;FD_ZERO(&tempfds);int acceptfd=0;int max_fd=0;int ret=0;int i=0;int nbytes=0;char buff[128]={0};//将套接字加入集合FD_SET(sockfd,&readfds);max_fd=max_fd>sockfd?max_fd:sockfd;struct timeval tm;while(1){tm.tv_sec=5;tm.tv_usec=0;tempfds = readfds;if(-1 == (ret = select(max_fd+1,&tempfds,NULL,NULL,&tm))){ERR_LOG("select error");}else if(0 == ret){printf("已经五秒没有fd就绪了\n");continue;}printf("有fd就绪\n");//说明有fd就绪了,判断是不是sockfdfor(i=3;i<max_fd+1 && 0<ret;i++){//先判断是不是这个fd就绪了if(FD_ISSET(i,&tempfds)){ret--;//判断就绪的fd是不是sockfdif(sockfd == i){//说明有新的客户端接入if(-1 == (acceptfd = accept(i,NULL,NULL))){ERR_LOG("accept error");}//将新的acceptfd加入集合FD_SET(acceptfd,&readfds);max_fd=max_fd>acceptfd?max_fd:acceptfd;printf("用户[%d]连接\n",i);}else{//说明是已连接的客户端要通信printf("接收到用户[%d]的消息\n",i);if(-1 == (nbytes = recv(i,buff,sizeof(buff),0))){ERR_LOG("recv error");}else if(0 == nbytes){printf("用户[%d]断开连接\n",i);//从集合中删除FD_CLR(i,&readfds);close(i);continue;}if(!strcmp(buff,"quit")){//用户退出printf("用户[%d]退出\n",i);//从集合中删除FD_CLR(i,&readfds);close(i);continue;}//正常数据处理printf("用户[%d]发送消息[%s]\n",i,buff);strcat(buff,"T^T");if(-1 == send(i,buff,sizeof(buff),0)){ERR_LOG("send error");}}}}} close(sockfd);return 0;
}
3. 输出结果
(二) 使用setsockopt函数
1. 函数定义
#include <sys/types.h>
#include <sys/socket.h>int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);功能:设置或者获取套接字属性
参数:sockfd:要操作的套接字level:选项的级别套接字API级别 SOL_SOCKETTCP级别 IPPROTO_TCPIP级别 IPPROTO_IPoptname:选项的名字套接字API级别SO_BROADCAST 允许发送广播SO_RCVBUF 接收缓冲区的大小SO_SNDBUF 发送缓冲区的大小SO_RCVTIMEO 接收超时时间参数是一个 struct timeval 结构体如果超时了 调用会返回-1 错误码 为 EAGAINSO_SNDTIMEO 发送超时时间SO_REUSEADDR 允许端口复用TCP级别TCP_NODELAY 关闭Nagle算法IP级别IP_ADD_MEMBERSHIP 设置加入多播组optval:选项的值,没有特殊说明时,一般是int型指针;1打开;0关闭optlen:optval的长度
返回值:成功 0失败 -1 重置错误码
- 注:如果参数没有特殊说明,一般是int型
2. 获取发送缓冲区和接收缓冲区的大小
#include <my_head.h>int main(int argc, char const *argv[])
{int sockfd=0;if(-1 == (sockfd = socket(AF_INET,SOCK_STREAM,0))){ERR_LOG("socket error");}int snd_buff_size = 0;int snd_buff_size_len = sizeof(snd_buff_size);if(-1 == getsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,&snd_buff_size,&snd_buff_size_len)){ERR_LOG("getsockopt error");}printf("Sendbuff:%dKB\n",snd_buff_size/1024);int recv_buff_size = 0;int recv_buff_size_len = sizeof(recv_buff_size);if(-1 == getsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,&recv_buff_size,&recv_buff_size_len)){ERR_LOG("getsockopt error");}printf("Recvbuff:%dKB\n",recv_buff_size/1024);return 0;
}
输出结果:
- 注:默认 发送缓冲区16k,接收缓冲区128k。
3. 端口复用
int flag = 1;
if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)))ERR_LOG("setsockopt error");
//把上述代码加在 创建套接字之后 和 bind 之前即可
4. 设置超时时间
struct timeval tm;
tm.tv_sec = 5;
tm.tv_usec = 0;
if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm)))ERR_LOG("setsockopt error");
当超时时,会立刻返回一个错误(EAGAIN),可以检测错误码来决定如何处理。
注:由已经设置过超时时间的 sockfd 产生的 acceptfd 会继承超时属性,
如果想用一样的超时时间,就无需对 acceptfd 设置了;
如果不想用一样的时间,可以使用setsockopt函数对每个acceptfd单独设置