3. 套接字编程简介
套接字地址结构
ipv4套接字地址结构
在头文件 <netinet/in.h> 中定义
struct in_addr {
in_addr_t s_addr; //32位ipv4地址
};
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
- sin_len 是长度字段,值为16,即 struct sockaddr_in 结构体的长度。这个成员是为增加对OSI协议支持而添加的。
- sin_zero 字段未使用。
- 在填写sockaddr_in成员时,应该把该整个结构先清0。
- 套接字地址结构本身不在主机之间传递,只在主机上使用。其中的某些字段会用在主机间的通信。
通用套接字地址结构
套接字地址总是以指针方式传递。通用的套接字地址结构:
struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
套接字函数的其中一个参数总是以指针的方式指向通用套接字地址结构。所以函数调用时此参数都会用强制转换 (struct sockaddr *),否则编译器会有警告。
ipv6套接字地址结构
struct in6_addr {
uint8_t s6_addr[16]; //128-bit ipv6 地址
};
#define SIN6_LEN
struct sockaddr_in6 {
uint8_t sin6_len;
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
- ipv6地址族是AF_INET6,而ipv4地址族是AF_INET
套接字参数传递
当往套接字函数传递套接字地址结构时,总是以指针的方式来传递。
- 当进程向内核调用套接字函数时,套接字地址结构的长度也作为一个参数传递。bind, connect, sendto
- 从内核向进程传递套接字地址结构,长度信息存储在一个指针参数里返回。accept, recvfrom, getsockname, getpeername.
字节序 little-endian/big-endian
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitavlue);
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);
- h 代表 host
- n 代表 network
- s 代表 short
- l 代表 long
inet_aton, inet_addr, inet_ntoa
#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);
把strptr所指的字符串转换成32位网络字节序二进制值。成功返回1, 否则返回0
in_addr_t inet_addr(const char *strptr);
把strptr所指的字符串转换成32位网络字节序二进制值,由返回值返回。但出错时返回ffffffff,即255.255.255.255。所以此函数在返回此IP地址时存在歧义。已废弃。
char *inet_ntoa(struct in_addr inaddr);
把32位网络字节序二进制ipv4地址转换成点分十进制数串。返回指针所指向的地址是驻留在静态内存中的,可能此函数内部用一个足够长的static char 数组来存储这个字串。此函数是不可重入的。
int inet_pton(int family, const char *strptr, void *addrptr);
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
* p 表示 persentation
* n 表示 numeric
* family 的参数是 AF_INET 或 AF_INET6
* len 表示缓冲区长度,已定义的有 INET_ADDRSTRLEN, INET6_ADDRSTRLEN
readn, writen, readline 函数
ssize_t /* Read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n)
从描述符fd中读出n个字节到vptr所指的地址中,返回成功读出的字节数。如果其中某次read返回EINTR, 则重读。
ssize_t /* Write "n" bytes to a descriptor. */
writen(int fd, const void *vptr, size_t n)
向描述符fd中写入由vptr指向的n个字节数据。成功则返回n,出错返回-1。write遇到EINTR失败时则重写。
ssize_t
readline(int fd, void *vptr, size_t maxlen)
从fd中读出一行数据。maxlen指定vptr缓冲区的长度。具体实现里,使用了一个长度为MAXLINE的静态缓冲区,避免了频繁调用read。
习题
-
为什么诸如套接字地址结构的长度之类的值-结果参数要用指针来传递?
-
为什么readn和writen都将void *型指针转换为char *型指针?
-
试写一个名为inet_pton_loose的函数,它能处理如下情形:如果地址族AF_INET且inet_pton返回0, 那就调用inet_aton看是否成功;类似地,如果地址族为AF_INET6且inet_pton返回0,那就调用inet_aton看是否成功,若成功则返回其ipv4映射的ipv6地址。