Bootstrap

网络通信 Posix API的原理与使用

1、Posix API简介

POSIX(Portable Operating System Interface) 是一个为操作系统提供标准化接口的系列规范,旨在确保程序在不同 UNIX 操作系统和兼容系统之间具有可移植性。POSIX API 是一组标准的系统调用和库函数,它们定义了程序与操作系统交互的接口。POSIX API 主要涉及文件操作、进程管理、线程管理、信号处理、网络通信等方面。

2、Posix 网络 API简介

POSIX 网络 API 是一组标准化的接口,主要用于在 UNIX 和类 UNIX 操作系统(如 Linux、macOS)上进行网络编程。它们允许程序员创建、管理和通信网络套接字。POSIX 网络 API 通常基于 Berkeley 套接字(BSD Sockets)API,其主要功能包括套接字创建、绑定、监听、连接、发送和接收数据等。

3、API 具体介绍

(1)套接字(socket)

套接字是网络通信的基本单元,通过它,应用程序可以发送和接收数据。POSIX 提供了一系列函数来操作套接字。

socket()

创建一个新的套接字。

int socket(int domain, int type, int protocol);

domain: 指定通信协议族,如 AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX(本地通信)、AF_PACKET(底层套接字,直接在链路层发送和接收原始数据包)。
type: 指定套接字类型,如 SOCK_STREAM(流式套接字,面向连接)或 SOCK_DGRAM(数据报套接字,无连接)、SOCK_RAW(原始套接字,允许直接访问下层协议)、SOCK_SEQPACKET(有序数据包套接字)。
protocol: 指定协议类型,通常使用 0 以选择默认协议。IPPROTO_TCP:指定使用 TCP 协议(通常与 SOCK_STREAM 搭配使用)。IPPROTO_UDP:指定使用 UDP 协议(通常与 SOCK_DGRAM 搭配使用)。IPPROTO_ICMP:指定使用 ICMP 协议(通常与 SOCK_RAW 搭配使用)。
返回值: 成功返回一个非负整数表示文件描述符(Socket Descriptor)。失败返回-1,并设置 error 变量表示错误类型。EACCES:权限不足,通常出现在尝试创建原始套接字时。EMFILE:当前进程已打开的文件描述符过多,超过了系统限制。ENFILE:系统级别的打开文件描述符数超过限制。EAFNOSUPPORT:指定的地址族不支持。EPROTONOSUPPORT:指定的协议不支持。EPROTOTYPE:指定的协议不匹配所选的套接字类型。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int sockfd;

    // 创建一个 IPv4 的 TCP 套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    printf("Socket created successfully, sockfd: %d\n", sockfd);

    // 关闭套接字
    close(sockfd);
    return 0;
}

bind()

将套接字绑定到特定的地址和端口。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd: 由 socket() 返回的套接字描述符。
addr: 指向 sockaddr 结构体的指针,包含套接字的地址信息。
addrlen: sockaddr 结构体的大小。
返回值: 返回 0,表示绑定成功。失败返回 -1,并设置 errno 以指示具体的错误类型。EACCES:权限不足,通常在绑定到特定端口(如低于 1024 的端口)时发生。EADDRINUSE:指定的地址或端口已被占用。EBADF:sockfd 不是有效的文件描述符。EINVAL:sockfd 已经绑定了另一个地址,或地址不适用于套接字。ENOTSOCK:sockfd 并不是一个套接字描述符。EADDRNOTAVAIL:指定的地址在本地机器上不可用。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    int sockfd;
    struct sockaddr_in servaddr;

    // 创建一个 IPv4 的 TCP 套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 填充服务器地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有本地地址
    servaddr.sin_port = htons(8080);        // 绑定到端口 8080

    // 绑定套接字到指定地址和端口
    if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("bind");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("Socket bound to port 8080\n");

    // 关闭套接字
    close(sockfd);
    return 0;
}

listen()

将一个套接字标记为被动套接字,用于接收连接请求(通常在服务器端使用)。

int listen(int sockfd, int backlog);

sockfd: 套接字描述符。
backlog: 等待连接队列的最大长度。实际能接受的 backlog 值可能受到系统的 SOMAXCONN 限制(在大多数系统中 SOMAXCONN 的值为 128)。
返回值: 返回 0,表示监听操作成功。失败返回 -1,并设置 errno 以指示具体的错误类型。EBADF:sockfd 不是有效的文件描述符。ENOTSOCK:sockfd 不是一个套接字描述符。EOPNOTSUPP:该套接字不支持监听操作(例如,非流式套接字)。EADDRINUSE:该地址已经被绑定到另一个套接字,且不允许重用。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    int sockfd;
    struct sockaddr_in servaddr;

    // 创建一个 IPv4 的 TCP 套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 填充服务器地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有本地地址
    servaddr.sin_port = htons(8080);        // 绑定到端口 8080

    // 绑定套接字到指定地址和端口
    if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("bind");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 将套接字设置为监听模式,等待连接请求
    if (listen(sockfd, 10) == -1) {
        perror("listen");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("Server is listening on port 8080...\n");

    // 通常在这里会调用 accept() 以处理传入的连接请求

    // 关闭套接字
    close(sockfd);
    return 0;
}

accept()

接受一个传入的连接请求,创建一个新的连接套接字(用于服务器端)。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd: 监听套接字的描述符。
addr: 用于存储客户端地址信息的 sockaddr 结构体。
addrlen: 存储 sockaddr 结构体的大小。
返回值: 成功返回一个新的套接字文件描述符,用于与客户端进行通信。失败返回 -1,并设置 errno 以指示具体的错误类型。EBADF:sockfd 不是有效的文件描述符。EINVAL:sockfd 不是一个监听套接字,或者 addrlen 不正确。EINTR:系统调用被信号中断。ENOBUFS:系统没有足够的缓冲区来完成请求。ENOMEM:内存不足。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int listenfd, connfd;
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddrlen;
    char buffer[BUFFER_SIZE];

    // 创建一个 IPv4 的 TCP 套接字
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 填充服务器地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有本地地址
    servaddr.sin_port = htons(PORT);        // 绑定到端口 8080

    // 绑定套接字到指定地址和端口
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("bind");
        close(listenfd);
        exit(EXIT_FAILURE);
    }

    // 将套接字设置为监听模式,等待连接请求
    if (listen(listenfd, 10) == -1) {
        perror("listen");
        close(listenfd);
        exit(EXIT_FAILURE);
    }

    printf("Server is listening on port %d...\n", PORT);

    // 初始化客户端地址结构体长度
    cliaddrlen = sizeof(cliaddr);

    // 接受客户端连接请求
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddrlen);
    if (connfd == -1) {
        perror("accept");
        close(listenfd);
        exit(EXIT_FAILURE);
    }

    printf("Accepted connection from %s:%d\n",
           inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

    // 接收客户端消息
    ssize_t bytes_received = recv(connfd, buffer, sizeof(buffer) - 1, 0);
    if (bytes_received > 0) {
        buffer[bytes_received] = '\0'; // 确保字符串以 null 结尾
        printf("Received message: %s\n", buffer);
    } else if (bytes_received == 0) {
        printf("Client disconnected\n");
    } else {
        perror("recv");
    }

    // 关闭连接套接字
    close(connfd);
    // 关闭监听套接字
    close(listenfd);
    return 0;
}

connect()

发起到特定地址和端口的连接请求(用于客户端)。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd: 套接字描述符。
addr: 目标服务器的地址信息。
addrlen: sockaddr 结构体的大小。
返回值: 成功返回 0,表示连接成功。失败返回 -1,并设置 errno 以指示具体的错误类型。EBADF:sockfd 不是有效的文件描述符。EINVAL:套接字未正确配置(例如,套接字类型与目标地址不匹配)。EADDRINUSE:地址已在使用中。EADDRNOTAVAIL:指定的地址在本地机器上不可用。ECONNREFUSED:目标服务器拒绝连接。ETIMEDOUT:连接超时。ENETUNREACH:网络不可达。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080

int main() {
    int sockfd;
    struct sockaddr_in servaddr;

    // 创建一个 IPv4 的 TCP 套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 填充服务器地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERVER_PORT);  // 目标端口
    if (inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr) <= 0) {
        perror("inet_pton");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 发起连接到服务器
    if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("connect");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("Connected to server %s:%d\n", SERVER_IP, SERVER_PORT);

    // 发送和接收数据代码通常在这里

    // 关闭套接字
    close(sockfd);
    return 0;
}

(2)数据传输

send()

向已连接的套接字发送数据。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd: 套接字描述符。
buf: 指向要发送的数据缓冲区。
len: 发送的数据长度。
flags: 传输标志。0:默认行为,数据将按顺序发送。MSG_OOB:发送带外数据(用于 TCP 套接字的紧急数据)。MSG_DONTROUTE:在发送数据时绕过路由表(仅适用于某些协议族)。MSG_NOSIGNAL:在 TCP 套接字上禁用 SIGPIPE 信号(避免因对端关闭连接导致的信号)。
返回值: 成功返回实际发送的字节数。失败返回 -1,并设置 errno 以指示具体的错误类型。EBADF:sockfd 不是有效的文件描述符。EFAULT:buf 指向的内存不可访问。ENOTCONN:尝试在未连接的套接字上发送数据。EPIPE:写入已关闭的管道或套接字(通常会触发 SIGPIPE 信号,如果 MSG_NOSIGNAL 未设置)。ENOBUFS:系统没有足够的缓冲区来完成请求。EAGAIN 或 EWOULDBLOCK:套接字处于非阻塞模式,且缓冲区已满。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define MESSAGE "Hello, Server!"

int main() {
    int sockfd;
    struct sockaddr_in servaddr;
    ssize_t bytes_sent;

    // 创建一个 IPv4 的 TCP 套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 填充服务器地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr) <= 0) {
        perror("inet_pton");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 连接到服务器
    if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("connect");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 发送数据到服务器
    bytes_sent = send(sockfd, MESSAGE, strlen(MESSAGE), 0);
    if (bytes_sent == -1) {
        perror("send");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("Sent %zd bytes to server\n", bytes_sent);

    // 关闭套接字
    close(sockfd);
    return 0;
}

recv()

从已连接的套接字接收数据。

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd: 套接字描述符。
buf: 接收数据的缓冲区。
len: 接收数据的最大长度。
flags: 传输标志。0:默认行为,数据将被接收并存储在 buf 中。MSG_OOB:接收带外数据(紧急数据),通常用于 TCP 套接字。MSG_PEEK:窥视数据,即不从接收缓冲区中移除数据,只查看数据内容。MSG_WAITALL:等待接收完整的数据(对于流式套接字)。需要注意的是,这个标志对 UDP 套接字没有意义。
返回值: 成功返回实际接收到的字节数。如果返回 0,表示对端已经关闭连接;如果返回负值,则表示发生了错误。失败返回 -1,并设置 errno 以指示具体的错误类型。EBADF:sockfd 不是有效的文件描述符。EFAULT:buf 指向的内存不可访问。EINVAL:len 超出了允许的范围,或 flags 无效。
ENOTCONN:尝试在未连接的套接字上接收数据。EINTR:系统调用被信号中断。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sockfd;
    struct sockaddr_in servaddr;
    char buffer[BUFFER_SIZE];
    ssize_t bytes_received;

    // 创建一个 IPv4 的 TCP 套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 填充服务器地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr) <= 0) {
        perror("inet_pton");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 连接到服务器
    if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("connect");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 接收数据
    bytes_received = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (bytes_received == -1) {
        perror("recv");
        close(sockfd);
        exit(EXIT_FAILURE);
    } else if (bytes_received == 0) {
        printf("Connection closed by the server\n");
    } else {
        buffer[bytes_received] = '\0'; // 确保字符串以 null 结尾
        printf("Received message: %s\n", buffer);
    }

    // 关闭套接字
    close(sockfd);
    return 0;
}

sendto()

向一个指定地址发送数据(用于无连接套接字,如 UDP)。

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

sockfd: 套接字描述符。
buf: 指向要发送的数据缓冲区。
len: 发送的数据长度。
flags: 传输标志。0:默认行为,数据将按顺序发送。MSG_OOB:发送带外数据(用于 TCP 套接字的紧急数据)。MSG_DONTROUTE:在发送数据时绕过路由表(仅适用于某些协议族)。MSG_NOSIGNAL:在 TCP 套接字上禁用 SIGPIPE 信号(避免因对端关闭连接导致的信号)。
dest_addr: 目标地址信息。
addrlen: sockaddr 结构体的大小。
返回值: 成功返回实际发送的字节数。失败返回 -1,并设置 errno 以指示具体的错误类型。EBADF:sockfd 不是有效的文件描述符。EFAULT:buf 指向的内存不可访问。EINVAL:len 超出了允许的范围,或 flags 无效。ENOTCONN:尝试在未连接的套接字上发送数据(虽然对于 UDP 套接字,这通常不会是问题)。EAGAIN 或 EWOULDBLOCK:套接字处于非阻塞模式,且缓冲区已满。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define MESSAGE "Hello, Server!"

int main() {
    int sockfd;
    struct sockaddr_in servaddr;
    ssize_t bytes_sent;

    // 创建一个 IPv4 的 UDP 套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 填充目标地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr) <= 0) {
        perror("inet_pton");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 发送数据到目标地址
    bytes_sent = sendto(sockfd, MESSAGE, strlen(MESSAGE), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
    if (bytes_sent == -1) {
        perror("sendto");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("Sent %zd bytes to %s:%d\n", bytes_sent, SERVER_IP, SERVER_PORT);

    // 关闭套接字
    close(sockfd);
    return 0;
}

recvfrom()

从一个指定地址接收数据(用于无连接套接字,如 UDP)。

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

sockfd: 套接字描述符。
buf: 接收数据的缓冲区。
len: 接收数据的最大长度。
flags: 传输标志。0:默认行为,数据将被接收并存储在 buf 中。MSG_OOB:接收带外数据(紧急数据),通常用于 TCP 套接字。MSG_PEEK:窥视数据,即不从接收缓冲区中移除数据,只查看数据内容。MSG_WAITALL:等待接收完整的数据(对于流式套接字)。需要注意的是,这个标志对 UDP 套接字没有意义。
src_addr: 用于存储源地址的 sockaddr 结构体。
addrlen: 存储 sockaddr 结构体的大小。
返回值: 成功返回实际接收到的字节数。如果返回 0,表示对端已经关闭连接。失败返回 -1,并设置 errno 以指示具体的错误类型。EBADF:sockfd 不是有效的文件描述符。EFAULT:buf 指向的内存不可访问。EINVAL:len 超出了允许的范围,或 flags 无效。ENOTCONN:尝试在未连接的套接字上接收数据(虽然对于 UDP 套接字,这通常不会是问题)。EINTR:系统调用被信号中断。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BUFFER_SIZE 1024
#define PORT 8080

int main() {
    int sockfd;
    struct sockaddr_in servaddr, cliaddr;
    socklen_t addrlen = sizeof(cliaddr);
    char buffer[BUFFER_SIZE];
    ssize_t bytes_received;

    // 创建一个 IPv4 的 UDP 套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 填充服务器地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(PORT);

    // 绑定套接字到地址
    if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("bind");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 接收数据
    bytes_received = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&cliaddr, &addrlen);
    if (bytes_received == -1) {
        perror("recvfrom");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    buffer[bytes_received] = '\0'; // 确保字符串以 null 结尾
    printf("Received message: %s\n", buffer);

    // 打印源地址
    char client_ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &cliaddr.sin_addr, client_ip, sizeof(client_ip));
    printf("Received from %s:%d\n", client_ip, ntohs(cliaddr.sin_port));

    // 关闭套接字
    close(sockfd);
    return 0;
}

(3)套接字选项

套接字选项允许程序员控制套接字的行为,如设置超时、指定数据包处理方式等。

setsockopt()

设置套接字选项。

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

sockfd: 套接字描述符。
level: 选项所在协议层,SOL_SOCKET:通用套接字选项,适用于所有类型的套接字。IPPROTO_IP:用于 IPv4 套接字的选项。IPPROTO_IPV6:用于 IPv6 套接字的选项。IPPROTO_TCP:用于 TCP 套接字的选项。IPPROTO_UDP:用于 UDP 套接字的选项。。
optname: 选项名称,选项名称根据 level 的不同而有所变化。例如,SOL_SOCKET 下的选项包括 SO_REUSEADDR 和 SO_RCVBUF,而 IPPROTO_TCP 下的选项包括 TCP_NODELAY。
optval: 选项的值。具体取值取决于 optname。例如,对于 SO_RCVBUF 选项,optval 指向一个 int,表示接收缓冲区的大小。
optlen: optval 的大小。
返回值: 成功返回 0。失败返回 -1,并设置 errno 以指示具体的错误类型。EBADF:sockfd 不是有效的文件描述符。ENOPROTOOPT:optname 无效或不支持。EFAULT:optval 指向的内存不可访问。EINVAL:optlen 超出了允许的范围,或 optval 的值无效。ENOBUFS:内存不足,无法分配用于选项设置的缓冲区。

级别选项名称描述类型
SOL_SOCKETSO_REUSEADDR允许重用本地地址,即在套接字关闭后,快速重新绑定到同一地址和端口int(通常设置为 1 表示启用)
SO_RCVBUF设置接收缓冲区的大小int
SO_SNDBUF设置发送缓冲区的大小int
SO_KEEPALIVE启用 TCP 保活功能,以检测连接的存活性int(通常设置为 1 表示启用)
SO_LINGER设置套接字关闭时的延迟行为struct linger,包含 l_onoff 和 l_linger 字段
IPPROTO_TCPTCP_NODELAY禁用 Nagle 算法,减少 TCP 延迟。适用于需要实时传输的应用int(通常设置为 1 表示启用)
IPPROTO_IPIP_TTL设置 IP 数据包的生存时间(TTL),用于限制数据包的传输范围int
IPPROTO_IPV6IPV6_V6ONLY限制 IPv6 套接字只处理 IPv6 流量int(通常设置为 1 表示启用)
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define PORT 8080

int main() {
    int sockfd;
    int optval;
    socklen_t optlen = sizeof(optval);
    struct sockaddr_in servaddr;

    // 创建一个 IPv4 的 TCP 套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置 SO_REUSEADDR 选项
    optval = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
        perror("setsockopt");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 填充服务器地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(PORT);

    // 绑定套接字到地址
    if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("bind");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 关闭套接字
    close(sockfd);
    return 0;
}

getsockopt()

获取套接字选项的当前值。

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);

sockfd: 套接字描述符。
level: 选项所在协议层。SOL_SOCKET:通用套接字选项,适用于所有类型的套接字。IPPROTO_IP:用于 IPv4 套接字的选项。IPPROTO_IPV6:用于 IPv6 套接字的选项。IPPROTO_TCP:用于 TCP 套接字的选项。IPPROTO_UDP:用于 UDP 套接字的选项。
optname: 选项名称。选项名称根据 level 的不同而有所变化。例如,SOL_SOCKET 下的选项包括 SO_RCVBUF 和 SO_SNDBUF,而 IPPROTO_TCP 下的选项包括 TCP_NODELAY。
optval: 用于存储选项值的缓冲区。
optlen: 用于存储 optval 的大小。
返回值: 成功返回 0。失败返回 -1,并设置 errno 以指示具体的错误类型。EBADF:sockfd 不是有效的文件描述符。EINVAL:optname 无效,或 optlen 指向的内存不可访问。EFAULT:optval 指向的内存不可访问。
getsockopt()函数的级别和选项跟setsockopt()的差不多,同上表。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define PORT 8080

int main() {
    int sockfd;
    int optval;
    socklen_t optlen = sizeof(optval);
    struct sockaddr_in servaddr;

    // 创建一个 IPv4 的 TCP 套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置 SO_REUSEADDR 选项
    optval = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
        perror("setsockopt");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 获取 SO_REUSEADDR 选项
    optlen = sizeof(optval);
    if (getsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, &optlen) == -1) {
        perror("getsockopt");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("SO_REUSEADDR is %s\n", optval ? "enabled" : "disabled");

    // 填充服务器地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(PORT);

    // 绑定套接字到地址
    if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("bind");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 关闭套接字
    close(sockfd);
    return 0;
}

(4)套接字关闭

当套接字完成通信时,需要将其关闭以释放系统资源。

close()

关闭套接字。

int close(int sockfd);

sockfd: 套接字描述符。
返回值: 成功返回 0。失败返回 -1,并设置 errno 以指示具体的错误类型。EBADF:fd 不是有效的文件描述符,可能已经被关闭或无效。EINTR:系统调用被信号中断。ENOSYS:在某些系统中,可能不支持 close() 操作。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    int fd;

    // 打开一个文件
    fd = open("example.txt", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 写入数据到文件
    if (write(fd, "Hello, world!\n", 14) != 14) {
        perror("write");
        close(fd); // 发生错误时也需要关闭文件描述符
        return 1;
    }

    // 关闭文件描述符
    if (close(fd) == -1) {
        perror("close");
        return 1;
    }

    printf("File descriptor closed successfully\n");
    return 0;
}

shutdown()(不推荐使用)

部分或完全关闭套接字连接。

int shutdown(int sockfd, int how);

sockfd: 套接字描述符。
how: 指定关闭的方式:SHUT_RD: 关闭读取端。SHUT_WR: 关闭写入端。SHUT_RDWR: 关闭读取和写入端。
返回值: 成功返回 0。失败返回 -1,并设置 errno 以指示具体的错误类型。EBADF:sockfd 不是有效的文件描述符。ENOTSOCK:sockfd 不是一个套接字。EINVAL:how 参数的值无效。
数据传输结束: 在关闭写方向时,套接字的另一端将会收到一个 “FIN” 包,表示数据传输结束。在关闭读方向时,套接字的另一端将会收到一个 “RST” 包,表示不能再读取数据。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define PORT 8080

int main() {
    int sockfd, newsockfd;
    struct sockaddr_in servaddr, cliaddr;
    socklen_t clilen;

    // 创建一个 IPv4 的 TCP 套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 填充服务器地址结构体
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(PORT);

    // 绑定套接字到地址
    if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        perror("bind");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(sockfd, 5) == -1) {
        perror("listen");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 接受连接
    clilen = sizeof(cliaddr);
    newsockfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
    if (newsockfd == -1) {
        perror("accept");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 关闭写方向
    if (shutdown(newsockfd, SHUT_WR) == -1) {
        perror("shutdown");
        close(newsockfd);
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 在此处可以继续进行读操作或其他处理

    // 关闭套接字
    close(newsockfd);
    close(sockfd);
    return 0;
}

以上 API 使用到的网络结构体可以参考我另一篇文章结构体

4、总结

POSIX 网络 API 提供了功能全面的网络编程接口,支持多种通信方式和协议。在现代网络编程中,这些 API 是基础和关键,适用于从简单的客户端/服务器模型到复杂的并发网络应用。可以在 IEEE 和其他标准化组织的网站上找到详细的 POSIX 规范。

;