# 网络,第 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 介绍性讨论。
- UIUC CS241 系统编程中文讲义
- 0. 简介
- #Informal 词汇表
- #Piazza:何时以及如何寻求帮助
- 编程技巧,第 1 部分
- 系统编程短篇小说和歌曲
- 1.学习 C
- C 编程,第 1 部分:简介
- C 编程,第 2 部分:文本输入和输出
- C 编程,第 3 部分:常见问题
- C 编程,第 4 部分:字符串和结构
- C 编程,第 5 部分:调试
- C 编程,复习题
- 2.进程
- 进程,第 1 部分:简介
- 分叉,第 1 部分:简介
- 分叉,第 2 部分:Fork,Exec,等等
- 进程控制,第 1 部分:使用信号等待宏
- 进程复习题
- 3.内存和分配器
- 内存,第 1 部分:堆内存简介
- 内存,第 2 部分:实现内存分配器
- 内存,第 3 部分:粉碎堆栈示例
- 内存复习题
- 4.介绍 Pthreads
- Pthreads,第 1 部分:简介
- Pthreads,第 2 部分:实践中的用法
- Pthreads,第 3 部分:并行问题(奖金)
- Pthread 复习题
- 5.同步
- 同步,第 1 部分:互斥锁
- 同步,第 2 部分:计算信号量
- 同步,第 3 部分:使用互斥锁和信号量
- 同步,第 4 部分:临界区问题
- 同步,第 5 部分:条件变量
- 同步,第 6 部分:实现障碍
- 同步,第 7 部分:读者编写器问题
- 同步,第 8 部分:环形缓冲区示例
- 同步复习题
- 6.死锁
- 死锁,第 1 部分:资源分配图
- 死锁,第 2 部分:死锁条件
- 死锁,第 3 部分:餐饮哲学家
- 死锁复习题
- 7.进程间通信&amp;调度
- 虚拟内存,第 1 部分:虚拟内存简介
- 管道,第 1 部分:管道介绍
- 管道,第 2 部分:管道编程秘密
- 文件,第 1 部分:使用文件
- 调度,第 1 部分:调度过程
- 调度,第 2 部分:调度过程:算法
- IPC 复习题
- 8.网络
- POSIX,第 1 部分:错误处理
- 网络,第 1 部分:简介
- 网络,第 2 部分:使用 getaddrinfo
- 网络,第 3 部分:构建一个简单的 TCP 客户端
- 网络,第 4 部分:构建一个简单的 TCP 服务器
- 网络,第 5 部分:关闭端口,重用端口和其他技巧
- 网络,第 6 部分:创建 UDP 服务器
- 网络,第 7 部分:非阻塞 I O,select()和 epoll
- RPC,第 1 部分:远程过程调用简介
- 网络复习题
- 9.文件系统
- 文件系统,第 1 部分:简介
- 文件系统,第 2 部分:文件是 inode(其他一切只是数据...)
- 文件系统,第 3 部分:权限
- 文件系统,第 4 部分:使用目录
- 文件系统,第 5 部分:虚拟文件系统
- 文件系统,第 6 部分:内存映射文件和共享内存
- 文件系统,第 7 部分:可扩展且可靠的文件系统
- 文件系统,第 8 部分:从 Android 设备中删除预装的恶意软件
- 文件系统,第 9 部分:磁盘块示例
- 文件系统复习题
- 10.信号
- 过程控制,第 1 部分:使用信号等待宏
- 信号,第 2 部分:待处理的信号和信号掩码
- 信号,第 3 部分:提高信号
- 信号,第 4 部分:信号
- 信号复习题
- 考试练习题
- 考试主题
- C 编程:复习题
- 多线程编程:复习题
- 同步概念:复习题
- 记忆:复习题
- 管道:复习题
- 文件系统:复习题
- 网络:复习题
- 信号:复习题
- 系统编程笑话