网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于
文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地
址呢?
- 网络数据流的地址这样规定:
先发出的数据是低地址,后发出的数据是高地址.

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下
库函数做网络字节序和主机字节序的转换。

htons/htonl/ntohl/ntohs
c
#include<arpa/inet.h>
unit32_t htonl(unit32_t hostlong);
- 参数:hostlong:主机字节序的32位无符号整数
- 返回值:网络字节序的32位整数
c
#include<arpa/inet.h>
unit16_t htons(unit16_t hostlong);
- 参数:hostshort:主机字节序的16位无符号整数
- 返回值:网络字节序的16位整数
c
#include<arpa/inet.h>
uint16_t ntohs(uint16_t netshort);
- 参数: netshort: 网络字节序的16位整数
- 返回值:网络字节序的16位整数
c
#include<arpa/inet.h>
uint32_t ntohl(uint32_t netlong);
- 参数:netlong: 传入网络字节序的32位整数
- 返回值:网络字节序的16位整数
| 函数 | 功能 | 含义 | 适用场景 |
|---|---|---|---|
| htons | 主机序 → 网络序,16 位 | host to network short | 端口号 (uint16_t) |
| htonl | 主机序 → 网络序,32 位 | host to network long | IPv4 整型地址 (uint32_t) |
| ntohs | 网络序 → 主机序,16 位 | network to host short | 从 sockaddr 取出端口解析 |
| ntohl | 网络序 → 主机序,32 位 | network to host long | 从 sockaddr 取出 ip 整型解析 |
记忆规则
h开头:h(ost) → n(etwork) 主机转网络,用于填sockaddr结构体,准备发包n开头:n(etwork) → h(ost) 网络转主机,用于从sockaddr取出数据,程序内打印/计算s= short 16位 → 端口l= long 32位 → IPv4地址
使用示例
c
#include <stdio.h>
#include <arpa/inet.h>
int main() {
// 端口号转换(16位)
uint16_t host_port = 8080;
uint16_t net_port = htons(host_port);
printf("Host port: %u -> Network port: %u\n", host_port, net_port);
// IP地址转换(32位)
uint32_t host_ip = 0xC0A80101; // 192.168.1.1
uint32_t net_ip = htonl(host_ip);
printf("Host IP: 0x%08X -> Network IP: 0x%08X\n", host_ip, net_ip);
// 从网络序转回主机序
uint16_t back_port = ntohs(net_port);
uint32_t back_ip = ntohl(net_ip);
return 0;
}

socket
c
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
| 参数 | 说明 | 常用值 |
|---|---|---|
| domain | 协议族/地址族 | AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX(本地) |
| type | 套接字类型 | SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAW(原始) |
| protocol | 协议(通常为 0) | 0(自动选择)、IPPROTO_TCP、IPPROTO_UDP |
- domain:和一个结构体有关------struct sockaddr,本地选择
AF_UNIX网络通信选AF_INET
sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX
Domain Socket. 然而, 各种网络协议的地址格式并不相同.

- IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的
首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的
内容. - socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样
的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构
体指针做为参数;
sockaddr结构

sockaddr_in结构

虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in;
这个结构里主要有三部分信息: 地址类型, 端口号, IP地址.
in_addr结构

in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;
bind
bind() 的作用是 给套接字绑定一个本地地址(IP + 端口),即告诉操作系统这个套接字在哪个地址上监听或发送数据
c
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
| 参数 | 类型 | 说明 |
|---|---|---|
| socket | int | 由 socket() 函数返回的文件描述符 |
| address | const struct sockaddr * | 指向套接字地址结构的指针(需强制转换) |
| address_len | socklen_t address | 所指结构的大小(字节数) |
bzero() 函数详解
bzero() 将内存区域 s 的前 n 个字节全部设置为零(\0)。
c
#include <strings.h>
void bzero(void *s, size_t n);
| 参数 | 类型 | 说明 |
|---|---|---|
| s | void * | 指向要清零的内存区域的指针 |
| n | size_t | 要清零的字节数 |
recvfrom
recvfrom() 的主要作用是 从无连接套接字接收数据,并获取发送方的地址。
c
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
| 参数 | 类型 | 说明 |
|---|---|---|
| sockfd | int | 套接字文件描述符 |
| buf | void * | 接收数据的缓冲区指针 |
| len | size_t | 缓冲区大小(字节数) |
| flags | int | 接收标志(通常设为 0) |
| src_addr | struct sockaddr * | 输出参数:存储发送方地址信息,可为 NULL |
| addrlen | socklen_t * | 输入输出参数:传入地址结构大小,返回实际地址长度 |
- sockfd:是socket接口的返回值。
sendto
sendto() 的主要作用是 通过套接字向指定目标地址发送数据 ,特别适用于 无连接的通信协议(如 UDP)。
c
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
| 参数 | 类型 | 说明 |
|---|---|---|
| sockfd | int | 套接字文件描述符 |
| buf | const void * | 指向要发送数据的缓冲区 |
| len | size_t | 要发送的字节数 |
| flags | int | 发送选项标志(通常为 0) |
| dest_addr | const struct sockaddr * | 目标地址结构指针 |
| addrlen | socklen_t | dest_addr 结构的大小 |