简单来说,连接超时就是当客户端调用connect函数跟服务端建立连接,等待一段时间后,最后connect函数返回ETIMEDOUT错误,建立连接失败。那么连接超时具体是怎么出现的呢?一般是客户端调用connect发送的SYN报文在网络传输过程中发生网络拥塞,导致报文丢失或服务端收到SYN,但未及时响应。
而这种情况一般发生在服务端的可能性比较大,因为服务端所处的网络流量环境负载通常都很高,如果发生网络拥塞,又或者服务器被D-Dos攻击了,那么是极有可能出现连接超时这种情况的。
为了模拟这种情况,通过ifconfig命令可以查看网卡信息,我们把服务端eth3网卡的所有SYN包都过滤掉:
iptables -F
iptables -I INPUT -p tcp --syn -i eth3 -j DROP
服务端程序:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#define SERV_PORT 10001
#define SERV_IP "192.168.0.107"
int main(void) {
int sfd, cfd;
int len, i;
//BUFSIZ是系统内嵌的一个宏,用来指定buf大小
char buf[BUFSIZ], clie_IP[BUFSIZ];
struct sockaddr_in serv_addr, clie_addr;
socklen_t clie_addr_len;
sfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET , SERV_IP , &serv_addr.sin_addr.s_addr);
serv_addr.sin_port = htons(SERV_PORT);
bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
listen(sfd, 64);
printf("wait for client connect ...\n");
clie_addr_len = sizeof(clie_addr);
//阻塞,等待客户端发起连接
cfd = accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
//打印客户端的ip地址和端口号
printf("client IP:%s\tport:%d\n",
inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),
ntohs(clie_addr.sin_port));
while (1) {
//循环读取客户端的数据请求
len = read(cfd, buf, sizeof(buf));
//read返回0说明对端已经关闭
if(len == 0)
{
break;
}
write(STDOUT_FILENO, buf, len);
//处理客户端数据,小写转大写
for (i = 0; i < len; i++){
buf[i] = toupper(buf[i]);
}
write(cfd, buf, len);
}
close(sfd);
close(cfd);
return 0;
}
客户端程序:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#define SERV_IP "192.168.0.107"
#define SERV_PORT 10001
int main(void) {
int sfd, len,ret;
struct sockaddr_in serv_addr;
char buf[BUFSIZ];
sfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);
serv_addr.sin_port = htons(SERV_PORT);
//建立连接
ret = connect(sfd, (struct sockaddr *)&serv_addr , sizeof(serv_addr));
if(ret < 0){
perror("connect error:");
exit(0);
}
//循环读写数据
while (1) {
fgets(buf, sizeof(buf), stdin);
//将数据写给服务器
write(sfd, buf, strlen(buf));
//从服务器读取转换后数据
len = read(sfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
if(buf[0] == 'Q'){
break;
}
}
close(sfd);
return 0;
}
./server先启动服务端,然后./client再启动客户端,通过tcpdump抓取到的报文如下:
客户端总共向服务端发送了6个YSN报文,后面5个SYN是重传的,每一次重传SYN包的间隔时间分别是1s,2s,4s,8s,16s,这些时间累积加起来总共为31s,也就是说客户端的SYN超时重传的时间间隔采用了指数退避算法增长的,而在重传最后一个报文其实还等待了32s,也就是说总共等待了63s才超时。
换句话说,客户端在第一次发送SYN报文时就会启动一个计时器,如果在该计时器的时间内还未收到对端的ACK,那么将超时重传。当然这个超时重传也是有次数的,不会一直重传,从tcpdump抓取到的数据包来看,客户端一旦重传超过5次,那么客户端tcp将会关闭这条连接。
最后connect函数返回失败,然后退出。