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


    昨晚写了这样的一个程序,目地是用来测试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)可能不会是同一条路.我们需要确保两条路都是通的.见上面的图进行理解@图

  • 相关阅读:
    mysql获取当前时间,及其相关操作
    notepad++ 样式随我变!
    MySQL索引的创建、删除和查看
    so easy 的弹出层——使用jquery
    mysql获取当前时间,及其相关操作
    侯捷大师畅谈技术人生与读书感悟
    《海量数据库解决方案》之聚簇表的代价
    博文视点大讲堂第44期——招聘真相全揭秘 圆满结束
    程序员修炼道路上的“葵花宝典”——博文视点大讲堂42期快乐结束
    众专家推荐
  • 原文地址:https://www.cnblogs.com/Tattoo-Welkin/p/10335240.html
Copyright © 2020-2023  润新知