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

2024-09-03 16:28

本文主要是介绍网络通信 Posix API的原理与使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 1、Posix API简介
    • 2、Posix 网络 API简介
    • 3、API 具体介绍
      • (1)套接字(socket)
        • socket()
        • bind()
        • listen()
        • accept()
        • connect()
      • (2)数据传输
        • send()
        • recv()
        • sendto()
        • recvfrom()
      • (3)套接字选项
        • setsockopt()
        • getsockopt()
      • (4)套接字关闭
        • close()
        • shutdown()(不推荐使用)
    • 4、总结

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 1024int 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 8080int 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 1024int 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 8080int 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 8080int 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 8080int 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 8080int 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 规范。

这篇关于网络通信 Posix API的原理与使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1133443

相关文章

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

python使用try函数详解

《python使用try函数详解》Pythontry语句用于异常处理,支持捕获特定/多种异常、else/final子句确保资源释放,结合with语句自动清理,可自定义异常及嵌套结构,灵活应对错误场景... 目录try 函数的基本语法捕获特定异常捕获多个异常使用 else 子句使用 finally 子句捕获所

SpringBoot监控API请求耗时的6中解决解决方案

《SpringBoot监控API请求耗时的6中解决解决方案》本文介绍SpringBoot中记录API请求耗时的6种方案,包括手动埋点、AOP切面、拦截器、Filter、事件监听、Micrometer+... 目录1. 简介2.实战案例2.1 手动记录2.2 自定义AOP记录2.3 拦截器技术2.4 使用Fi

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

Python对接支付宝支付之使用AliPay实现的详细操作指南

《Python对接支付宝支付之使用AliPay实现的详细操作指南》支付宝没有提供PythonSDK,但是强大的github就有提供python-alipay-sdk,封装里很多复杂操作,使用这个我们就... 目录一、引言二、准备工作2.1 支付宝开放平台入驻与应用创建2.2 密钥生成与配置2.3 安装ali