套接字地址结构
IPv4套接字地址结构
通常也称为“网际套接字地址结构”,以sockaddr_in
命名,定义在netinet/in.h
头文件中:
struct in_addr {
in_addr_t s_addr; /*32位的IPv4地址,网络字节序*/
};
/*POSIX规范只需要这个结构中的3个字段:sin_family,sin_addr,sin_port*/
struct sockaddr_in {
uint8_t sin_len; /*套接字地址结构长度(16)*/
sa_family_t sin_family; /*AF_INET*/
in_port_t sin_port; /*16位TCP或UDP端口号,网络字节序*/
struct in_addr sin_addr; /*32位IPv4地址,网络字节序*/
char sin_zero[8]; /*unused*/
};
套接字地址结构可以在两个方向传递:
- 从进程到内核
int bind(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
int connect(int sockfd, struct sockaddr *sver_addr, socklen_t *addrlen)
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags)
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
const struct sockadr *to, socklen_t addrlen)
- 从内核到进程
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags)
ssize_t recvfrom(int sockfd, const void *buff, size_t nbytes, int flags,
struct sockaddr *from, socklen_t *addrlen)int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen)
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen)
套接字地址结构仅在给定的主机上使用:虽然结构中的某些字段(例如IP地址和端口号)用在不同主机之间的通信中,但是结构本身并不在主机之间传递。
通用套接字
为了使套接字函数能够处理来自所支持的任何协议族的套接字地址结构,在<sys/socket.h>
头文件中定义了一个通用的套接字地址结构,并将指向某个套接字地址结构的一个指针作为其参数之一:
struct sockaddr {
uint8_t sa_len; /*套接字地址结构长度(>=16)*/
sa_family_t sa_family; /*AF_xxx*/
char sa_data[14]; /*protocol-specific address*/
};
IPv6套接字地址结构
IPv6套接字地址结构在netinet/in.h
头文件中定义:
struct in6_addr {
uint8_t s6_addr[16]; /*128位IPv6地址,网络字节序*/
};
#define SIN6_LEN /* required for compile-time tests */
struct sockaddr_in6 {
uint8_t sin6_len; /* 套接字地址结构大小(28字节) */
sa_family_t sin6_familay; /* AF_INET6 */
in_port_t sin6_port; /* 传输层端口,网络字节序 */
uint32_t sin6_flowinfo; /* flow information */
struct in6_addr sin6_addr; /* IPv6地址,网络字节序 */
uint32_t sin6_scope_id; /* set of interface for a scope */
};
说明:1)sin6_flowinfo字段分为两部分,低20位是流标(flowlabel),高12位保留;2)对于具备范围的地址(scoped address),sin6_scope_id字段标识其范围,最常见的是链路局部地址的接口索引。
新的通用套接字地址结构
作为IPv6套接字API的一部分而定义的新的通用套接字地址结构struct sockaddr_storage
足以容纳系统支持的任何套接字地址结构,定义在头文件<netinet/in.h>
中:
struct sockaddr_storage {
uint8_t ss_len; /* length of this struct (implementation dependent) */
sa_family_t ss_family; /* address family: AF_xxx value */
/* implementation-dependent elements to provide:
* a) alignment sufficient to fulfill the alignment requirements of all socket
* address types that the system supports.
* b) enough storage to hold any type of socket address that the system
* supports.
*/
};
套接字地址结构比较
值-结果(value-result)参数
在网络编程中,值-结果参数最常见的例子是所返回套接字地址结构的长度,但还会碰到其他一些值-结果参数,如:
select
函数中间的3个参数getsockopt
函数的长度字段- 使用
recvmsg
函数时,msghdr
结构中的msg_namelen
和msg_controllen
字段 ifconf
结构中的ifc_len
字段sysctl
函数两个长度参数中的第一个
字节排序函数
小端(litle-endian)字节序、大端(big-endian)字节序;主机字节序、网络字节序。
术语“小端”和“大端”表示多个字节值的哪一端存储在该值的起始地址。
由于历史原因和POSIX规范的规定,套接字地址结构中的某些字段(IP地址、端口号)必须按照网络字节序进行维护。主机字节序和网络字节序之间的转换使用如下4个函数:
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);
字节操纵函数
名字以b
(表示字节)开头的源自Berkeley的函数定义在头文件<strings.h>
中:
void bzero(void *dest, size_t nbytes);
void bcopy(const void *src, void *dest, size_t nbytes);
int bcmp(cont void *ptr1, const void *ptr2, size_t nbytes);
名字以mem
(表示内存)的源自ANSI C标准的函数定义在头文件<string.h>
中:
void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src, size_t nbytes);
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);
注意:当源字节串与目标字节串重叠时,
bcopy
能够正确处理,但是memcpy
的操作结果却不可知。这时必须改用ANSI C的memmove
函数。
IP地址转换函数
地址转换函数定义在头文件<arpa/inet.h>
中,它们在ASCII字符串(如“206.168.112.96”)与网络字节序的二进制值(存放在套接字地址结构中的值)之间转换IP地址:
inet_aton、inet_addr和inet_ntoa函数
int inet_aton(const char *strptr, struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr);
char *inet_ntoa(struct in_addr inaddr);
inet_addr
函数在出错时返回INADDR_NONE
常值,通常是一个32位均为1的值。这意味着点分十进制数串“255.255.255.255”(IPv4的有限广播地址)不能由该函数处理。
inet_ntoa
函数返回值指向的字符串驻留在静态内存中,因此该函数是不可重入的。
inet_pton和inet_ntop函数
这两个函数是随IPv6出现的新函数,对于IPv4地址和IPv6地址都适用。
int inet_pton(int family, const char *strptr, void *addrptr);
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
在头文件<netinet/in.h>
中定义了两个常量,便于指定参数len
:
#define INET_ADDRSTRLEN 16 /* for IPv4 dotted-decimal */
#define INET6_ADDRSTRLEN 46 /* for IPv6 hex string */
其它
- 通配地址
- 字节流套接字、非阻塞套接字
read
、write
函数和errno
变量- 防御性编程(defensive programming)
当需要从某个套接字读入文本行时,为了提升性能可以使用标准I/O函数库(stdio)。不过需要指出这是种危险的做法,因为stdio缓冲区的状态是不可见的,可能导致应用程序中存在相当隐蔽的缺陷。
基于文本行的网络协议有很多,如SMTP、HTTP、FTP的控制连接协议以及finger等。