先看服务端:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
int main()
{
int sockSrv = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_addr.s_addr = INADDR_ANY;
addrSrv.sin_port = htons(8765);
bind(sockSrv, (const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));
listen(sockSrv, 5);
struct sockaddr_in addrClient;
int len = sizeof(struct sockaddr_in);
int sockConn = accept(sockSrv, (struct sockaddr *)&addrClient, (socklen_t*)&len);
while(1)
{
char szRecvBuf[50001] = {0};
int iRet = recv(sockConn, szRecvBuf, sizeof(szRecvBuf) - 1, 0);
printf("iRet is %d\n", iRet);
getchar();
close(sockConn);
}
while(1);
close(sockSrv);
return 0;
}
启动它。
再来看一个带超时时间的connect函数的客户端程序:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <time.h>
int main(int argc, char *argv[]) // 注意输入参数, 带上ip和port, 带上超时参数
{
int sockClient = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addrSrv;
addrSrv.sin_addr.s_addr = inet_addr(argv[1]);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(atoi(argv[2]));
fcntl(sockClient, F_SETFL, fcntl(sockClient, F_GETFL, 0)|O_NONBLOCK);
int iRet = connect(sockClient, ( const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));
printf("connect iRet is %d, errmsg:%s\n", iRet, strerror(errno)); // 返回-1不一定是异常
if (iRet != 0)
{
if(errno != EINPROGRESS)
{
printf("connect error:%s\n",strerror(errno));
}
else
{
int nTime = atoi(argv[3]); // 微秒
struct timeval tm = {0, nTime};
fd_set wset, rset;
FD_ZERO(&wset);
FD_ZERO(&rset);
FD_SET(sockClient, &wset);
FD_SET(sockClient, &rset);
int n = select(sockClient + 1, &rset, &wset, NULL, &tm);
if(n < 0)
{
printf("select error, n is %d\n", n);
}
else if(n == 0)
{
printf("connect time out\n");
}
else if (n == 1)
{
if(FD_ISSET(sockClient, &wset))
{
printf("connect ok!\n");
fcntl(sockClient, F_SETFL, fcntl(sockClient, F_GETFL, 0) & ~O_NONBLOCK);
}
else
{
printf("unknow error:%s\n", strerror(errno));
}
}
else
{
printf("oh, not care now, n is %d\n", n);
}
}
}
printf("I am here!\n");
//getchar();
close(sockClient);
return 0;
}
服务端在10.100.70.140机器上, 且正在监听8765端口, 我们来看看客户端的log和握手过程(connect函数的超时为1微秒):
xxxxxx$ ./client 10.100.70.140 8765 1
connect iRet is -1, errmsg:Operation now in progress
connect time out
I am here!
xxxxxx$ sudo tcpdump -iany port 23456 -Xnlps0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
21:43:40.898224 IP 10.100.70.139.51256 > 10.100.70.140.aequus: Flags [S], seq 948273802, win 14280, options [mss 1428,sackOK,TS val 1560139469 ecr 0,nop,wscale 8], length 0
0x0000: 4500 003c 5447 4000 4006 4496 0a64 468b E..<TG@.@.D..dF.
0x0010: 0a64 468c c838 5ba0 3885 828a 0000 0000 .dF..8[.8.......
0x0020: a002 37c8 a20d 0000 0204 0594 0402 080a ..7.............
0x0030: 5cfd d6cd 0000 0000 0103 0308 0000 0000 \...............
0x0040: 0000 0000 0000 0000 0000 0000 ............
21:43:40.898426 IP 10.100.70.140.aequus > 10.100.70.139.51256: Flags [S.], seq 802450158, ack 948273803, win 14160, options [mss 1428,sackOK,TS val 1560138227 ecr 1560139469,nop,wscale 8], length 0
0x0000: 4500 003c 0000 4000 4006 98dd 0a64 468c E..<..@.@....dF.
0x0010: 0a64 468b 5ba0 c838 2fd4 6aee 3885 828b .dF.[..8/.j.8...
0x0020: a012 3750 9277 0000 0204 0594 0402 080a ..7P.w..........
0x0030: 5cfd d1f3 5cfd d6cd 0103 0308 0000 0000 \...\...........
0x0040: 0000 0000 0000 0000 0000 0000 ............
21:43:40.898450 IP 10.100.70.139.51256 > 10.100.70.140.aequus: Flags [R], seq 948273803, win 0, length 0
0x0000: 4500 0028 04e5 4000 4006 940c 0a64 468b E..(..@.@....dF.
0x0010: 0a64 468c c838 5ba0 3885 828b 0000 0000 .dF..8[.8.......
0x0020: 5004 0000 2f18 0000 0000 0000 0000 0000 P.../...........
0x0030: 0000 0000 0000 0000 ........
看到RST了。
分析下, syn/ack包是在1微秒之后到达的, 所以客户端的select会超时, 代码走入超时逻辑, 等syn/ack到达时, 客户端代码中的超时逻辑中, 执行了close socket的操作。 于是可以这么认为, 经历两次握手后, 客户端调用close socket, 此时发送了RST.
OK, 不多说。