1.套接字I/O超時設置方法
- alarm
因爲該鬧鐘可能會被其它使用,所以一般不使用鬧鐘來實現超時
SIGALRM
void handle(int sig)
{
return 3;
}
signal(SIGALARM, handle);
alarm(5);
在read之前設置一個鬧鐘,若5s鍾內沒有返回數據(到達5s鍾),則會產生一個SIGALRM信號將read打斷
int ret = read(fd, buf, sizeof(buf));
if (ret == -1 && errno == EINTR)//被EINTR打斷
{
errno =ETIMEDOUT;
}
else if (ret >=0 )
{
alarm(0);
}
- 套接字選項
SO_SNDTIMEO發送超時時間
SO_RCVTIMEO接收超時時間
不好移植,因爲一些TCP選項不支持這兩個選項
setsockopt(sock,SOL_SOCKET, SO_RCVTIMEO, 5)
int ret = read(fd, buf, sizeof(buf));
if (ret == -1 && errno == EWOULDBLOCK)//超時的錯誤碼是EWOULDBLOCK
{
errno =ETIMEDOUT;
}
-select
2.用select實現超時
-
同select實現超時
read_timeout函數封裝
write_timeout函數封裝
accept_timeout函數封裝
connect_timeout函數封裝 -
connect_timeout的存在的價值?
(1)RTT:一次往返的時間,
(2)connect要停RTT這麼長的時間才返回,系統默認是75s,在廣域網上可能出現網絡擁塞,connect可能要停比較長的時間才能返回,系統默認75s,對於用於來說是不能容忍的,所以需要編寫connect_timeout函數,用於連接超時的時間
-
eg:NetworkProgramming-master (1)\LinuxNetworkProgramming\P16Timeout
=================NetworkProgramming-master (1)\LinuxNetworkProgramming\P16Timeout\sysutil.c==============
#include <sysutil.h>
/*
read_timeout使用方法
int ret;
ret = read_timeout(fd, 5);
if (ret == 0)//成功返回0,進行讀操作
{
read(fd,....);若wait_seconds=0,則阻塞在read
}
else if (ret == -1 && errno == ETIMEOUT)
{
timeout...
}
else
{
ERR_EXIT("read_timeout");
}
*/
/*
* read timeout - 讀超時檢測函數,不含讀操作
* @fd:文件描述符
* @wait_seconds:等待超時秒數,如果爲0表示不檢測超時
* 成功(未超時)返回0,失敗返回-1,超時返回-1並且errno = ETIMEDOUT
*/
//若檢測到超時,就不進行讀操作,若返回成功未超時,將做讀操作
//read_timeout作用:檢測IO是否超時
int read_timeout(int fd, unsigned int wait_seconds)
{
int ret = 0;
if (wait_seconds > 0)
{
fd_set read_fdset;//讀集合
struct timeval timeout;//超時時間
FD_ZERO(&read_timeout);
FD_SET(fd, &read_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
//超時時間到了,沒有發生可讀事件,那麼返回值爲0
//在超時時間之內,發生可讀事件,返回值爲1
//select調用失敗返回小於0,若小於0是由信號中斷的話(errno == EINTR),則繼續調用select
do
{
ret = select(fd +1, &read_fdset, NULL, NULL, &timeout);
}while(ret < 0 && errno == EINTR);
if (ret == 0 )
{
ret = -1;
errno = ETIMEDOUT;
}
else if (ret == 1)//因爲當前只將一個fd放入到讀集合fdset中
ret = 0;
//若ret= -1,並且errno不等於EINTR,則不做任何處理,直接return
//若wait_seconds=0,則不按照超時的方式進行處理
}
//wait_seconds=0,則直接返回ret=0
return ret;
}
/*
* read timeout - 讀超時檢測函數,不含寫操作
* @fd:文件描述符
* @wait_seconds:等待超時秒數,如果爲0表示不檢測超時
* 成功(未超時)返回0,失敗返回-1,超時返回-1並且errno = ETIMEDOUT
*/
//若檢測到超時,就不進行寫操作,若返回成功未超時,將做寫操作
//write_timeout作用:檢測IO是否超時
int write_timeout(int fd, unsigned int wait_seconds)
{
int ret = 0;
if (wait_seconds > 0)
{
fd_set write_fdset;//寫集合
struct timeval timeout;//超時時間
FD_ZERO(&write_timeout);
FD_SET(fd, &write_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
ret = select(fd +1, &write_timeout, NULL, NULL, &timeout);
}while(ret < 0 && errno == EINTR);
if (ret == 0 )
{
ret = -1;
errno = ETIMEDOUT;
}
else if (ret == 1)//因爲當前只將一個fd放入到讀集合fdset中
ret = 0;
}
return ret;
}
/*
* accept_timeout 帶超時的accept
* fd:套接字,檢測的IO
* addr:輸出參數,返回對方地址
* wait_seconds:等待超時秒數,如果爲0表示正常模式
* 成功(未超時)返回已連接套接字,超時返回-1並且errno = ETIMEDOUT
*/
int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret;
socklen_t addrlen = sizeof(struct sockaddr_in);
if (wait_seconds > 0)
{
fd_set accept_fdset;
struct timeval timeout;
FD_ZERO(&accept_fdset);
FD_SET(fd, &accept_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
//三次握手已完成,已完成連接隊列有一個條目,accept不阻塞,
//就說明accept_fdset集合中產生了可讀事件
ret = select(fd+1, &accept_fdset, NULL, &timeout);
} while (ret < 0 && errno == EINTR);
if (ret == -1)//這裏是ret=-1,errno不等於EINTR
return -1;
else if (ret == 0)//ret=0表示超時了
{
errno = ETIMEDOUT;
return -1;
}
}
//ret=1表示:表示檢測到事件
//wait_seconds=0,則直接調用accept,會阻塞
if (addr != NULL)
ret = accept(fd, (struct sockaddr*)addr, &addrlen);//返回已連接套接字
else
ret = accept(fd, NULL, NULL);
if (ret == -1)
ERR_EXIT("accept");
return ret;
}
/*
* activate_noblock:設置I/O爲非阻塞模式
* @fd:文件描述符
*/
void activate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);//獲取fd的標記
if (flags == -1)
ERR_EXIT("fcntl");
flags |= O_NONBLOCK;//添加非阻塞模式
ret = fcntl(fd, F_SETTFL, flags);
if (ret == -1)
ERR_EXIT("fcntl");
}
/*
* activate_noblock:設置I/O爲阻塞模式
* @fd:文件描述符
*/
void deactivate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
if (flags == -1)
ERR_EXIT("fcntl");
flags &= ~O_NONBLOCK;//去掉非阻塞模式
ret = fcntl(fd, F_SETTFL, flags);
if (ret == -1)
ERR_EXIT("fcntl");
}
/*
* connect_timeout connect
* @fd:套接字
* @addr:要連接的對方地址
* @wait_seconds等待超時秒數,如果爲0表示正常模式
* 成功(未超時)返回0,失敗返回-1,超時返回-1並且errno=ETIMEDOUT
*/
int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret;
socklen_t addrlen = sizeof(struct sockaddr_in);
//不能直接調用connect,直接調用就會阻塞,希望以非阻塞的方式調用,所以fd
//設置爲非阻塞模式
if (wait_seconds > 0)
activate_nonblock(fd);
ret = connect(fd, (struct sockaddr*)addr, addrlen);
if (ret <0 && errno == EINPROGRESS)//非阻塞fd返回一定是EINPROGRESS,表示正在處理中
{
printf("AAAAA");
fd_set connect_fdset;//已連接fd集合
struct timeval timeout;//超時時間
FD_ZERO(connect_fdset);
FD_SET(fd, &connect_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
/* 一旦連接建立,套接字就可寫 */
ret = select(fd+1, NULL, &connect_fdset, NULL, &timeout);
} while (ret <0 && errno == EINTER);
if (ret == 0)//超時時間到了,還沒有產生可寫事件,意味着連接沒成功
{
ret = -1;
errno = ETIMEDOUT;
}
else if (ret < 0)
return -1;
else if (ret == 1)//檢測到可寫事件
{
printf("BBBBB");
//ret返回1,可能有2種情況,一種是連接建立成功,一種是套接字產生錯誤
//此時錯誤信息不會保存至errno變量中,因此,需要調用getsockopt來獲取
int err;
socklen_t socklen = sizeof(err);
int sockoptret = getsockopt(fd, SOL_SOCKET , SO_ERROR, &err, &socklen);//獲取錯誤到err中
if (sockoptret == -1)
{
return -1;
}
if (err == 0)//表示沒有錯誤,連接建立成功
{
printf("DDDDD");
ret = 0;
}
else
{
printf("CCCCC");
errno =err;
ret = -1;
}
}
}
if (wait_seconds >0)
{
deactivate_nonblock(fd);//重新設置爲阻塞模式
}
return ret;
}
========================NetworkProgramming-master (1)\LinuxNetworkProgramming\P16Timeout\sysutil.h=========
#ifndef _SYSUTIL_H
#define _SYSUTIL_H
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
//設置fd爲阻塞或者非阻塞
void activate_nonblock(int fd);
void deactivate_nonblock(int fd);
//超時IO函數
int read_timeout(int fd, unsigned int wait_seconds);
int write_timeout(int fd, unsigned int wait_seconds);
int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds);
int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds);
#endif
=======================NetworkProgramming-master (1)\LinuxNetworkProgramming\P16Timeout\srv.c==========
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
int main(void)
{
int listenfd;
if ((listen = socket(PF_INET, SOCK_STREAM, 0)) <0)
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sinport = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on ,sizeof(on)) < 0)
ERR_EXIT("setsockopt");
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind");
if (listen(listenfd, SOMAXCONN) <0)
ERR_EXIT("listen");
struct sockaddr_in peeraddr;
socklen_t peerlen;
int conn;
if ((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen))< 0)
ERR_EXIT("accept");
printf("ip=%s port =%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
return 0;
}
=======================NetworkProgramming-master (1)\LinuxNetworkProgramming\P16Timeout\cli.c======================
#include <sysutil.h>
int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sinport = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect_timout(sock, &servaddr, 5);
if (ret == -1 && errno == ETIMEDOUT)
{
printf("timeout...\n");
return 1;
}
else if (ret == -1)
ERR_EXIT("connect_timeout");
struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
if (getsockname(sock, (struct sockaddr*)localaddr, &addrlen) < 0)
ERR_EXIT("getsockname");
printf("ip=%s port =%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));//打印本地端口和ip
return 0;
}
- Makefile
NetworkProgramming-master (1)\LinuxNetworkProgramming\P16Timeout\Makefile
.PHONY:clean all
CC=gcc
CFLAGS=-Wall -g
BIN=cli srv
all:$(BIN)
%.o:%.c
$(cc) $(CFLAGS) -c $< -o $@
cli:cli.o sysutil.o
$(cc) $(CFLAGS) $^ -o $@
srv:srv.o sysutil.o
$(cc) $(CFLAGS) $^ -o $@
clean:
rm -f *.o $(BIN)
-
測試1:
-
測試2:
若只執行客戶端,可以看一下connect_timeout是如何調用的,在裏面增加了相關標記代碼