《Linux C编程实战》笔记:套接字编程
套接字地址结构
结构struct sockaddr定义了通用的套接字地址,它在sys/socket.h中的定义代码如下:
#include<sys/socket.h>
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<netinet/in.h>
struct sockaddr_in {sa_family_t sin_family; // 地址族,必须是 AF_INETin_port_t sin_port; // 端口号(网络字节序)struct in_addr sin_addr; // IPv4 地址char sin_zero[8];// 填充字段,无实际意义
};
sockaddr_in6
(用于 IPv6 地址)
#include<netinet/in.h>
struct sockaddr_in6 {sa_family_t sin6_family; // 地址族,AF_INET6in_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<iostream>
#include<sys/socket.h>
#include<netinet/in.h>
#include <arpa/inet.h>// inet_pton(), inet_ntop()
#include<cstring>
int main(){struct sockaddr_in addr;addr.sin_family=AF_INET;//type:IPv4addr.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<struct sockaddr *>(&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<sys/types.h>
#include<sys/socket.h>
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<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <arpa/inet.h> // inet_pton(), inet_ntop(), etc.
#include<unistd.h>int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建tcp套接字if(sockfd==-1){perror("socket creation failed");return 1;}
close(sockfd);
建立连接
connect函数
#include <sys/types.h>
#include <sys/socket.h>
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 地址无效或本地不可用 |
示意程序1
该程序是connect的用法;尝试连接本机端口为8080的服务器,采用tcp连接
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <arpa/inet.h> // inet_pton(), inet_ntop(), etc.
#include<unistd.h>
#include<cstring>
int main(){int sockfd=socket(AF_INET,SOCK_STREAM,0);//ipv4+tcpif(sockfd<0){perror("socket error");return 1;}sockaddr_in serv_addr;//ipv4 addressserv_addr.sin_family=AF_INET;//ipv4serv_addr.sin_port=htons(8080);//port:8080inet_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<sockaddr*>(&serv_addr),sizeof(serv_addr))<0){perror("connect");close(sockfd);return 1;}std::cout<<"Connected to server"<<std::endl;//可以发送或接受了close(sockfd);return 0;
}
绑定套接字
在网络编程中,bind()
用于将 socket 绑定到一个本地地址(IP 和端口)。这一步通常是服务器端所需的,它告诉操作系统:这个 socket 用于监听哪个地址和端口。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
可以将my_addr的sin_addr 设置为INADDR_ANY而不是某个确定的IP地址就可以绑定到任意网络接口。对于多宿主主机(拥有多块网卡),INADDR_ANY表示本服务器程序将处理来自所有网络接口上相应端口的连接请求。
函数执行成功返回0,如果失败则返回 -1
,并设置 errno。
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <unistd.h>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; // 监听所有本地 IPmemset(addr.sin_zero, 0, sizeof(addr.sin_zero));if (bind(sockfd, reinterpret_cast<sockaddr*>(&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 <sys/types.h>
#include <sys/socket.h>
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 <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数 | 说明 |
---|---|
sockfd | 监听 socket(通过 socket() + bind() + listen() 创建) |
addr | 输出参数,保存客户端的地址信息(可选,可以设为 nullptr ) |
addrlen | 输入/输出参数,指明 addr 的大小,调用后返回实际大小 |
返回值
-
成功:返回新的 socket 文件描述符(用于后续和客户端通信)
-
失败:返回
-1
,并设置errno
示例程序2
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <unistd.h>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;
}
accept() 的行为特点
-
是 阻塞调用:如果没有连接,它会一直等待。
-
一旦有连接,就返回一个新的 socket fd,原 server fd 继续监听
-
可以在
while (true)
循环中不断接收多个客户端连接。
套接字文件描述符对比
socket fd | 用途 |
---|---|
server_fd | 原始监听 socket,用于接收连接(accept() ) |
client_fd | 新生成的 socket,用于与特定客户端通信 |
总结:四步法建立服务器连接
-
socket()
创建 socket -
bind()
绑定 IP+端口 -
listen()
设置监听状态 -
accept()
接受客户端连接并获得新 socket
TCP发送接收
send()
和 recv()
是最基本的 数据发送与接收接口,它们直接作用于 accept()
得到的 socket 文件描述符。下面是它们的详细介绍:
send()
函数 —— 发送数据
#include <sys/types.h>
#include <sys/socket.h>
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 <sys/types.h>
#include <sys/socket.h>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() ) |
示例程序3:tcp建立连接以及发送数据程序
逻辑是连接后客户端发送数据,服务器接收
服务器代码
#include<iostream>
#include<cstring>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<unistd.h>
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<sockaddr*>(&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<sockaddr*>(&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);//默认会发送\0if(len<=0){std::cout<<"receive error"<<std::endl;close(client_fd); close(server_fd);exit(1);}std::cout<<"Server receive data: "<<recv_buf<<std::endl;close(client_fd); close(server_fd);
}
客户端代码:
#include<iostream>
#include<cstring>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include <arpa/inet.h>
#include<unistd.h>
//封装了出错的逻辑
void exit_with_error(const char *msg,std::initializer_list<int> 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+tcpif(sockfd<0){exit_with_error("socket",{sockfd});}sockaddr_in serv_addr;//ipv4 addressserv_addr.sin_family=AF_INET;//ipv4serv_addr.sin_port=htons(8080);//port:8080inet_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<sockaddr*>(&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);
}
UDP发送接收
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<sockaddr*>(&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<sockaddr*>(&sender), &sender_len);
if (n > 0) {buffer[n] = '\0'; // 添加字符串结束符std::cout << "Received: " << buffer << std::endl;
}
示例程序4:udp发送接收数据程序
客户端发送,服务器接收
服务器代码:
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<sockaddr*>(&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<sockaddr*>(&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<sockaddr*>(&server), sizeof(server));
UDP 客户端的 socket 通常不需要 connect()
。这是因为:
UDP 是**无连接(connectionless)**协议
-
每次通信都是独立的数据报;
-
不像 TCP 那样需要三次握手,也没有连接状态;
-
所以你用
sendto()
/recvfrom()
就能完成通信,无需connect()
。
但是!UDP 其实也可以用 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<sockaddr*>(&server), sizeof(server));// 然后就能用 send / recv,而不用每次都写目标地址
send(sockfd, "Hello", 5, 0);
recv(sockfd, buffer, sizeof(buffer), 0);
关闭套接字
close
int close(int sockfd);
含义:
-
关闭整个 socket,释放其占用的所有资源(文件描述符、缓冲区等);
-
TCP 情况下,它会向对端发送
FIN
包,优雅断开连接; -
若是 UDP,直接释放资源,没有通知对端;
-
是 强制关闭,无论当前读写状态如何。
返回值:
-
成功返回 0;
-
失败返回 -1(例如关闭两次),并设置
errno
。
适用于:
-
所有 socket,TCP 和 UDP 都可;
-
简单直接,用完就
close()
,非常常用。
shutdown
int shutdown(int sockfd, int how);
how 值 | 含义 |
---|---|
SHUT_RD | 关闭读方向(recv 将立即返回 0) |
SHUT_WR | 关闭写方向(send 将报错) |
SHUT_RDWR | 同时关闭读和写,相当于完全断开 |
特点:
-
常用于 TCP,适合逐步“关闭连接”;
-
允许只断一半,如只发送
FIN
,不马上收FIN
; -
有助于实现半关闭连接的协议逻辑,例如:
-
我发完数据,但还要接收对方数据(只关写);
-
我不想再接收任何数据(只关读);
-
-
最终仍然要用
close()
来释放 socket;
系统调用函数
主机字节序(Host Byte Order)和网络字节序(Network Byte Order)之间进行转换
为什么需要这些函数?
不同计算机架构的**字节序(Endian)**可能不同:
-
小端序(如 x86):低位字节在前;
-
大端序(网络字节序):高位字节在前。
而 网络协议(如 TCP/IP)统一使用大端序(Big Endian)。因此,在主机和网络通信之间要进行字节序转换。
四个函数介绍(来自 <arpa/inet.h>
)
函数名 | 全称 | 功能说明 |
---|---|---|
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 <arpa/inet.h>
#include <iostream>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系列
inet
系列函数是用于 IP地址和文本字符串之间转换 以及 基本验证 的一组函数,常用于处理 IPv4 地址。它们来自 <arpa/inet.h>
头文件,常见的有:
函数名 | 功能简介 | 支持协议 | 常用吗 | 备注 |
---|---|---|---|---|
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 算法(即是否立即发送) |