昨晚写了这样的一个程序,目地是用来测试connect
超时连接.代码如下:
客户端
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int timeout_connect(const char *ip, int port, int time) // 5
{
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
assert(sockfd >= 0);
struct timeval timeout;
timeout.tv_sec = time;
timeout.tv_usec = 0;
socklen_t len = sizeof(timeout);
ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len);
assert(ret != -1);
ret = connect(sockfd, (struct sockaddr *)&address, sizeof(address));
printf("ret ==%d \n", ret);
if (ret == -1)
{
if (errno == EINPROGRESS)
{
printf("connecting timeout\n");
return -1;
}
printf("error occur when connecting to server\n");
return -1;
}
printf("%d\n", sockfd);
return sockfd;
}
int main(int argc, char *argv[])
{
if (argc <= 2)
{
printf("usage: %s ip_address port_number\n", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
int sockfd = timeout_connect(ip, port, 5);
if (sockfd < 0)
{
return 1;
}
return 0;
}
服务器
#include "../myhead.h"
void fun(int connfd)
{
ssize_t n;
char buf[1024] = {0};
while (1)
{
bzero(buf, sizeof(buf));
n = Recvline(connfd, buf, 1024, 0);
if (n <= 0)
{
printf("对端关闭\n");
Close(connfd);
break;
}
Sendlen(connfd, buf, n, 0);
}
}
int main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
const char *ip = argv[1];
const int port = atoi(argv[2]);
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
//servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
inet_pton(AF_INET, ip, &servaddr.sin_addr);
servaddr.sin_port = htons(port); //9877
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (int *)&opt, sizeof(int));
Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for (;;)
{
clilen = sizeof(cliaddr);
printf("stsrt sleep !!!!!!\n");
sleep(100); //注意这里的 sleep
printf("end sleep ......\n");
connfd = Accept(listenfd, (SA *)&cliaddr, &clilen);
if ((childpid = Fork()) == 0)
{ /* child process */
Close(listenfd); /* close listening socket */
printf("新的连接:connfd== %d \n", connfd);
fun(connfd); /* process the request */
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}
我们为connect
设置5秒的时间进行连接,一旦超过5秒,就终止程序.那么我们如何编写服务器程序去让他终止呐??自然我们会想到在accept 函数之前sleep()一会即可!!!
然而这样子对不对呐?你可以自己先试一下哦
那么我在这里直接告诉答案,那就是
不对!!! 完全不对
OK
,那我们现在来看看为什么不对,免得你说我吹比8^8
首先,我们从三次握手讲起
初始化状态:
服务器端在调用listen
之后,内核会为之建立两个队列,SYN
队列和ACCEPT
队列,其中ACCEPT
队列的长度由backlog
指定(内核版本>2.2)。
服务器端和客户端初始的状态都是CLOSED
,服务器端经过socket
、bind
、listen
进入LISTEN
监听状态 .之后调用accept
,将阻塞,等待ACCEPT
队列有元素。有元素就立马返回一个连接套接字
三次握手:
- 客户端首先通过
connect
函数发送一个SYN
(synchronize)给服务器端,自己进入SYN_SENT
状态.这是第一次握手.这时候还处于connect
函数,也就是说connect
函数还没有返回. - 服务器端接收到
SYN
并进入到SYN_RCVD
状态,把请求方放入SYN
队列中,并给客户端回复一个确认帧ACK
,此帧还会携带一个请求与客户端建立连接的请求标志,也就是SYN
,这称为第二次握手. - 这边客户端接收到
SYN/ACK
后,connect
函数立马返回!!与此同时进入ESTABLISHED
状态,正常收发数据.并发送确认建立连接帧ACK
给服务器端。这称为第三次握手.
之后服务器端收到ACK
帧后,会把请求方从SYN
队列中移出,放至ACCEPT
队列中,而accept函数
也等到了自己的资源,从阻塞中唤醒,从ACCEPT
队列中取出请求方,重新建立一个新的sockfd
,并返回.
以上就是connect
,accept
,listen
这几个函数的工作流程与原理.对于我而言主要有两点感悟:
1. 服务端:三次握手与acccept()
真的一点关系都没有,全是TCP
协议栈整的.accept
只是一个张大嘴巴等饭的人,没有就一直张着,有就吧嘴闭上开始运行.
2. 客户端: 在connect
调用到返回的过程中,发生了两次握手.connect
一旦返回,要么连接成功,直接可以进行收发数据,要么连接失败返回.
那么现在你知道是为什么不对了吗?
OK,我相信你知道了.那么我们就通过连接一个不可达的server去测试我们的客户端就行了
另外,Linux
系统上也提供了nc
命令去自己设置超时,使用nc -w
参数
nc 1.0.0.1 10000 -w 5
出来的效果与上面截图的效果相同