3.2 套接字地址结构
3.2.1 IPv4 套接字地址结构
struct in_addr {
in_addr_t s_addr; /* 32位的IPv4地址,需要转换为网络字节的 */
};
struct sock_addr_in {
uint8_t sin_len; /* 结构长度 */
sa_family_t sin_family; /* AF_INET 无符号短整型,16位*/
in_port_t sin_port; /* 端口号 16位,需要转化为网络字节的 */
struct in_addr sin_addr; /* 32位的IPv4地址,需要转换为网络字节的 */
char sin_zero[8]; /* unused 长度为8的字符数组,8个字节*/
};
- sin_len字段简化了长度可变的套接字地址结构的处理;无需检查和设置该字段,除非涉及路由套接字;
- 地址和端口号在套接字地址结构中总是以网络字节存储的;
- sin_zero字段总是被设置为0,但前提是设置该字段前,需要把整个结构设置为0;
- 套接字地址结构仅在本机上使用,不在主机之间传递;
3.2.1 套接字地址结构作为参数
当套接字地址结构需要作为参数进行传递时,怎么处理?ANSIC后有一个办法是void *,但套接字的定义是在其之前,所以需要定义一个通用的套接字地址结构:
struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family; /* 地址族, AF_XXX */
char sa_data[14]; /* 指定协议地址 */
};
为了容纳系统所支持的任何套接字地址结构,新的通用套接字地址结构:
struct sockaddr_storage {
uint8_t ss_len;
sa_family_t ss_family;
}
在套接字地址结构需要作为参数进行传递时,必须要进行类型转换。如:
bind(sockfd, (struct sockaddr *)&serv, sizeof(serv));
否则就会报警告信息:warning :passing arg 2 of "bind" incompatible pointer type
3.3 值-结果参数
参数传递的方向有两个,进程到内核,内核到进程。
- 进程到内核:这样的函数有3个:bind、connect 、sendto,这些函数的参数一个是指向套接字地址结构的指针,一个是该结构的大小。如:
struct sockaddr_in serv;
...
connect(sockfd, (SA*)&serv, sizeof(serv));
把指针和指针大小都传递给内核,内核就知道需要从进程复制多少数据进来;
2.内核到进程:这样的函数有4个:accept、recvfrom、getsockname、getpeername,这些函数的参数一个是指向套接字地址结构的指针,一个是指向该结构大小的变量的指针。如:
struct sockaddr_t cli;
socklen_t len; /*套接字地址结构大小的类型实际是socklen_t,不是int,只是POSIX规范建议将其改成uint32_t*/
len = sizeof(cli);
getpeername(unixfd, (SA*)&cli, &len);
为什么这里要把套接字地址结构的大小由一个整数值改为指针呢????
因为:当函数被调用时,结构大小是一个value,告诉内核该结构的大小,这样内核在读数据的时候不至于越界;当函数返回结果时,结构大小是一个指针,告诉内核在该结构中存储了多少信息。
3.4 字节排序函数
考虑一个16位整数,由2个字节组成。内存中有两种存储该整数的方法:一种是低序字节存储在起始地址,另一种是高序字节存储在起始地址。
这两种存储方式都有系统在用,那怎么查看当前系统采用的哪种存储方式呢???
#include "unp.h"
int main(int argc, char** argv) {
union {
short s;
char c[sizeof(short)];
} un;
un.s = 0x0102; /* 在一个短整型变量中存放2字节的值0x0102 */
printf("%s:", CPU_VENDOR_OS); /* 标识CPU类型、厂家、操作系统版本 */
if (ssizeof(short) == 2) {
if (un.c[0] == 1 && un.c[1] == 2) { /* 查看它的连续两个字节的 */
printf("big-endian\n")
} else if (un.c[0] == 2 && un.c[1] == 1) {
printf("little-endian\n")
} else {
printf("unknown\n");
}
} else {
printf("sizeof(short) = %d\n", sizeof(short))
}
exit(0);
}
网际协议使用大端字节序存储。
主机字节序和网络字节序的转换,涉及的函数:
#include <netinet/in.h>
/* host to net */
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
/* net to host */
uint16_t ntohs(uint16_t net16bitvlaue);
uint32_t ntohl(uint32_t net32bitvalue);
3.5 字节操纵函数
以b开头的函数、以mem开头的函数。
如用bzero函数将套接字结构初始化为0;bcopy、bcmp(两个字符串相同则返回0,否则返回非0)。
#include <string.h>
void bzero(void *dest, size_t nbytes);
void bcopy(const void *src, void *dest, size_t nbytes);
int bcmp(const void *src, const *dest, size_t nbytes);
#include <string.h>
/* 把目标字符串指定数目的字节值设置为c */
void *memset(void *dest, int c, size_t len);
/* 与bcopy函数类似,但参数相反 */
/* 当源字节串与目标字节串重叠时,bcopy可以正确处理,但memcpy返回结果不可知 */
/* 这种情况下建议使用memmove函数 */
void *memcpy(void *dest, const void *src, size_t len);
int memcmp(const void *ptr1, const void *ptr2, size_t len);
3.6 地址转换函数
点分十进制IPv4地址和32位的网络字节序二进制地址的相互转换
#include <arpa/inet.h>
/* 点分十进制转网络字节序二进制,转换成功返回1,否则返回0 */
/* 如果addrptr指针为空,依旧会进行转换,只是不存储结果 */
int inet_aton(const char *strptr, struct in_addr *addrptr);
/* 已废弃 */
in_addr_t inet_addr(const char *strptr);
/* 网络字节序二进制转点分十进制 */
char *int_ntoa(strcut in_addr *inaddr);
以下两个新的函数对于IPv4和IPv6地址都适用。
#include <arpa/inet.h>
/* 地址有效返回1,地址无效返回0,出错返回-1 */
int inet_pton(int family, const char *strptr, void *addrptr);
/* 转换成功返回指针,出错返回NULL */
const char *innet_ntop(int family, const *addrptr, char *strptr, size_t len);
/* 函数中的p是地址的表达presentation,n是二进制值numeric */
3.9 读写函数
使用read或者write函数输入或输出套接字数据的时候,可能数据比请求的数据量少,这不是出错,而是内核中用于套接字的缓冲区以达到极限,你可以循环多次调用read/write函数,也可以使用readn/writen代替
ssize_t readn(int filedes, void *buff, size_t nbytes);
ssize_t writen(int filedes, const void *buff, size_t nbytes);
ssize_t readline(int fileds, void *buff, size_t maxlen);