Sockets Introduction
1. Socket Address Structure
绝大多数socket function都需要一个指向socket address structure的指针作为参数. 所有socket address structure都以sockaddr_
作为开头.
1.1 IPv4 Socket Address Structure
IPv4 Socket Address Structure定义在<netinet/in.h>
:
struct in_addr{ |
补充:
- 4.3BSD-Reno发布时为支持OSI, 将
sin_len
加入到sockaddr_in
中; 在这之前第一个成员变量为sa_family
, 其类型uint8_t
是POSIX.1中规定的数据类型, 以下是所有POSIX-compliant系统提供的数据类型:int8_t
: Signed 8-bit integeruint8_t
: Unsigned 8-bit integerint16_t
: Signed 16-bit integeruint16_t
: Unsigned 16-bit integerint32_t
: Signed 32-bit integeruint32_t
: Unsigned 32-bit integersa_family_t
: Address family of socket address structuresocklen_t
: Length of socket address structure,uint32_t
in_addr_t
: IPv4 address,uint32_t
in_port_t
: TCP or UDP port,uint16_t
- 用户不需要设置
sin_len
, 只有kernel接触该域:bind()
,connect()
,sentto()
,sendmsg()
这四个函数将socket address structure从进程传入kernel时, 都会经过sockargs()
, 该函数负责填充sin_len
;accept()
,recvfrom()
,recvmsg()
,getpeername()
,getsockname()
这五个函数从kernel中取回到进程时, 会设置sin_len
域. - The POSIX specification只要求用户填写三个域:
sin_family
,sin_addr``和sin_port
.sin_zero
为填充位, 用来保证socket address structure至少16 bytes大小. sa_family_t
为无符号整数类型,in_port_t
至少16位无符号整数,in_addr_t
至少32位无符号整数- TCP和UDP port number都以network byte order(网络字节序)存储.
- 两种访问
sockaddr_in
中32-bit IPv4 address的方式: 假设现在有一个名为serv
的sockaddr_in
结构体, 其中serv.sin_addr
代表in_addr struct
;ser.sin_addr.s_addr
代表in_addr_t
(an unsigned 32-bit integer) sin_zero
不可用, 一般置为0. 通常先将整个socket address structure置0, 再填写其他域.- Socket address structure只放置在主机中用于协助通信, 并不传输到网络中.
1.2 Generic Socket Address Structure
不同协议使用不同的socket address structure, socket function会接收并处理指向socket address structure的指针. 但如何将不同的socket address structure作为参数传入socket function成了一个问题: ANSI C提出void *
作为接收不同指针类型的参数类型, 但socket function的出现早于ANSI C, 导致无法使用void *
. 因此提出generic socket address structure作为解决方案.
/* 定义在<sys/socket.h> */ |
所以socket function只需要以sockaddr
指针作为参数即可接受所有socket address structure. 以bind()
为例:
int bind(int, struct sockaddr *, socklen_t); |
可将目标socket address structure强制转换为sockaddr
指针, 再将sockaddr
指针和长度传入bind()
:
struct sockaddr_in serv; |
1.3 IPv6 Socket Address Structure
IPv6 socket address structure定义在<netinet/in.h>
中.
struct in6_addr { |
补充:
- 若系统支持socket address structure中的长度成员, 则必须定义
SIN6_LEN
常量 - IPv6 family为
AF_INET6
, IPv4 family为AF_INET
sin6_flowinfo
分为两个字段: 低字节的20位为flow label, 高字节的12位为保留位
sin6_scope_id
用于标示scope zone, 常用于link-local address的接口索引
1.4 New Generic Socket Address Structure
sockaddr_storage可放置任何大小的socket address, 定义在<netinet/in.h>:
struct sockaddr_storage { |
与sockaddr的差别:
sockaddr_storage
提供最严格的的对齐要求sockaddr_storage
可容纳任何大小的socket address structure
1.5 Comparison of Socket Address Structures
2. Value-Result Arguments
所有socket function都会接收两个参数: socket address structure和其长度. 根据socket address structure传输的方向不同, 长度的传参方式也不同.
2.1 From Process to Kernel
bind()
, connect()
, 和sendto()
会将指向socket address structure的指针和length作为参数. 以connect()
为例:
struct sockaddr_in serv; |
结果如下:
2.2 From Kernel to Process:
accept()
, recvfrom()
, getsockname()
, 和getpeername()
这四个函数将指向socket address structure的指针和指向length的指针作为参数, 以getpeername()
为例:
struct sockaddr_un cli; |
结果如下:
3. Byte Ordering Functions
内存中有两种存储字节的方式: 以低序字节开头的little-endian byte order(小端字节序)和以高序字节开头的big-endian byte order(大端字节序). 以16-bit integer为例:
不同的系统使用不同的字节序, 统称为host byte order(主机字节序). 但对于网络的发送方和接收方, 它们可能使用不同的字节序, 从而导致数据解析错误. 为此, 在发送和接收任何数据前都必须将系统中的host byte order转换为network byte order(网络字节序). network byte order采用big-endian byte order, 为方便byte order的转换, The POSIX Specification提供了以下函数:
|
其中, h表示host, n表示network, s表示short(16-bit), l表示long(32-bit).
4. Byte Manipulation Functions
操纵多字节字段的函数有两组: b开头的函数组和mem开头的函数组
4.1 Functions Whose Names Begin with b
自4.2BSD起, 几乎所有遵循POSIX.1的UNIX系统都包含以下函数:
|
4.2 Functions Whose Names Begin with mem
mem开头的函数来自ANSI C standard, 所有支持ANSI C library的系统都可用以下函数:
|
memcmp()
对比的数据单位为unsigned char. 当p1的某个unsigned char比p2对应的unsigned char大, 则返回值大于0; 否则返回值小于0.
5. Address Conversion Functions
UNIX提供了两组函数负责将Internet address在ASCII string和network byte order value之间转换:
inet_aton()
,inet_ntoa()
,inet_addr()
: 负责IPv4地址在dotted-decimal string(例如:206.168.112.96
)和32-bit network byte order value之间的转换.
/* Convert the character string pointed to by strptr
* into 32-bit network byte ordered value
*/
int inet_aton(const char* strptr, struct in_addr* addrptr);
/* Same as inet_aton(), return the 32-bit network byte ordered value */
int_addr_t inet_addr(const char* strptr);
/* Convert the 32-bit network byte ordered value to character string */
char* inet_ntoa(struct in_addr inaddr);inet_pton()
,inet_ntop()
: 负责IPv4和IPv6地址在ASCII string和network byte ordered value之间转换.其中, p表示presentation, n表示numeric. family支持
/* Convert the presentation of address pointed to by strptr
* to the numeric address addrptr
*/
int inet_pton(int family, const char* strptr, void* addrptr);
/* Convert the numeric address addrptr to the presentation of
* address pointed to by strptr
*/
const char* inet_ntop(int family, const void* addrptr, char* strptr, size_t len);AF_INET
(IPv4)和AF_INET6
(IPv6), 若family无法识别, 则返回报错且将errno
置为EAFNOSUPPORT
.inet_ntop()
中的len参数表示strptr的长度, 若len过小, 则将errno
置为ENOSPC
.
6. readn, writen and readline Functions
Stream socket(例如: TCP socket)拥有read()
和write()
函数, 用于接收和发送数据. 不同于文件I/O的read和write, stream socket的read和write可能会接收或发出比目标要求少的数据, 因为kernel中的buffer存在上限. 这就需要多次调用read和nonblocking write(blocking write不会出现这种情况). 以下函数会不断读取或写入数据, 直到数据被读取或写入完毕.
ssize_t readn(int fd, void *ptr, size_t n) |
上述readline()
每次都需调用read()
读取单个字节, 导致效率低下. 修改后的readline()
如下:
static int read_cnt; |
上述readline()
一次读取MAXLINE
个字节, 因而效率提高.