1. TCP/IP协议栈
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函数调用顺序:
下面我们一一介绍各API。
2.1 listen函数
listen函数的函数原型:
/*** @brief 监听连接请求* @param[in] sock 要监听的套接字* @param[in] backlog listen队列长度,若队列为5,表示最多可以使5个连接请求进入队列。* @return 0:成功 -1:失败*/int listen(int sock, int backlog);
listen函数用于创建队列,调用listen后的套接字,系统会为其维护两个队列:未完成连接队列、已完成连接队列,两个队列的长度不能大于backlog,其中未完成队列保存未完成三次握手的连接,已完成队列保存完成三次握手的队列。
2.2 accept函数
accept函数的函数原型:
/*** @brief 接收客户端连接* @param[in] sock 服务器套接字* @param[out] addr 客户端地址信息* @param[out] addrlen 客户端地址信息长度* @return -1:失败 其他:发起连接的客户端套接字*/int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
默认状态下,当调用accept函数后,服务端程序会处于阻塞状态。`<br />accept函数`负责受理客户端的连接请求,当连接成功建立后,函数会将高链接移入已完成连接队列,并返回。
3. 基于TCP的客户端函数调用
下图为函数调用顺序:
客户端不需要添加网络地址信息,这些都会在执行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);
`connect``函数`向服务器发起连接请求,若连接成功建立,则返回`0`,若连接建立失败,则返回`-1`。<a name="pR6nz"></a>### 4. 三次握手与socket API的关系<br />我们可以看出:- 三次握手请求由`客户端通过connet函数`发起,服务器只是在被动等待三次握手请求- `connect``函数`返回发生在三次握手的第二步完成之后- `accept``函数`返回发生在三次握手完成之后<a name="zV8Gd"></a>### 5. 实例程序:```cpp// server.c#include <stdio.h>#include <unistd.h>#include <string.h>#include <sys/socket.h>#include <arpa/inet.h>#define ACCEPT_NUM 5#define BUFFER_SIZE 1024int main(int argc, char *argv[]){if (argc < 2){return -1;}int i = 0;int read_len = 0;socklen_t accept_len = 0;char buf[BUFFER_SIZE];int sock_server = 0;int sock_client = 0;struct sockaddr_in addr_server;struct sockaddr_in addr_client;// 初始化服务器套接字sock_server = socket(PF_INET, SOCK_STREAM, 0);if (-1 == sock_server){printf("初始化套接字错误 \n");return -1;}// 初始化网络地址memset(&addr_server, 0, sizeof(struct sockaddr_in));addr_server.sin_family = AF_INET;addr_server.sin_addr.s_addr = inet_addr(argv[1]);addr_server.sin_port = htons(atoi(argv[2]));if (-1 == bind(sock_server, (struct sockaddr *)&addr_server, sizeof(addr_server))){printf("绑定网络地址到套接字失败 \n");return -1;}if (-1 == listen(sock_server, ACCEPT_NUM)){printf("监听网络套接字失败 \n");return -1;}for (i = 0; i < ACCEPT_NUM; ++i){sock_client = accept(sock_server, (struct sockaddr *)&addr_client, &accept_len);if(-1 == sock_client){printf("accept失败 \n");}else{printf("与套接字 %d 客户端建立连接 \n", sock_client);}while ((read_len = read(sock_client, buf, BUFFER_SIZE)) != 0){buf[read_len] = '\0';write(sock_client, buf, read_len);}close(sock_client);}close(sock_server);return 0;}// client.c#include <unistd.h>#include <string.h>#include <sys/socket.h>#include <arpa/inet.h>#define BUFFER_SIZE 1024int main(int argv, char *argc[]){if (2 > argv){return -1;}int i = 0, write_len = 0, read_len = 0;char buf[BUFFER_SIZE] = {0};int sock_client = 0;struct sockaddr_in addr_server;// 创建客户端套接字sock_client = socket(PF_INET, SOCK_STREAM, 0);if (-1 == sock_client){printf("初始化套接字错误 \n");return -1;}// 初始化要连接的服务器网络地址memset(&addr_server, 0, sizeof(struct sockaddr_in));addr_server.sin_family = AF_INET;addr_server.sin_addr.s_addr = inet_addr(argc[1]);addr_server.sin_port = htons(atoi(argc[2]));if (-1 == connect(sock_client, (struct sockaddr*)&addr_server, sizeof(struct sockaddr))){printf("连接服务器失败 \n");return -1;}while (1){fputs("请输入字符串:", stdout);fgets(buf, BUFFER_SIZE, stdin);if (0 == strcmp(buf, "q\n") || 0 == strcmp(buf, "Q\n")){break;}write_len = write(sock_client, buf, strlen(buf));while (read_len < write_len){read_len += read(sock_client, &buf[read_len], BUFFER_SIZE);}buf[read_len] = '\0';read_len = 0;printf("接收到的字符串:%s \n", buf);}close(sock_client);}
