文章目录
- 一、概念
- 二、实现方式
- (一) 使用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就绪了,判断是不是sockfd
for(i=3;i<max_fd+1 && 0<ret;i++){
//先判断是不是这个fd就绪了
if(FD_ISSET(i,&tempfds)){
ret--;
//判断就绪的fd是不是sockfd
if(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_SOCKET
TCP级别 IPPROTO_TCP
IP级别 IPPROTO_IP
optname:选项的名字
套接字API级别
SO_BROADCAST 允许发送广播
SO_RCVBUF 接收缓冲区的大小
SO_SNDBUF 发送缓冲区的大小
SO_RCVTIMEO 接收超时时间
参数是一个 struct timeval 结构体
如果超时了 调用会返回-1 错误码 为 EAGAIN
SO_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. 端口复用
4. 设置超时时间