1. TCP/IP协议栈

TCP/IP协议栈示意图如下所示:
捕获.PNG

1.1 链路层

链路层是物理链路连接标准化的结果,也是最基本的一层,是所有网络协议栈的基石。

1.2 IP层

IP层通过使用链路层来发送数据,IP层面向的是一个个的数据包,通过选定发送路径将数据包发送出去。因为IP层只负责传输数据包,因此当传输路径发送错误时,IP层可以重新选择路径并重新发送,但如果数据包发生错误或丢失,IP层使无法解决的。

1.3 TCP/UDP层

因IP层无法解决数据丢失/发生错误的情况,因此由上层的TCP/UDP层来对数据包进行控制,利用IP层提供的网络路径信息完成数据。同时TCP由于存在三次握手、四次挥手、数据包接收确认、重传等机制,可以实现数据包的可靠传输。

1.4 应用层

应用层运行的是我们的应用程序,使用socket相关接口完成网络通信、数据传输。

2. 基于TCP的服务端函数调用

下图为基于TCP的服务端socket函数调用顺序:
Untitled Diagram.drawio.png
下面我们一一介绍各API。

2.1 listen函数

listen函数的函数原型:

  1. /**
  2. * @brief 监听连接请求
  3. * @param[in] sock 要监听的套接字
  4. * @param[in] backlog listen队列长度,若队列为5,表示最多可以使5个连接请求进入队列。
  5. * @return 0:成功 -1:失败
  6. */
  7. int listen(int sock, int backlog);

listen函数用于创建队列,调用listen后的套接字,系统会为其维护两个队列:未完成连接队列、已完成连接队列,两个队列的长度不能大于backlog,其中未完成队列保存未完成三次握手的连接,已完成队列保存完成三次握手的队列。

2.2 accept函数

accept函数的函数原型:

  1. /**
  2. * @brief 接收客户端连接
  3. * @param[in] sock 服务器套接字
  4. * @param[out] addr 客户端地址信息
  5. * @param[out] addrlen 客户端地址信息长度
  6. * @return -1:失败 其他:发起连接的客户端套接字
  7. */
  8. int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);

默认状态下,当调用accept函数后,服务端程序会处于阻塞状态。`<br />accept函数`负责受理客户端的连接请求,当连接成功建立后,函数会将高链接移入已完成连接队列,并返回。

3. 基于TCP的客户端函数调用

下图为函数调用顺序:
Untitled Diagram.drawio (1).png
客户端不需要添加网络地址信息,这些都会在执行connect``函数时由系统处理:

  • 添加操作发生在操作系统内核中。
  • 端口、IP信息由操作系统选取,IP地址为本机地址,端口为本机随机空闲端口。

    3.1 connect函数``

    connect函数的函数原型为: ```cpp /**
    • @brief 发起连接请求
    • @param[in] sock 客户端套接字
    • @param[in] addr 服务端地址信息
    • @param[in] addrlen 服务端地址信息长度
    • @return 0:成功 -1:失败 */

int connet(int sock, struct sockaddr *addr, socklen_t addrlen);

  1. `connect``函数`向服务器发起连接请求,若连接成功建立,则返回`0`,若连接建立失败,则返回`-1`
  2. <a name="pR6nz"></a>
  3. ### 4. 三次握手与socket API的关系
  4. ![Untitled Diagram.drawio (2).png](https://cdn.nlark.com/yuque/0/2021/png/1735291/1636184097386-f4dbbd50-3afe-4b38-aa1e-88807fa523ca.png#height=389&id=O665E&margin=%5Bobject%20Object%5D&name=Untitled%20Diagram.drawio%20%282%29.png&originHeight=389&originWidth=375&originalType=binary&ratio=1&size=14521&status=done&style=none&width=375)<br />我们可以看出:
  5. - 三次握手请求由`客户端通过connet函数`发起,服务器只是在被动等待三次握手请求
  6. - `connect``函数`返回发生在三次握手的第二步完成之后
  7. - `accept``函数`返回发生在三次握手完成之后
  8. <a name="zV8Gd"></a>
  9. ### 5. 实例程序:
  10. ```cpp
  11. // server.c
  12. #include <stdio.h>
  13. #include <unistd.h>
  14. #include <string.h>
  15. #include <sys/socket.h>
  16. #include <arpa/inet.h>
  17. #define ACCEPT_NUM 5
  18. #define BUFFER_SIZE 1024
  19. int main(int argc, char *argv[])
  20. {
  21. if (argc < 2)
  22. {
  23. return -1;
  24. }
  25. int i = 0;
  26. int read_len = 0;
  27. socklen_t accept_len = 0;
  28. char buf[BUFFER_SIZE];
  29. int sock_server = 0;
  30. int sock_client = 0;
  31. struct sockaddr_in addr_server;
  32. struct sockaddr_in addr_client;
  33. // 初始化服务器套接字
  34. sock_server = socket(PF_INET, SOCK_STREAM, 0);
  35. if (-1 == sock_server)
  36. {
  37. printf("初始化套接字错误 \n");
  38. return -1;
  39. }
  40. // 初始化网络地址
  41. memset(&addr_server, 0, sizeof(struct sockaddr_in));
  42. addr_server.sin_family = AF_INET;
  43. addr_server.sin_addr.s_addr = inet_addr(argv[1]);
  44. addr_server.sin_port = htons(atoi(argv[2]));
  45. if (-1 == bind(sock_server, (struct sockaddr *)&addr_server, sizeof(addr_server)))
  46. {
  47. printf("绑定网络地址到套接字失败 \n");
  48. return -1;
  49. }
  50. if (-1 == listen(sock_server, ACCEPT_NUM))
  51. {
  52. printf("监听网络套接字失败 \n");
  53. return -1;
  54. }
  55. for (i = 0; i < ACCEPT_NUM; ++i)
  56. {
  57. sock_client = accept(sock_server, (struct sockaddr *)&addr_client, &accept_len);
  58. if(-1 == sock_client)
  59. {
  60. printf("accept失败 \n");
  61. }
  62. else
  63. {
  64. printf("与套接字 %d 客户端建立连接 \n", sock_client);
  65. }
  66. while ((read_len = read(sock_client, buf, BUFFER_SIZE)) != 0)
  67. {
  68. buf[read_len] = '\0';
  69. write(sock_client, buf, read_len);
  70. }
  71. close(sock_client);
  72. }
  73. close(sock_server);
  74. return 0;
  75. }
  76. // client.c
  77. #include <unistd.h>
  78. #include <string.h>
  79. #include <sys/socket.h>
  80. #include <arpa/inet.h>
  81. #define BUFFER_SIZE 1024
  82. int main(int argv, char *argc[])
  83. {
  84. if (2 > argv)
  85. {
  86. return -1;
  87. }
  88. int i = 0, write_len = 0, read_len = 0;
  89. char buf[BUFFER_SIZE] = {0};
  90. int sock_client = 0;
  91. struct sockaddr_in addr_server;
  92. // 创建客户端套接字
  93. sock_client = socket(PF_INET, SOCK_STREAM, 0);
  94. if (-1 == sock_client)
  95. {
  96. printf("初始化套接字错误 \n");
  97. return -1;
  98. }
  99. // 初始化要连接的服务器网络地址
  100. memset(&addr_server, 0, sizeof(struct sockaddr_in));
  101. addr_server.sin_family = AF_INET;
  102. addr_server.sin_addr.s_addr = inet_addr(argc[1]);
  103. addr_server.sin_port = htons(atoi(argc[2]));
  104. if (-1 == connect(sock_client, (struct sockaddr*)&addr_server, sizeof(struct sockaddr)))
  105. {
  106. printf("连接服务器失败 \n");
  107. return -1;
  108. }
  109. while (1)
  110. {
  111. fputs("请输入字符串:", stdout);
  112. fgets(buf, BUFFER_SIZE, stdin);
  113. if (0 == strcmp(buf, "q\n") || 0 == strcmp(buf, "Q\n"))
  114. {
  115. break;
  116. }
  117. write_len = write(sock_client, buf, strlen(buf));
  118. while (read_len < write_len)
  119. {
  120. read_len += read(sock_client, &buf[read_len], BUFFER_SIZE);
  121. }
  122. buf[read_len] = '\0';
  123. read_len = 0;
  124. printf("接收到的字符串:%s \n", buf);
  125. }
  126. close(sock_client);
  127. }