你以为你以为的只是你以为的

昨晚写了这样的一个程序,目地是用来测试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 
", ret);

	if (ret == -1)
	{
		if (errno == EINPROGRESS)
		{
			printf("connecting timeout
");
			return -1;
		}
		printf("error occur when connecting to server
");
		return -1;
	}
	printf("%d
", sockfd);
	return sockfd;
}

int main(int argc, char *argv[])
{
	if (argc <= 2)
	{
		printf("usage: %s ip_address port_number
", 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("对端关闭
");
			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 !!!!!!
");
		sleep(100); //注意这里的 sleep 
        printf("end sleep ......
");
		connfd = Accept(listenfd, (SA *)&cliaddr, &clilen);
		if ((childpid = Fork()) == 0)
		{					 /* child process */
			Close(listenfd); /* close listening socket */

			printf("新的连接:connfd== %d 
", 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,服务器端经过socketbindlisten进入LISTEN监听状态 .之后调用accept,将阻塞,等待ACCEPT队列有元素。有元素就立马返回一个连接套接字

三次握手:

  1. 客户端首先通过connect函数发送一个SYN(synchronize)给服务器端,自己进入SYN_SENT状态.这是第一次握手.这时候还处于connect函数,也就是说connect函数还没有返回.
  2. 服务器端接收到SYN 并进入到SYN_RCVD状态,把请求方放入SYN队列中,并给客户端回复一个确认帧ACK,此帧还会携带一个请求与客户端建立连接的请求标志,也就是SYN,这称为第二次握手.
  3. 这边客户端接收到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

出来的效果与上面截图的效果相同


为什么需要三次握手?

一句话概括:因为来的路(client->server)和去的路(server->client)可能不会是同一条路.我们需要确保两条路都是通的.见上面的图进行理解@图

原文地址:https://www.cnblogs.com/Tattoo-Welkin/p/10335240.html