结构struct sockaddr定义了通用的套接字地址,它在sys/socket.h中的定义代码如下:
#include
struct sockaddr {
sa_family_t sa_family; // 地址族,例如 AF_INET(IPv4)、AF_INET6(IPv6)
char sa_data[14]; // 地址数据(具体内容依地址族而定)
};
其中,sa_family表示套接字的协议族类型;sa_data存储具体的协议地址。sa_data被定义成14个字节,因为有的协议族使用较长的地址格式。
不过一般在编程中也并不对该结构体进行操作,而是使用与它等价的数据结构:sockaddr_in(用于 IPv4 地址)
#include
struct sockaddr_in {
sa_family_t sin_family; // 地址族,必须是 AF_INET
in_port_t sin_port; // 端口号(网络字节序)
struct in_addr sin_addr; // IPv4 地址
char sin_zero[8];// 填充字段,无实际意义
};
sockaddr_in6
(用于 IPv6 地址)
#include
struct sockaddr_in6 {
sa_family_t sin6_family; // 地址族,AF_INET6
in_port_t sin6_port; // 端口号(网络字节序)
uint32_t sin6_flowinfo; // 流信息
struct in6_addr sin6_addr; // IPv6 地址
uint32_t sin6_scope_id; // 作用域 ID
};
下面暂且只介绍sockaddr_in。
其中的in_addr结构体定义如下
struct in_addr {
in_addr_t s_addr; // 实际保存 IPv4 地址,类型是 uint32_t,使用网络字节序
};
那为什么还要sockaddr呢?因为许多 socket API(如 connect()
, bind()
, accept()
, recvfrom()
, sendto()
等)接受的是 struct sockaddr *
指针参数,这样可以统一处理不同类型的地址结构体。而sockaddr和sockaddr_in的长度同为16字节。所以通常使用sockaddr_in来设置地址,然后强行转换为sockaddr类型。
以下代码是一个转化的过程:
#include
#include
#include
#include // inet_pton(), inet_ntop()
#include
int main(){
struct sockaddr_in addr;
addr.sin_family=AF_INET;//type:IPv4
addr.sin_port=htons(8080);//port:8080
//设置ip地址
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); //书中写的inet_addr方法已经被废弃了
memset(addr.sin_zero,0,sizeof(addr.sin_zero));
//强制转化,用C风格的强转也可以
struct sockaddr *sock=reinterpret_cast(&addr);
}
以下是为什么要这么做?
虽然
struct sockaddr
和struct sockaddr_in
是两个不同的结构体类型,但它们被设计成内存布局兼容,因此可以安全地通过指针强制转换。这是一种 "有约定的、不安全但合法的" 技巧,用于传递参数给统一接口。虽然没有继承关系(C 语言没有继承),但
sockaddr_in
的前两个字段与sockaddr
的布局保持一致。这是 POSIX 的明确规定,用于保证强制转换后,sendto()
、bind()
等系统调用能正确读取必要字段(如sa_family
,port
,addr
)。为什么系统调用使用
sockaddr*
?因为系统调用如
bind()
、connect()
、accept()
要支持多种地址族(IPv4、IPv6、Unix Domain 等),所以它们都统一声明为接受:struct sockaddr *addr只要你将它们强制转换成
(struct sockaddr*)
传进去,系统就知道怎么根据sa_family
去解释后续字段。可以说,这是C语言实现的继承功能。
socket函数:
#include
#include
int socket(int domain, int type, int protocol);
参数 | 说明 |
---|---|
domain |
指定协议族(如 IPv4、IPv6) |
type |
指定套接字类型(如 TCP、UDP) |
protocol |
通常填 0,由系统根据 domain 和 type 自动选择 |
domain
(协议族)值 | 含义 |
---|---|
AF_INET |
IPv4 地址族 |
AF_INET6 |
IPv6 地址族 |
AF_UNIX / AF_LOCAL |
本地通信(Unix域套接字) |
type
(套接字类型)值 | 含义 |
---|---|
SOCK_STREAM |
流式套接字(TCP) |
SOCK_DGRAM |
数据报套接字(UDP) |
SOCK_RAW |
原始套接字(自定义协议) |
protocol
(协议)通常填 0
,由系统根据 domain
和 type
自动匹配:
AF_INET
+ SOCK_STREAM
⇒ TCP
AF_INET
+ SOCK_DGRAM
⇒ UDP
但也可以显式写:
IPPROTO_TCP
IPPROTO_UDP
下面是创建套接字的示例:
#include
#include
#include
#include // inet_pton(), inet_ntop(), etc.
#include
int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建tcp套接字
if(sockfd==-1){
perror("socket creation failed");
return 1;
}
close(sockfd);
connect函数
#include
#include
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数 | 含义 |
---|---|
sockfd |
套接字文件描述符(由 socket() 返回) |
addr |
指向服务器地址结构体的指针(如 sockaddr_in* 强转而来) |
addrlen |
地址结构体的大小(一般是 sizeof(struct sockaddr_in) ) |
对于 TCP socket:
发起连接请求(开始三次握手)
成功时,socket 进入“已连接”状态,可用于 send()/recv()
只能调用一次
对于 UDP socket:
不建立连接,但设置“默认目标地址”,之后可用 send()
而不用指定地址
可以多次调用,改变默认目标地址
如果连接失败,connect()
返回 -1
,并设置 errno
。常见错误包括:
errno 错误码 | 原因说明 |
---|---|
ECONNREFUSED |
对方主机无监听服务或被拒绝连接 |
ETIMEDOUT |
超时未连接成功 |
ENETUNREACH |
网络不可达,目标 IP 无法连接 |
EADDRNOTAVAIL |
IP 地址无效或本地不可用 |
该程序是connect的用法;尝试连接本机端口为8080的服务器,采用tcp连接
#include
#include
#include
#include
#include // inet_pton(), inet_ntop(), etc.
#include
#include
int main(){
int sockfd=socket(AF_INET,SOCK_STREAM,0);//ipv4+tcp
if(sockfd<0){
perror("socket error");
return 1;
}
sockaddr_in serv_addr;//ipv4 address
serv_addr.sin_family=AF_INET;//ipv4
serv_addr.sin_port=htons(8080);//port:8080
inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr);//设置ipv4地址
memset(serv_addr.sin_zero,0,sizeof(serv_addr.sin_zero));
if(connect(sockfd,reinterpret_cast(&serv_addr),sizeof(serv_addr))<0){
perror("connect");
close(sockfd);
return 1;
}
std::cout<<"Connected to server"<
在网络编程中,bind()
用于将 socket 绑定到一个本地地址(IP 和端口)。这一步通常是服务器端所需的,它告诉操作系统:这个 socket 用于监听哪个地址和端口。
#include
#include
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
可以将my_addr的sin_addr 设置为INADDR_ANY而不是某个确定的IP地址就可以绑定到任意网络接口。对于多宿主主机(拥有多块网卡),INADDR_ANY表示本服务器程序将处理来自所有网络接口上相应端口的连接请求。
函数执行成功返回0,如果失败则返回 -1
,并设置 errno。
#include
#include
#include
#include
#include
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
return 1;
}
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY; // 监听所有本地 IP
memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
if (bind(sockfd, reinterpret_cast(&addr), sizeof(addr)) < 0) {
perror("bind");
close(sockfd);
return 1;
}
std::cout << "Listening on all interfaces, port 8080..." << std::endl;
close(sockfd);
return 0;
}
listen()
函数用于将一个 处于绑定状态(bind) 的 socket 设置为 监听状态,准备接受来自客户端的连接请求。
这是 TCP 服务器三部曲的第二步:
socket()
→bind()
→listen()
→accept()
#include
#include
int listen(int sockfd, int backlog);
参数 | 含义 |
---|---|
sockfd |
已创建并绑定了地址的 socket(SOCK_STREAM 类型) |
backlog |
连接请求的排队长度上限(等待队列长度);达到上限后,之后的请求会被拒绝 |
成功:返回 0
失败:返回 -1
,并设置 errno
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
return 1;
}
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
if (bind(server_fd, (sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
return 1;
}
if (listen(server_fd, 10) < 0) {
perror("listen");
return 1;
}
注意事项
情况 | 说明 |
---|---|
只用于 SOCK_STREAM (TCP) |
UDP 是无连接的,不能使用 listen() |
在调用 accept() 前必须 listen() |
否则会报错 |
一般在服务器中使用 | 客户端不需要 listen() |
accept()
用于在服务器端接收一个客户端的连接请求。它会从由 listen()
建立的等待队列中,取出一个客户端连接,创建一个新的 socket 文件描述符用于通信。
原始 socket 继续监听,返回的新 socket 用于与客户端通信。
#include
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数 | 说明 |
---|---|
sockfd |
监听 socket(通过 socket() + bind() + listen() 创建) |
addr |
输出参数,保存客户端的地址信息(可选,可以设为 nullptr ) |
addrlen |
输入/输出参数,指明 addr 的大小,调用后返回实际大小 |
成功:返回新的 socket 文件描述符(用于后续和客户端通信)
失败:返回 -1
,并设置 errno
#include
#include
#include
#include
#include
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
bind(server_fd, (sockaddr*)&addr, sizeof(addr));
listen(server_fd, 5);
std::cout << "Waiting for connection..." << std::endl;
sockaddr_in client_addr{};
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (sockaddr*)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept");
return 1;
}
std::cout << "Client connected!" << std::endl;
// 此时可以通过 client_fd 与客户端进行 read/write 读写操作
close(client_fd);
close(server_fd);
return 0;
}
是 阻塞调用:如果没有连接,它会一直等待。
一旦有连接,就返回一个新的 socket fd,原 server fd 继续监听
可以在 while (true)
循环中不断接收多个客户端连接。
套接字文件描述符对比
socket fd | 用途 |
---|---|
server_fd |
原始监听 socket,用于接收连接(accept() ) |
client_fd |
新生成的 socket,用于与特定客户端通信 |
socket()
创建 socket
bind()
绑定 IP+端口
listen()
设置监听状态
accept()
接受客户端连接并获得新 socket
send()
和 recv()
是最基本的 数据发送与接收接口,它们直接作用于 accept()
得到的 socket 文件描述符。下面是它们的详细介绍:
send()
函数 —— 发送数据#include
#include
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数 | 含义 |
---|---|
sockfd |
与客户端连接的 socket(如 accept() 返回值) |
buf |
指向要发送数据的缓冲区 |
len |
要发送的字节数 |
flags |
发送行为控制,一般为 0(详见下方) |
成功:返回实际发送的字节数
失败:返回 -1,并设置 errno
如果要发送的数据太长而不能发送时,将出现错误,errno设置为EMSGSIZE;如果要发送的
数据长度大于该套接字的缓冲区剩余空间大小时,send一般会被阻塞,如果该套接字被设置为非
阻塞方式,则此时立即返回-1并将errno设为EAGAIN。
常用 flags 值
flags 值 | 说明 |
---|---|
0 |
默认 |
MSG_NOSIGNAL |
防止 send() 在对方关闭时发出 SIGPIPE 信号 |
MSG_DONTWAIT |
非阻塞发送/接收(需要 socket 设置为非阻塞) |
注意:执行成功只是说明数据写入套接字的缓冲区内,不表示数据已经通过网络成功发送。
const char send_buf[]{"hello"};
int len=strlen(send_buf)+1;
if(send(client_fd,send_buf,len,0)<0){
perror("send");
return 1;
}
recv()
函数 —— 接收数据#include
#include
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数 | 含义 |
---|---|
sockfd |
与客户端连接的 socket(如 accept() 返回值) |
buf |
接收数据的缓冲区 |
len |
最多接收的字节数 |
flags |
接收行为控制,一般为 0 |
!=0:实际接收到的字节数
=0:表示 对端已关闭连接
-1:出错,设置 errno
flags 值 |
说明 |
---|---|
0 |
默认阻塞接收,直到收到数据或对方关闭连接 |
MSG_PEEK |
窥探数据,查看缓冲区数据但不取走,下次 recv() 还会读到这部分 |
MSG_WAITALL |
等够指定长度才返回,否则继续阻塞(仅适合已知固定长度的消息) |
MSG_DONTWAIT |
非阻塞接收,若没有数据立即返回 -1,errno=EAGAIN 或 EWOULDBLOCK |
MSG_NOSIGNAL |
防止因对端关闭 socket 时 recv() 引发 SIGPIPE 信号(更常用于 send() ) |
逻辑是连接后客户端发送数据,服务器接收
服务器代码
#include
#include
#include
#include
#include
#include
int main(){
int server_fd=socket(AF_INET,SOCK_STREAM,0);
if(server_fd<0){
perror("socket");
close(server_fd);
exit(1);
}
sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(8080);//监听8080端口
addr.sin_addr.s_addr=INADDR_ANY;
memset(addr.sin_zero,0,sizeof(addr.sin_zero));
if(bind(server_fd,reinterpret_cast(&addr),sizeof(addr))<0){
perror("bind");
close(server_fd);
exit(1);
}
if(listen(server_fd,10)<0){
perror("listen");
close(server_fd);
exit(1);
}
//准备接收数据
sockaddr_in client_addr;
socklen_t client_len=sizeof(client_addr);
int client_fd=accept(server_fd,reinterpret_cast(&client_addr),&client_len);
if(client_fd<0){
perror("accept");
close(server_fd);
exit(1);
}
char recv_buf[100];
int len=recv(client_fd,recv_buf,100,0);//默认会发送\0
if(len<=0){
std::cout<<"receive error"<
客户端代码:
#include
#include
#include
#include
#include
#include
#include
//封装了出错的逻辑
void exit_with_error(const char *msg,std::initializer_list fds = {}){
perror(msg);
for (int fd : fds) {
if (fd >= 0) close(fd);
}
exit(EXIT_FAILURE);
}
int main(){
int sockfd=socket(AF_INET,SOCK_STREAM,0);//ipv4+tcp
if(sockfd<0){
exit_with_error("socket",{sockfd});
}
sockaddr_in serv_addr;//ipv4 address
serv_addr.sin_family=AF_INET;//ipv4
serv_addr.sin_port=htons(8080);//port:8080
inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr);//设置ipv4地址
memset(serv_addr.sin_zero,0,sizeof(serv_addr.sin_zero));
if(connect(sockfd,reinterpret_cast(&serv_addr),sizeof(serv_addr))<0){
exit_with_error("connect",{sockfd});
}
const char send_buf[]{"hello"};
int send_len=strlen(send_buf)+1;
if(send(sockfd,send_buf,send_len,0)<0){
exit_with_error("send",{sockfd});
}
close(sockfd);
}
sendto()
函数 —— 向某个地址发送 UDP 数据报ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数 | 含义 |
---|---|
sockfd |
用 socket(AF_INET, SOCK_DGRAM, 0) 创建的 UDP socket |
buf |
要发送的数据 |
len |
数据长度(单位:字节) |
flags |
与send一致 |
dest_addr |
对方地址(sockaddr_in 强转为 sockaddr* ) |
addrlen |
对方地址结构体的大小(用 sizeof(sockaddr_in) ) |
使用示例
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in dest{};
dest.sin_family = AF_INET;
dest.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &dest.sin_addr);
const char* msg = "Hello UDP";
sendto(sockfd, msg, strlen(msg), 0,
reinterpret_cast(&dest), sizeof(dest));
recvfrom()
函数 —— 从某个来源接收 UDP 数据报ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数 | 含义 |
---|---|
sockfd |
已绑定的 UDP socket(一般使用 bind() ) |
buf |
用于存储接收的数据 |
len |
缓冲区长度 |
flags |
与recv一致 |
src_addr |
可选,保存发送方地址(可为 NULL) |
addrlen |
src_addr 对应的结构体大小变量(可为 NULL) |
使用示例
char buffer[1024];
sockaddr_in sender{};
socklen_t sender_len = sizeof(sender);
int n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0,
reinterpret_cast(&sender), &sender_len);
if (n > 0) {
buffer[n] = '\0'; // 添加字符串结束符
std::cout << "Received: " << buffer << std::endl;
}
客户端发送,服务器接收
服务器代码:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd, reinterpret_cast(&addr), sizeof(addr));
while (true) {
char buf[100];
sockaddr_in client;
socklen_t client_len = sizeof(client);
int len = recvfrom(sockfd, buf, sizeof(buf)-1, 0,
reinterpret_cast(&client), &client_len);
if (len > 0) {
buf[len] = '\0';
std::cout << "Client says: " << buf << std::endl;
}
}
客户端:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in server{};
server.sin_family = AF_INET;
server.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
std::string msg = "hello UDP server!";
sendto(sockfd, msg.c_str(), msg.size(), 0,
reinterpret_cast(&server), sizeof(server));
UDP 客户端的 socket 通常不需要 connect()
。这是因为:
UDP 是**无连接(connectionless)**协议
每次通信都是独立的数据报;
不像 TCP 那样需要三次握手,也没有连接状态;
所以你用 sendto()
/recvfrom()
就能完成通信,无需 connect()
。
connect()
(不常见,但有用)虽然 UDP 天生是无连接的,但你仍然可以在 UDP socket 上调用 connect()
,它会:
功能 | 效果 |
---|---|
设定目标地址 | 后续的 send() /recv() 不再需要传目标地址 |
内核过滤非目标地址的包 | 只接收来自你 connect() 指定地址的数据(recv() 时自动屏蔽其他地址) |
更简洁的编程 | 可以用 send() / recv() 替代 sendto() / recvfrom() |
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in server{};
server.sin_family = AF_INET;
server.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
// 虽然是 UDP,也可以 connect()
connect(sockfd, reinterpret_cast(&server), sizeof(server));
// 然后就能用 send / recv,而不用每次都写目标地址
send(sockfd, "Hello", 5, 0);
recv(sockfd, buffer, sizeof(buffer), 0);
int close(int sockfd);
含义:
关闭整个 socket,释放其占用的所有资源(文件描述符、缓冲区等);
TCP 情况下,它会向对端发送 FIN
包,优雅断开连接;
若是 UDP,直接释放资源,没有通知对端;
是 强制关闭,无论当前读写状态如何。
返回值:
成功返回 0;
失败返回 -1(例如关闭两次),并设置 errno
。
适用于:
所有 socket,TCP 和 UDP 都可;
简单直接,用完就 close()
,非常常用。
int shutdown(int sockfd, int how);
how 值 |
含义 |
---|---|
SHUT_RD |
关闭读方向(recv 将立即返回 0) |
SHUT_WR |
关闭写方向(send 将报错) |
SHUT_RDWR |
同时关闭读和写,相当于完全断开 |
常用于 TCP,适合逐步“关闭连接”;
允许只断一半,如只发送 FIN
,不马上收 FIN
;
有助于实现半关闭连接的协议逻辑,例如:
我发完数据,但还要接收对方数据(只关写);
我不想再接收任何数据(只关读);
最终仍然要用 close()
来释放 socket;
为什么需要这些函数?
不同计算机架构的**字节序(Endian)**可能不同:
小端序(如 x86):低位字节在前;
大端序(网络字节序):高位字节在前。
而 网络协议(如 TCP/IP)统一使用大端序(Big Endian)。因此,在主机和网络通信之间要进行字节序转换。
四个函数介绍(来自
)
函数名 | 全称 | 功能说明 |
---|---|---|
htons() |
Host to Network Short | 主机字节序 → 网络字节序(16位) |
htonl() |
Host to Network Long | 主机字节序 → 网络字节序(32位) |
ntohs() |
Network to Host Short | 网络字节序 → 主机字节序(16位) |
ntohl() |
Network to Host Long | 网络字节序 → 主机字节序(32位) |
函数名 | 用于哪个方向 | 处理的数据类型 | 常用场景 |
---|---|---|---|
htons |
主机 → 网络 | uint16_t |
设置端口号 |
htonl |
主机 → 网络 | uint32_t |
设置 IPv4 地址(整型) |
ntohs |
网络 → 主机 | uint16_t |
接收端口号 |
ntohl |
网络 → 主机 | uint32_t |
接收 IPv4 地址(整型) |
注意:如果你是用字符串 inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr)
,就不需要自己调用 htonl()
,因为 inet_pton
已经内部处理过了。
转换示例:
#include
#include
int main() {
uint16_t port = 8080;
uint16_t net_port = htons(port);
std::cout << "网络字节序端口: " << net_port << std::endl;
uint16_t host_port = ntohs(net_port);
std::cout << "主机字节序端口: " << host_port << std::endl;
return 0;
}
使用示例:
sockaddr_in addr;
addr.sin_port = htons(8080); // 正确:16位端口号 -> 网络序
addr.sin_addr.s_addr = htonl(0x7F000001); // 相当于 127.0.0.1
inet
系列函数是用于 IP地址和文本字符串之间转换 以及 基本验证 的一组函数,常用于处理 IPv4 地址。它们来自
头文件,常见的有:
函数名 | 功能简介 | 支持协议 | 常用吗 | 备注 |
---|---|---|---|---|
inet_addr() |
IP字符串 → 整数(网络序) | IPv4 | ❌已废弃 | 不推荐使用 |
inet_ntoa() |
整数(网络序)→ IP字符串(静态返回) | IPv4 | ❌不推荐 | 线程不安全 |
inet_aton() |
IP字符串 → in_addr 结构(更安全) |
IPv4 | ✅推荐 | 用于转换 |
inet_ntop() |
网络序地址(IPv4/IPv6)→ 字符串 | IPv4/IPv6 | ✅推荐 | 新接口 |
inet_pton() |
字符串 → 网络序地址(IPv4/IPv6) | IPv4/IPv6 | ✅推荐 | 新接口 |
int inet_aton(const char *cp, struct in_addr *inp);
cp
:IP地址字符串(如 "127.0.0.1"
)
inp
:输出参数,保存转换结果
int inet_pton(int af, const char *src, void *dst);
af
:地址族(AF_INET
= IPv4,AF_INET6
= IPv6)
src
:IP地址字符串
dst
:输出缓冲区,IPv4用 struct in_addr*
,IPv6用 struct in6_addr*
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af
:地址族(AF_INET
或 AF_INET6
)
src
:输入网络字节序地址(struct in_addr*
或 struct in6_addr*
)
dst
:目标字符串缓冲区
size
:dst
缓冲区大小(IPv4推荐用 INET_ADDRSTRLEN
,IPv6用 INET6_ADDRSTRLEN
)
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数 | 说明 |
---|---|
sockfd |
套接字描述符 |
level |
选项所在的协议层,常用值有: - SOL_SOCKET :套接字层(大多数选项) - IPPROTO_TCP :TCP 层的选项 |
optname |
要设置/获取的选项名(如 SO_REUSEADDR , SO_RCVBUF 等) |
optval |
设置:传入的值地址;获取:传出的值地址 |
optlen |
设置:值的字节大小;获取:传入地址大小,返回实际大小 |
常见选项
选项名 | 所属层 | 说明 |
---|---|---|
SO_REUSEADDR |
SOL_SOCKET |
是否允许重用本地地址 |
SO_RCVBUF |
SOL_SOCKET |
接收缓冲区大小 |
SO_SNDBUF |
SOL_SOCKET |
发送缓冲区大小 |
SO_KEEPALIVE |
SOL_SOCKET |
是否启用 TCP 保活机制 |
SO_LINGER |
SOL_SOCKET |
控制 close() 行为,是否等待数据发送完 |
TCP_NODELAY |
IPPROTO_TCP |
是否关闭 Nagle 算法(即是否立即发送) |