Skip to content

Latest commit

 

History

History
128 lines (85 loc) · 5.86 KB

58.md

File metadata and controls

128 lines (85 loc) · 5.86 KB

网络,第 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 服务器所需的四个系统调用是:socketbind listenaccept。每个都有特定的目的,应按上述顺序调用

端口信息(由 bind 使用)可以手动设置(许多较旧的仅使用 IPv4 的 C 代码示例执行此操作),或使用getaddrinfo创建

我们以后也会看到 setsockopt 的例子。

调用socket的目的是什么?

为网络通信创建端点。一个新的插座本身并不是特别有用;虽然我们已经指定了数据包或基于流的连接,但它并未绑定到特定的网络接口或端口。相反,套接字返回一个网络描述符,可以用于以后调用 bind,listen 和 accept。

调用bind的目的是什么

bind调用将抽象套接字与实际网络接口和端口相关联。可以在 TCP 客户端上调用 bind,但是通常不需要指定传出端口。

调用listen的目的是什么

listen调用指定传入的未处理连接数的队列大小,即尚未通过accept分配网络描述符的队列大小。高性能服务器的典型值为 128 或更多。

为什么服务器套接字被动?

服务器套接字不会主动尝试连接到另一台主机;相反,他们等待传入的连接。此外,当对等设备断开连接时,服务器套接字不会关闭。相反,当远程客户端连接时,它会立即被撞到未使用的端口号以供将来通信。

调用accept的目的是什么

初始化服务器套接字后,服务器调用accept以等待新连接。与socket bindlisten不同,此调用将被阻止。即,如果没有新连接,则此呼叫将阻止,并且仅在新客户端连接时返回。

注意accept调用返回一个新的文件描述符。此文件描述符特定于特定客户端。将原始服务器套接字描述符用于服务器 I / O 然后想知道为什么网络代码失败是常见的编程错误。

创建 TCP 服务器的难点是什么?

  • 使用被动服务器套接字的套接字描述符(如上所述)
  • 未指定 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 介绍性讨论。