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

客官°小女子只卖身不卖艺 2022-03-29 09:23 325阅读 0赞

昨晚写了这样的一个程序,目地是用来测试connect超时连接.代码如下: 
客户端

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. #include <netinet/in.h>
  4. #include <arpa/inet.h>
  5. #include <stdlib.h>
  6. #include <assert.h>
  7. #include <stdio.h>
  8. #include <errno.h>
  9. #include <fcntl.h>
  10. #include <unistd.h>
  11. #include <string.h>
  12. int timeout_connect(const char *ip, int port, int time) // 5
  13. {
  14. int ret = 0;
  15. struct sockaddr_in address;
  16. bzero(&address, sizeof(address));
  17. address.sin_family = AF_INET;
  18. inet_pton(AF_INET, ip, &address.sin_addr);
  19. address.sin_port = htons(port);
  20. int sockfd = socket(PF_INET, SOCK_STREAM, 0);
  21. assert(sockfd >= 0);
  22. struct timeval timeout;
  23. timeout.tv_sec = time;
  24. timeout.tv_usec = 0;
  25. socklen_t len = sizeof(timeout);
  26. ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len);
  27. assert(ret != -1);
  28. ret = connect(sockfd, (struct sockaddr *)&address, sizeof(address));
  29. printf("ret ==%d \n", ret);
  30. if (ret == -1)
  31. {
  32. if (errno == EINPROGRESS)
  33. {
  34. printf("connecting timeout\n");
  35. return -1;
  36. }
  37. printf("error occur when connecting to server\n");
  38. return -1;
  39. }
  40. printf("%d\n", sockfd);
  41. return sockfd;
  42. }
  43. int main(int argc, char *argv[])
  44. {
  45. if (argc <= 2)
  46. {
  47. printf("usage: %s ip_address port_number\n", basename(argv[0]));
  48. return 1;
  49. }
  50. const char *ip = argv[1];
  51. int port = atoi(argv[2]);
  52. int sockfd = timeout_connect(ip, port, 5);
  53. if (sockfd < 0)
  54. {
  55. return 1;
  56. }
  57. return 0;
  58. }

服务器

  1. #include "../myhead.h"
  2. void fun(int connfd)
  3. {
  4. ssize_t n;
  5. char buf[1024] = { 0};
  6. while (1)
  7. {
  8. bzero(buf, sizeof(buf));
  9. n = Recvline(connfd, buf, 1024, 0);
  10. if (n <= 0)
  11. {
  12. printf("对端关闭\n");
  13. Close(connfd);
  14. break;
  15. }
  16. Sendlen(connfd, buf, n, 0);
  17. }
  18. }
  19. int main(int argc, char **argv)
  20. {
  21. int listenfd, connfd;
  22. pid_t childpid;
  23. socklen_t clilen;
  24. struct sockaddr_in cliaddr, servaddr;
  25. const char *ip = argv[1];
  26. const int port = atoi(argv[2]);
  27. listenfd = Socket(AF_INET, SOCK_STREAM, 0);
  28. bzero(&servaddr, sizeof(servaddr));
  29. servaddr.sin_family = AF_INET;
  30. //servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  31. inet_pton(AF_INET, ip, &servaddr.sin_addr);
  32. servaddr.sin_port = htons(port); //9877
  33. int opt = 1;
  34. setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (int *)&opt, sizeof(int));
  35. Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
  36. Listen(listenfd, LISTENQ);
  37. for (;;)
  38. {
  39. clilen = sizeof(cliaddr);
  40. printf("stsrt sleep !!!!!!\n");
  41. sleep(100); //注意这里的 sleep
  42. printf("end sleep ......\n");
  43. connfd = Accept(listenfd, (SA *)&cliaddr, &clilen);
  44. if ((childpid = Fork()) == 0)
  45. { /* child process */
  46. Close(listenfd); /* close listening socket */
  47. printf("新的连接:connfd== %d \n", connfd);
  48. fun(connfd); /* process the request */
  49. exit(0);
  50. }
  51. Close(connfd); /* parent closes connected socket */
  52. }
  53. }

我们为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参数

  1. nc 1.0.0.1 10000 -w 5

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


为什么需要三次握手?

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

发表评论

表情:
评论列表 (有 0 条评论,325人围观)

还没有评论,来说两句吧...

相关阅读

    相关 钱,真像那样吗?

    本文写作目的:复述、警示自己 人们想多赚钱。对于家境普通的人来说,比较普遍的赚钱路子就是读书,要读好的学校好的专业,最好还能拿到最高的学位。然而,仅仅有靠读书赚钱的想法还是不