UNIX网络编程总结三
套接口结构
IPv4套接口地址结构:
进程到内核传递套接口地址结构的4个套接口函数:bind,connect,sendto,sendmsg,源自Berkeley的sockargs函数从这四个函数获取地址,并以传入的长度设置sin_len成员。
内核到进程传递套接口地址的5个套接口函数:accetp,recvfrom,recvmsg,getpeername和getsockname,在返回前设置sin_len。
几乎所有实现都增加sin_zero成员,,sin_zero成员暂时不使用,但总是将它置为0。
IPv4地址和TCP、UDP端口号,在套接口地址结构中以网络字节序来存储。
通用套接字地址结构
2 3 4 5 6 |
struct sockaddr { uint8_t sin_len; sa_family_t sa_famliy; char sa_data[14]; /*14字的协议地址*/ } |
套接口函数被定义为指向通用套接口地址的指针,对于这些函数的调用,必须将特定协议的套接口地址结构的指针类型转换为指向通用套接口地址的指针,例如:
2 |
struct sockaddr_in serv; bind(fd, (struct sockaddr *)&serv, sizeof(serv) |
IPv6套接口地址结构:
从进程到内核的函数我们仍然用bind举例,bind将指向套接口地址结构的指针和结构长度传给内核,这样内核就知道从进程拷贝多少数据,但是这只是一个最大值,实例可能并没有拷贝那么多数据,具体拷贝了多少存档在len的指针里,而从内核到进程,传递的是套接口地址结构的指针和表示地址结构大小的指针,该指针指向的长度就是实际写入的长度。因为当函数被调用时结构大小是一个值,当函数返回时,结构大小是一个结果。当使用值-结果参数作为套接口地址结构的长度时,若套接口地址结构是定长的,则从内核返回也是定长的,否则,返回值可能比结构的最大长度小。
大端-小端
将低序字节存在起始地址为小端,将高序字节存在起始地址为大端。比如:数字16的16进制表示为0x0010,数字4096的16进制表示为0x1000。由于Intel机器是小尾端,存储数字16时实际顺序为1000,存储4096时实际顺序为0010。我们可以通过一小段程序来判断大端还是小端:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <stdio.h> # 通过存储位置来确定字节序 int main(int argc, char **argv){ union{ short s; char c[sizeof(short)]; }un; un.s = 0x0102; if(sizeof(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("UNKNOW\n"); }else printf("sizeof(short) = %d", sizeof(short)); return 0; } |
而网际协议在处理多节整数时使用大端字节序,因为存在不一致,我们就需要考虑主机字节序和网络字节序的转换转换用如下四个函数,h代表host,n代表net,s代表short,l代表long:
2 3 4 5 6 7 |
#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); |
协议无关的地址转换函数
以下两个函数协议无关,IPv4和IPv6均可处理,字母p和n分别代表presentation和numeric。
2 3 4 5 |
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr) const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len) |
下面是一个只支持IPv4的简单inet_pton:
下面是一个只支持IPv4的简单的inet_ntop:
1 2 3 4 5 6 7 8 9 10 11 1213 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <stdio.h> #include <arpa/inet.h> #include <sys/socket.h> #include <string.h> #include <errno.h>
#ifndef INET_ADDRSTRLEN #define INET_ADDRSTRLEN 16 #endif
int main(int argc, char **argv){ const char *inet_ntop_t(int, const void *, char *, size_t); struct sockaddr_in servaddr; servaddr.sin_family = AF_INET; servaddr.sin_port = htons(2329); servaddr.sin_addr.s_addr = 606284554; char c[INET_ADDRSTRLEN]; const char *str_ptr;
str_ptr = inet_ntop_t(AF_INET, &servaddr.sin_addr, c, sizeof(c)); printf("str_ptr is %s\r\n", str_ptr); printf("addr is %s\r\n", c); return 0; }
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; } |
readn_t, writen_t, readline_t
这里定义了几个函数,是对read,write的封装,是为了预防返回不足的字节计数值,比如write的时候内核套接口缓存区已经满了,只写入部分,那我们仍然要继续写入,直到写完,读也是一样。
下面给出以上三个函数代码,这些代码都是跑过的,可直接拷贝使用:
readn_t:
writen_t:
readline_t:
测试套接字是否为套接口描述字的函数isfdtype
1 2 3 4 5 6 7 8 9 10 11 |
#include "../lib/lib_sock.h"
int isdftype(int fd, int fdtype){ struct stat buf; if(fstat(fd, &buf) < 0) return -1; if((buf.st_mode & S_IFMT) == fdtype) return 1; else return 0; } |