Unix socket的准备(一)
套接字地址结构
套接字编程中,五元组是广为人知的. (host_ip, host_port, target_ip, target_port, protocol).
其中 ip 和 port 就是由套接字地址结构来表示的, 无论是Server端监听的 ip 和 port 或是客户端连接的服务端的地址,都需要通过套接字地址结构来表示
ipv4 套接字结构
# <netinet/in.h>
struct in_addr{ /* Network byte order*/
in_addr_t s_addr; /* 32-bit IPV4 address*/
};
struct sockaddr_in{
uint8_t sin_len;
sa_family_t sin_family; /* Address family, default: AF_INET */
in_port_t sin_port; /* 16 bit port num: Network byte order */
struct in_addr sin_addr; /* 32 bit ip addr: network byte order */
char sin_zero[8]; /* Unused */
};
在Linux的套接字结构中并没有sin_len
成员. POSIX规范中也不要求有这个成员. POSIX规范只需要这个结构中的3个字段 sin_family
, sin_addr
, sin_port
. 几乎所有的实现都增加了sin_zero
成员.
其中sin_port
成员和sin_addr
成员是网络字节序,需要使用字节排序函数处理。
通用套接字地址结构
将套接字结构(ipv4/ipv6/unix sock
)传递给内核时需要转成通用的套接字地址结构。
struct sockaddr{
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
值-结果参数
从内核到进程传递套接字地址结构的函数有4个: accept
、 recvfrom
、getsockname
、getpeername
这4个函数中的两个参数都是指向某个套接字地址结构的指针和表示该结构大小的整形变量的指针
struct sockaddr_un cli; /* Unix domain*/
socklen_t len = sizeof(cli);
getpeername(unixfd, (sockaddr*) &cli, &len); /* len may have changed*/
这里使用的是len的指针,而不是len,原因在于:当函数被调用时,结构大小是一个值(value),它告诉内核该结构的大小,这样内核在写该结构时不至于越界;当信息返回时,结构大小又是一个结果(result),它告诉进程内核在该结构中究竟存了多少信息.这种类型的参数称为 值-结果(value-result)参数.
值-结果参数从内核中获取数据的调用中经常会用到. 在网络编程中除了从内核中获取套接字地址的调用外,下面的函数同样也使用了值-结果参数
select
函数中间的3个参数
getsockopot
函数的长度参数
使用recvmsg
函数,msghdr
结构中的msg_namelen
和msg_controllen
字段
ifconf
中ifc_len
字段
sysctl
函数两个长度参数中的一个
字节排序函数
在网络通信中统一使用大端字节序(即网络字节序)
在套接字地址结构中, 以sockaddr_in
为例, sin_port
和 sin_addr
存储的port和ip均为网络字节序,
那么对于端口号为8080的端口来说,需要将8080转为网络字节序,才能放到套接字地址结构中。为此我们有字节序转换函数:
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue); // 返回:网络字节序
uint32_t htonl(uint32_t host32bitvalue);
uint16_t ntohs(uint16_t net16bitvalue);
uint16_t ntohl(uint16_t net32bitvalue); // 返回:主机的字节序
我们并不关心主机字节序和网络字节序的真实值,我们需要做的调用上述函数在套接字地址结构中存入转后的值就可以。
在那些主机为大端字节序的机器中,四个函数通常被定义为空宏。