💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 网络,第 4 部分:构建一个简单的 TCP 服务器 > 原文:<https://github.com/angrave/SystemProgramming/wiki/Networking%2C-Part-4%3A-Building-a-simple-TCP-Server> ## 什么是`htons`以及何时使用? 整数可以首先表示最低有效字节或最高有效字节。只要机器本身内部一致,任何一种方法都是合理的。对于网络通信,我们需要对商定的格式进行标准化。 `htons(xyz)`以网络字节顺序返回 16 位无符号整数'short'值 xyz。 `htonl(xyz)`以网络字节顺序返回 32 位无符号整数'long'值 xyz。 这些功能被读作“主机到网络”;反函数(ntohs,ntohl)将网络有序字节值转换为主机有序排序。那么,是主机订购 little-endian 还是 big-endian?答案是 - 这取决于你的机器!它取决于运行代码的主机的实际体系结构。如果体系结构恰好与网络排序相同,那么这些函数的结果就是参数。对于 x86 机器,主机和网络订购 _ 与 _ 不同。 简介:每当您读取或写入低级 C 网络结构(例如端口和地址信息)时,请记住使用上述功能以确保正确转换为/从数据库格式转换。否则显示或指定的值可能不正确。 ## 用于创建服务器的“4 大”网络呼叫是什么? 创建 TCP 服务器所需的四个系统调用是:`socket`,`bind` `listen`和`accept`。每个都有特定的目的,应按上述顺序调用 端口信息(由 bind 使用)可以手动设置(许多较旧的仅使用 IPv4 的 C 代码示例执行此操作),或使用`getaddrinfo`创建 我们以后也会看到 setsockopt 的例子。 ## 调用`socket`的目的是什么? 为网络通信创建端点。一个新的插座本身并不是特别有用;虽然我们已经指定了数据包或基于流的连接,但它并未绑定到特定的网络接口或端口。相反,套接字返回一个网络描述符,可以用于以后调用 bind,listen 和 accept。 ## 调用`bind`的目的是什么 `bind`调用将抽象套接字与实际网络接口和端口相关联。可以在 TCP 客户端上调用 bind,但是通常不需要指定传出端口。 ## 调用`listen`的目的是什么 `listen`调用指定传入的未处理连接数的队列大小,即尚未通过`accept`分配网络描述符的队列大小。高性能服务器的典型值为 128 或更多。 ## 为什么服务器套接字被动? 服务器套接字不会主动尝试连接到另一台主机;相反,他们等待传入的连接。此外,当对等设备断开连接时,服务器套接字不会关闭。相反,当远程客户端连接时,它会立即被撞到未使用的端口号以供将来通信。 ## 调用`accept`的目的是什么 初始化服务器套接字后,服务器调用`accept`以等待新连接。与`socket` `bind`和`listen`不同,此调用将被阻止。即,如果没有新连接,则此呼叫将阻止,并且仅在新客户端连接时返回。 注意`accept`调用返回一个新的文件描述符。此文件描述符特定于特定客户端。将原始服务器套接字描述符用于服务器 I / O 然后想知道为什么网络代码失败是常见的编程错误。 ## 创建 TCP 服务器的难点是什么? * 使用被动服务器套接字的套接字描述符(如上所述) * 未指定 getaddrinfo 的 SOCK_STREAM 要求 * 无法重新使用现有端口。 * 不初始化未使用的 struct 条目 * 如果当前正在使用端口,则`bind`调用将失败 注意,端口是每台机器 - 不是每个进程或每个用户。换句话说,当另一个进程正在使用该端口时,您无法使用端口 1234。更糟糕的是,端口在进程完成后默认为“绑定”。 ## 服务器代码示例 一个工作简单的服务器示例如下所示。请注意,此示例不完整 - 例如,它不会关闭套接字描述符,也不会释放由`getaddrinfo`创建的内存 ```c #include <string.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <unistd.h> #include <arpa/inet.h> int main(int argc, char **argv) { int s; int sock_fd = socket(AF_INET, SOCK_STREAM, 0); struct addrinfo hints, *result; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; s = getaddrinfo(NULL, "1234", &hints, &result); if (s != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); exit(1); } if (bind(sock_fd, result->ai_addr, result->ai_addrlen) != 0) { perror("bind()"); exit(1); } if (listen(sock_fd, 10) != 0) { perror("listen()"); exit(1); } struct sockaddr_in *result_addr = (struct sockaddr_in *) result->ai_addr; printf("Listening on file descriptor %d, port %d\n", sock_fd, ntohs(result_addr->sin_port)); printf("Waiting for connection...\n"); int client_fd = accept(sock_fd, NULL, NULL); printf("Connection made: client_fd=%d\n", client_fd); char buffer[1000]; int len = read(client_fd, buffer, sizeof(buffer) - 1); buffer[len] = '\0'; printf("Read %d chars\n", len); printf("===\n"); printf("%s\n", buffer); return 0; } ``` ## 为什么我的服务器无法重新使用该端口? 默认情况下,套接字关闭时不会立即释放端口。相反,端口进入“TIMED-WAIT”状态。这可能会在开发过程中导致严重的混淆,因为超时可能会使有效的网络代码看起来失败。 为了能够立即重新使用端口,请在绑定到端口之前指定`SO_REUSEPORT`。 ```c int optval = 1; setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); bind(.... ``` 这是[对`SO_REUSEPORT`](http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t) 的扩展 stackoverflow 介绍性讨论。