- 1、简介
- 1.1 IPv4套接字地址结构
- 1.2 通用套接字地址结构
- 1.3 IPv6套接字结构
- 1.4 新的通过套接字地址结构
- 1.5 套接字结构的比较
- 2、值-结果参数
- 2.1 从进程到内核传递
- 2.2 从内核到进程
- 3、字节排序函数
- 4、字节操纵函数
- 5、inet_aton、inet_addr和inet_ntoa函数
- 6、inet_pton和inet_ntop函数
- 7、sock_ntop和相关函数
- 8、readn、writen和readline函数
大多数套接字函数都需要一个指向套接字地址结构的指针作为参数; 每个协议族都定义它自己的套接字地址结构;均以sockaddr_开头,并以对应每个协议族的唯一后缀结尾;1.1 IPv4套接字地址结构
// 即网际套接字地址结构 #include1.2 通用套接字地址结构struct in_addr{ in_addr_t s_addr; // 32位IPv4地址的网络字节序 }; // POSIX规范只需要中间三个字段即可 // 该结构的大小至少位16字节 struct sockaddr_in { uint8_t sin_len; // 无需 sa_family_t sin_family; // 协议族如:AF_INET in_port_t sin_port; // 16位TCP或UDP端口号 struct in_addr sin_addr; char sin_zero[8]; // 无需 }; 【注意】: - 在适用时IPv4地址和port总是以网络字节序来存储; - 套接字结构仅在给定主机上适用,并不在主机之间传递;
参数传递中,该结构总是以引用形式传递; 【为了解决如何声明传递指针的数据类型上的问题】: - 要求对函数任何调用都指向特定于协议的套接字地址结构的指针进行强制转换; 如: struct sockaddr_in serv; bind(sfd, (struct sockaddr*)&serv, sizeof(serv));
#include1.3 IPv6套接字结构struct sockaddr { uint8_t sa_len; sa_family_t sa_family; char sa_data[14]; // 协议专用地址 };
struct in6_addr{
uint8_t s6_addr[16]; // 128位IPv6地址
};
#define SIN6_LEN
struct sockaddr_in6{
uint8_t sin6_len; // 长度
sa_family_t sin6_family; // AF_INET6
in_port_t sin6_port; // 端口
uint32_t sin6_flowinfo; // 流信息
struct in6_addr sin6_addr; // IPv6地址
uint32_t sin6_scope_id; // 范围 ID
};
【注意】:
- 若系统支持SIN6_LEN,则需要定义sin6_len;
- 该结构字段先后顺序做过编排,若该结构本身是64位对齐,则128位的sin6_addr字段也是64位对齐;
- sin6_flowinfo该字段分为两个字段:
低序20位是流标;
高序12位保留;
- sin6_scope_id最常见的是链路局部地址的接口索引;
1.4 新的通过套接字地址结构
struct sockaddr_storage{
uint8_t ss_len; // 结构体长度
sa_family_t ss_family; // 协议族
};
与旧通用套接字的差别
- sockaddr_storage能够满足最苛刻的对齐要求; - sockaddr_storage足够大,能容纳系统支持的任何套接字地址结构;1.5 套接字结构的比较 2、值-结果参数 2.1 从进程到内核传递
从进程到内核传递套接字地址结构函数有3个:bind、connect、sendto; - 此类函数分别有两个参数,某个套接字地址结构的指针,另一个是该结构的整数大小; struct sockaddr_in serv; connect(sockfd, (struct sockaddr*)&serv, sizeof(serv));2.2 从内核到进程
该类函数有:accept、recvfrom、getsockname、getpeername; - 该类函数有两个参数:指向某个套接字地址结构的指针和指向表示结构大小的整数变量的指针; struct sockaddr_un cli; socklen_t len = sizeof(cli); getpeername(ufd, (struct sockaddr*)&cli, &len); 【参数3的含义】: 当函数被调用时,传入用于告诉内核该结构的大小,故内核在写该结构时不会越界; 当函数返回值,结构大小又是一个结果,告诉进程内核在该结构中存储了多少信息; 若地址长度固定,则返回固定长度(IPv4为16、IPv6为28); 此类函数还有select、getsocketopt、热recvmsg、ifconf、sysctl;3、字节排序函数
【网络协议】| 【01】网络字节序大端、小端
大端、小端没有标准可循,都有系统使用;把给定系统所用是字节称为主机字节序; 系统能够在复位或运行时,在大端和小端之间进行切换; 【网络协议】:发送协议栈和接收协议栈中必须就这些多字节阶段各个字节的传送顺序达成一致; - 网际协议使用大端字节序来传送这些字节;
8 #include9 10 using namespace std; 11 12 13 void test() { 14 union { 15 short s; 16 char c[sizeof(short)]; 17 }un; 18 24 un.s = 0x0102; // 查看它的两个连续字节c[0]和c[1]的地址,来确定字节序 25 if(sizeof(short) == 2) { 26 if(un.c[0] == 1 && un.c[1] == 2) 27 cout << "big-endian" << endl; 28 else if(un.c[0] == 2 && un.c[1] == 1) 29 cout << "little-endian" << endl; 30 else 31 cout << "unknown" << endl; 32 }else { 33 cout << "sizeof(short) = " << sizeof(short) << endl; 34 } 35 exit(0); 36 } 37 38 int main(int argc, char* argv[]) 39 { 40 test(); 41 42 return 0; 43 }
如何在主机字节序和网络字节序之间相互转换
#includeuint16_t htons(uint64_t host16bitvalue); uint32_t htonl(uint32_t host32bitvalue); uint16_t htohs(uint16_t host16bitvalue); uint32_t ntohl(uint32_t net32bitvalue);
位序
它表示按照在线缆上出现的顺序排列的4个字节,最左边的位是最早出现的最高有效位; - 注意位序的编号从0开始,分配给最高有效位的编号为0;4、字节操纵函数
用来处理套接字结构,为了操纵如IP地址此类的字段,由于该字段可能包含值为0的字节,并非C字符串;
#includevoid bzero(void *dest, size_t nbytes); void bcopy(const void *src, void *dest, size_t nbytes); int bcmp(const void *ptr1, const void *prt2, size_t nbytes);
ANSI C函数
#include5、inet_aton、inet_addr和inet_ntoa函数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);
以下为地址转换函数,在ASCII字符串与网络字节序的二进制之间转换网际地址; - inet_aton、inet_addr和inet_ntoa在点分十进制数串与它长度为32位的网络字节序二进制间转换IPv4地址; - 其中inet_aton、inet_ntoa对于IPv4和IPv6都适用;
#include 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);6、inet_pton和inet_ntop函数
此类函数适用于IPv4和IPv6,其中p代表表达为ASCII字符串,n代表数值存放到套接字结构中的二进制值;
#include int inet_pton(int family, const char *strptr, void *addrptr); const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
简化版
#include7、sock_ntop和相关函数#include #include #include #include #include using namespace std; int inet_pton_t(int family, const char *strptr, void *addrptr) { if(family == AF_INET) { struct in_addr in_val; if(inet_aton(strptr, &in_val)) { memcpy(addrptr, &in_val, sizeof(struct in_addr)); return 1; } return 0; } errno = EAFNOSUPPORT; return -1; } const char *inet_ntop_t(int family, const void *addrptr, char *strptr, size_t len) { const u_char *p = (const u_char*)addrptr; if(family == AF_INET) { char temp[INET_ADDRSTRLEN]; snprintf(temp, sizeof(temp), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); if(strlen(temp) >= len) { errno = ENOSPC; return NULL; } strcpy(strptr, temp); return strptr; } errno = EAFNOSUPPORT; return NULL; } int main(int argc, char* argv[]) { const char *str = "192.1.3.2"; char buf[128]; inet_pton_t(AF_INET, str, buf); char ret[128]; inet_ntop_t(AF_INET, buf, ret, 128); cout << ret << endl; return 0; }
如果适用inet_ntop,那么将要求调用者传递一个指向某个二进制地址的指针,该地址通常包含一个套接字地址结构 要求调用者将该结构的格式和地址族; 为了解决上述问题,我们将自定义sock_ntop指向某个套接字地址结构的指针位参数,查看该结构的内部,调用适当的函数;
sock_ntop实现
#include8、readn、writen和readline函数#include #include #include #include #include #include #ifdef HAVE_SOCKADDR_DL_STURCT #include #endif char *sock_ntop(const struct sockaddr *sa, socklen_t salen){ char portstr[8]; static char str[128]; switch (sa->sa_family) { case AF_INET: { struct sockaddr_in *sin = (struct sockaddr_in *) sa; if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str) == NULL)) { return NULL; } if (ntohs(sin->sin_port) != 0) { snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port)); strcat(str, portstr); } return str; } #ifdef IPV6 case AF_INET6 : { struct sockaddr_in6 *sin6 = (struct sockaddr_in6) as; str[0] = '['; if(inet_ntop(AF_INET6, &sin6->sin6_addr, str+1, sizeof(str)-1) == NULL) return NULL; if(ntohs(sin6->sin6_port) != 0){ snprintf(portstr, sizeof(portstr), "]:%d", ntohs(sin6->sin6_port)); strcat(str, portstr); return str; } return str+1; } #endif #ifdef AF_UNIX case AF_UNIX : { struct sockaddr_un *unp = (struct sockaddr_un*)sa; if(unp->sun_path[0] == 0) strcpy(str, "(no pathname bound)"); else snprintf(str, sizeof(str), "%s", unp->sun_path); return str; } #endif #ifdef HAVE_SOCKADDR_DL_STRUCT case AF_LINK: { struct sockaddr_dl *sdl = (struct sockaddr_dl *)sa; if(sdl->sdl_nlen > 0) snprintf(str, sizeof(str), "%*s (index %d)", sdl->sdl_nlen, &sdl->sdl_data[0], sdl->sdl_index); else snprintf(str, sizeof(str), "AF_LINKm index=%d", sdl->sdl_index); return str; } #endif default: { snprintf(str, sizeof(str), "sock_ntop: unknown AF_xxx: %d, len %d", sa->sa_family, salen); return str; } } return NULL; } // 包裹函数,对sock_ntop进行错误处理 char *Sock_ntop(const struct sockaddr *sa, socklen_t salen) { char *ptr; if((ptr == sock_ntop(sa, salen)) == NULL) { printf("sock_ntop error"); exit(1); } return ptr; }
字节流套接字上调用read和write的字节数可能比请求的数量少,不同于通常的文件; 导致该现象可能是内核中用于套接字的缓冲区可能已达极限,此时需要再次调用write或read; ==> 为了不让实现返沪iyig不足的字节计数值,改写使用writen代替write函数;
ssize_t Readn(int fd, void *buf, size_t n) {
size_t nleft = n; // 还剩多少字符
size_t nread; // 读取多少字符
char *ptr = (char*)buf; // 装载读取内容
while(nleft > 0) {
if((nread = read(fd, ptr, nleft)) < 0){
if(errno == EINTR)
nread = 0;
else
return -1;
}else if(nread == 0)
break;
nleft -= nread; // 剩余多少字符未读取
ptr += nread; // 指针偏移
}
return n-nleft;
}
ssize_t Written(int fd, const void *buf, size_t n) {
size_t nleft = n;
ssize_t nwritten;
const char *ptr = (char *)buf;
while (nleft > 0) {
if((nwritten = write(fd, ptr, nleft)) <= 0){
if(nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
static int read_cnt;
static char* read_ptr;
static char read_buf[128];
static ssize_t my_read(int fd, char *ptr) {
if(read_cnt <= 0){
again:
if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
if(errno == EINTR)
goto again;
return -1;
}else if(read_cnt == 0)
return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
ssize_t Readline(int fd, void *buf, size_t maxlen) {
ssize_t n, rc;
char C, *ptr;
ptr = (char*)buf;
for(n = 1; n < maxlen; ++n) {
if((rc = my_read(fd, &C)) == 1) {
*ptr++ = C;
if(C == 'n') break;
}else if(rc == 0) {
*ptr = 0;
return n-1;
}else {
return -1;
}
}
*ptr = 0;
return n;
}
ssize_t readlinebuf(void **vptrptr) {
if(read_cnt)
*vptrptr = read_ptr;
return read_cnt;
}



