整数可以首先表示最低有效字节或最高有效字节。只要机器本身内部一致,任何一种方法都是合理的。对于网络通信,我们需要对商定的格式进行标准化。
htons(xyz)
以网络字节顺序返回 16 位无符号整数'short'值 xyz。 htonl(xyz)
以网络字节顺序返回 32 位无符号整数'long'值 xyz。
这些功能被读作“主机到网络”;反函数(ntohs,ntohl)将网络有序字节值转换为主机有序排序。那么,是主机订购 little-endian 还是 big-endian?答案是 - 这取决于你的机器!它取决于运行代码的主机的实际体系结构。如果体系结构恰好与网络排序相同,那么这些函数的结果就是参数。对于 x86 机器,主机和网络订购 _ 与 _ 不同。
简介:每当您读取或写入低级 C 网络结构(例如端口和地址信息)时,请记住使用上述功能以确保正确转换为/从数据库格式转换。否则显示或指定的值可能不正确。
创建 TCP 服务器所需的四个系统调用是:socket
,bind
listen
和accept
。每个都有特定的目的,应按上述顺序调用
端口信息(由 bind 使用)可以手动设置(许多较旧的仅使用 IPv4 的 C 代码示例执行此操作),或使用getaddrinfo
创建
我们以后也会看到 setsockopt 的例子。
为网络通信创建端点。一个新的插座本身并不是特别有用;虽然我们已经指定了数据包或基于流的连接,但它并未绑定到特定的网络接口或端口。相反,套接字返回一个网络描述符,可以用于以后调用 bind,listen 和 accept。
bind
调用将抽象套接字与实际网络接口和端口相关联。可以在 TCP 客户端上调用 bind,但是通常不需要指定传出端口。
listen
调用指定传入的未处理连接数的队列大小,即尚未通过accept
分配网络描述符的队列大小。高性能服务器的典型值为 128 或更多。
服务器套接字不会主动尝试连接到另一台主机;相反,他们等待传入的连接。此外,当对等设备断开连接时,服务器套接字不会关闭。相反,当远程客户端连接时,它会立即被撞到未使用的端口号以供将来通信。
初始化服务器套接字后,服务器调用accept
以等待新连接。与socket
bind
和listen
不同,此调用将被阻止。即,如果没有新连接,则此呼叫将阻止,并且仅在新客户端连接时返回。
注意accept
调用返回一个新的文件描述符。此文件描述符特定于特定客户端。将原始服务器套接字描述符用于服务器 I / O 然后想知道为什么网络代码失败是常见的编程错误。
- 使用被动服务器套接字的套接字描述符(如上所述)
- 未指定 getaddrinfo 的 SOCK_STREAM 要求
- 无法重新使用现有端口。
- 不初始化未使用的 struct 条目
- 如果当前正在使用端口,则
bind
调用将失败
注意,端口是每台机器 - 不是每个进程或每个用户。换句话说,当另一个进程正在使用该端口时,您无法使用端口 1234。更糟糕的是,端口在进程完成后默认为“绑定”。
一个工作简单的服务器示例如下所示。请注意,此示例不完整 - 例如,它不会关闭套接字描述符,也不会释放由getaddrinfo
创建的内存
#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
。
int optval = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
bind(....
这是对SO_REUSEPORT
的扩展 stackoverflow 介绍性讨论。