1983 年,4.2 BSD 发布了基于套接字技术的第一个 TCP/IP 协议栈 API 实现,它成为此后其它系统 TCP/IP 实现的基础。POSIX 的 socket(7)标准是在 4.4 BSD 的基础上制定,微软则于 1990 年代初期在成功移植 BSD 套接字的基础上开发了 winsock,此外使用 TCP/IP 技术进行通讯的各类嵌入式系统也有诸多基于 Socket API 的移植版本。git
套接字是在文件 I/O 机制的基础上实现的,包括匿名
和有名
两种文件形式。典型的有名套接字是 /dev/log,它使用的是 Unix 域套接字,守护进程 syslogd(8)
使用它和使用系统日志服务的客户进程通讯。下面的内容除非特别注明,不然“套接字”特指匿名套接字。web
用于分析 TCP/IP 协议的经典 Unix 工具包括 netcat(1)
和 tcpdump(1)
。前者被称为网络瑞士军刀,能够创建任意基于 TCP/IP 的网络链接并进行输入输出;后者能够把所在网络上的数据流转储到当前的标准输出,这些输出可经过管道线链接到一些文本过滤器之类的程序进行分析。编程
进程标识由两部分组成:安全
套接字地址结构能够在两个方向上传递:从进程到内核和从内核到进程。网络
套接字类型app
网络上通讯的双方多是异构主机,这意味着可能存在字节顺序(Endianness)的不一样。less
例如 Motorola 68K 系列、早期的 SPARC 等采用的是大端(big-endian)(或称高地址优先)字节序,即在一个机器字的存储单元上,低字节存在高地址,高字节存在低地址上
;而 Interl X86 等则采用小端(little-endian)(或称低地址优先)字节序,即在一个机器字的存储单元上,低字节存在低地址,高字节存在高地址
;而 ARM, SPARC V9, MIPS 等体系结构能够选择使用大端仍是小端模式。它们之间直接通讯会获得错误的数据。dom
术语 “小端” 和 “大端” 表示多个字节值的哪一端(小端或大端)存储在该值的起始地址。socket
主机字节序(host byte order):某个给定系统中所使用的字节序(可能使用大端或者小端)称为主机字节序。tcp
网络字节序(network byte order):网际协议使用大端字节序来传送这些多字节数。
如下接口函数提供了主机字节序和网络字节序的转换。用户没必要关心网络字节顺序是什么,只要数据从主机发送到网络上或者从网络上接收数据时,使用这些函数进行转换,就不用担忧字节顺序错误的问题:
#include <netinet/in.h> uint32_t htonl(uint32_t hostint32); 返回值:以网络字节序表示的 32 位整数 uint16_t htons(uint16_t hostint16); 返回值:以网络字节序表示的 16 位整数 uint32_t ntohl(uint32_t netint32); 返回值:以主机字节序表示的 32 位整数 uint16_t ntohs(uint16_t netint16); 返回值:以主机字节序表示的 16 位整数
异种体系结构的不一样字节顺序同时也带来带有位操做的程序的可移植性问题,移植时须要特别注意。
//肯定主机字节序的程序 //**************** //咱们在一个短整数变量中存放 2 字节的值 0x0102,而后查看它的两个连续字节 //c[0](低位) 和 c[1](高位),以此肯定字节序。 //**************** int main(int argc, char **argv) { union { short s; char c[sizeof(short)]; } un; un.s = 0x0102; printf("%s: ", CPU_VENDOR_OS); 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("unknown\n"); } else printf("sizeof(short) = %d\n", sizeof(short)); exit(0); }
//肯定主机字节序的程序 //**************** //咱们在一个短整数变量中存放 2 字节的值 0x0102,而后查看它的两个连续字节 //c[0](低位) 和 c[1](高位),以此肯定字节序。 //**************** int main(int argc, char **argv) { union { short s; char c[sizeof(short)]; } un; un.s = 0x0102; printf("%s: ", CPU_VENDOR_OS); 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("unknown\n"); } else printf("sizeof(short) = %d\n", sizeof(short)); exit(0); }
一个套接字绑定的进程,在网络上主要以该进程的主机(或 IP 地址,网络层的标识)、协议(传输层的标识)、端口(应用层的标识)等信息来标识。这些信息在 ipv4 因特网域中,以结构 sockaddr_in 来描述,在 ipv6 中,以结构sockaddr_in6表示,两个结构均被封装到套接字的 sockaddr 结构。
#include <netinet/in.h> struct sockaddr_in{ uint8_t sin_len;/* length of structure(16)*/ sa_family_t sin_family; /* address family:AF_INET*/ in_port_t sin_port; /* 16-bit TCP or UDP port number;网络字节序*/ struct in_addr sin_addr; /* 32-bit IPv4 address;网络字节序*/ char sin_zero[8] /* unused */ ... }; 其中,typedef unsigned short sa_family_t; typedef uint16_t in_port_t; struct in_addr{ in_addr_t s_addr;/*IPv4 address*/ }; 其中,typedef uint32_t in_addr_t;
#include <netinet/in.h> struct sockaddr_in{ uint8_t sin_len;/* length of structure(16)*/ sa_family_t sin_family; /* address family:AF_INET*/ in_port_t sin_port; /* 16-bit TCP or UDP port number;网络字节序*/ struct in_addr sin_addr; /* 32-bit IPv4 address;网络字节序*/ char sin_zero[8] /* unused */ ... }; 其中,typedef unsigned short sa_family_t; typedef uint16_t in_port_t; struct in_addr{ in_addr_t s_addr;/*IPv4 address*/ }; 其中,typedef uint32_t in_addr_t;
#include <netinet/in.h> struct sockaddr_in6{ uint8_t sin6_len; /*length of this struct (28)*/ sa_family_t sin6_family; /* address family:AF_INET6*/ in_port_t sin6_port; /* port number;网络字节序*/ uint32_t sin6_flowinfo;/*traffic class and flow info*/ struct in6_addr sin6_addr; /* IPv6 address;网络字节序*/ uint32_t sin6_scope_id;/*set of interfaces for scope*/ } 其中, struct in6_addr{ uint8_t s6_addr[16];/*IPv6 address*/ }
#include <sys/un.h> struct sockaddr_un{ sa_family_t sun_family; /* AF_LOCAL */ char sun_path[104] /* null-terminated pathname */ }
#include <sys/un.h> struct sockaddr_un{ sa_family_t sun_family; /* AF_LOCAL */ char sun_path[104] /* null-terminated pathname */ }
struct sockaddr_dl{ unit8_t sdl_len; sa_family_t sdl_family; /* AF_LINK*/ uint16_t sdl_index; /* system assigned index,if > 0 */ uint8_t sdl_type; /* IFT_ETHER,etc.from <net/if_types.h> */ uint8_t sdl_nlen; /* name length, starting in sdl_data[0] */ uint8_t sdl_alen; /* link-layer address length */ uint8_t sdl_slen; /* link-layer selector length */ char sdl_data[12];/* minimum work area,can be larger; contains i/f name and link-layer address */ }
struct sockaddr_dl{ unit8_t sdl_len; sa_family_t sdl_family; /* AF_LINK*/ uint16_t sdl_index; /* system assigned index,if > 0 */ uint8_t sdl_type; /* IFT_ETHER,etc.from <net/if_types.h> */ uint8_t sdl_nlen; /* name length, starting in sdl_data[0] */ uint8_t sdl_alen; /* link-layer address length */ uint8_t sdl_slen; /* link-layer selector length */ char sdl_data[12];/* minimum work area,can be larger; contains i/f name and link-layer address */ }
#include <sys/socket.h> struct sockaddr{ uint8_t sa_len; sa_family_t sa_family; /* address family*/ char sa_data[]; //variable-length address;Linux下为 sa_data[14] ... };
因而套接字函数被定义为以指向某个通用套接字地址结构的一个指针做为其参数之一,这正如bind 函数的 ANSI C 函数原型所示:
int bind(int, struct sockaddr *, socklen_t);
int bind(int, struct sockaddr *, socklen_t);
这就要求对这些函数的任何调用都必需要将指向特定于协议的套接字地址结构的指针进行类型强制转换(casting),变成指向某个通用套接字地址结构的指针,例如:
struct sockaddr_in serv; /* IPv4 socket address structure */ /* fill in serv{} */ bind( sockfd, (struct sockaddr *) &serv, sizeof( serv));
struct sockaddr_in serv; /* IPv4 socket address structure */ /* fill in serv{} */ bind( sockfd, (struct sockaddr *) &serv, sizeof( serv));
#include <netinet/in.h> struct sockaddr_storage{ uint8_t sa_len;/* length of this struct(implementation dependent)*/ sa_family_t sa_family;/* address family:AF_xxx value*/ }
#include <netinet/in.h> struct sockaddr_storage{ uint8_t sa_len;/* length of this struct(implementation dependent)*/ sa_family_t sa_family;/* address family:AF_xxx value*/ }
sockaddr_ storage 类型提供的通用套接字地址结构相比 sockaddr 存在如下两点差异。
套接字地址结构(sockaddr_in)一直在内核和进程地址空间之间进行传递。当从用户进程向 内核空间传递时(例如 bind,connect,sendto),其中一个参数是套接字地址结构的大小,从 而告诉内核从用户进程到内核确切拷贝多少数据。而当从内核空间向用户进程传递时(accept,recvfrom,getsockname,getpeername)时,传递的是表示结构大小的整数的指针。这是一个**“值-结果”参数**。
struct sockaddr_in serv; connect(sockfd,(sock_addr *)&serv,sizeof(serv));
struct sockaddr_in serv; connect(sockfd,(sock_addr *)&serv,sizeof(serv));
len = sizeof(cli); getpeername(unixfd,(sock_addr *)&cli,&len);
len = sizeof(cli); getpeername(unixfd,(sock_addr *)&cli,&len);
#include <arpa/inet.h> int inet_aton( const char *strptr, struct in_addr *addrptr); //返回:若字符串有效则为 1,不然为 0 in_addr_t inet_addr( const char *strptr); //该函数已经弃用了 //返回:若字符串有效则为 32 位二进制网络字节序的 IPv4 地址,不然为INADDR_ NONE char *inet_ntoa( struct in_addr inaddr); //返回:指向一个点分十进制数串的指针
#include <arpa/inet.h> int inet_aton( const char *strptr, struct in_addr *addrptr); //返回:若字符串有效则为 1,不然为 0 in_addr_t inet_addr( const char *strptr); //该函数已经弃用了 //返回:若字符串有效则为 32 位二进制网络字节序的 IPv4 地址,不然为INADDR_ NONE char *inet_ntoa( struct in_addr inaddr); //返回:指向一个点分十进制数串的指针
#include <arpa/inet.h> /* 将文本字符串格式转换为网络字节序的二进制地址 */ int inet_pton(int family, const char* restrict str, void* restrict addr); //返回值:若成功,返回 1;若格式无效,返回 0;若出错,返回 -1 /* 将网络字节序的二进制地址转换为文本字符串格式 */ const char* inet_ntop(int family, const void *restrict addr, char* restrict str, socklen_t size); //返回值:若成功,返回地址字符串指针;若出错,返回 NULL
#include <arpa/inet.h> /* 将文本字符串格式转换为网络字节序的二进制地址 */ int inet_pton(int family, const char* restrict str, void* restrict addr); //返回值:若成功,返回 1;若格式无效,返回 0;若出错,返回 -1 /* 将网络字节序的二进制地址转换为文本字符串格式 */ const char* inet_ntop(int family, const void *restrict addr, char* restrict str, socklen_t size); //返回值:若成功,返回地址字符串指针;若出错,返回 NULL
family: AF_INET或者AF_INET6。若是以不被支持的地址族做为 family 参数,这两个函数就都返回一个错误,并将 errno 置为 EAFNOSUPPORT。
size: 指定保存文本字符串缓冲区 str 的大小,该参数为 INET_ADDRSTREAM 或者 INET6_ADDRSTREAM 时,代表使用足够大的空间来存放该地址。若是 size 过小,不足以容纳表达格式结果( 包括结尾的空字符),那么返回一个空指针,并置 errno 为 ENOSPC。
#include <netinet/in.h> #define INET_ ADDRSTRLEN 16 /* for IPv4 dotted- decimal */ #define INET6_ ADDRSTRLEN 46 /* for IPv6 hex string */
#include <netinet/in.h> #define INET_ ADDRSTRLEN 16 /* for IPv4 dotted- decimal */ #define INET6_ ADDRSTRLEN 46 /* for IPv6 hex string */
inet_pton 的输出为网络字节序,inet_ntop 的输入为网络字节序,要注意转换。
使用方法:
struct sockaddr_in addr; inet_ntop( AF_INET, &addr.sin_addr, str, sizeof( str)); //或为 IPv6 编写以下代码: struct sockaddr_in6 addr6; inet_ntop( AF_INET6, &addr6.sin6_addr, str, sizeof( str));
struct sockaddr_in addr; inet_ntop( AF_INET, &addr.sin_addr, str, sizeof( str)); //或为 IPv6 编写以下代码: struct sockaddr_in6 addr6; inet_ntop( AF_INET6, &addr6.sin6_addr, str, sizeof( str));
注意:对结果进行静态存储致使该函数不可重入且非线程安全。
#include "unp.h" #ifdef HAVE_SOCKADDR_DL_STRUCT #include <net/if_dl.h> #endif /* include sock_ntop */ char * sock_ntop(const struct sockaddr *sa, socklen_t salen) { char portstr[8]; static char str[128]; /* Unix domain is largest */ 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); } /* end sock_ntop */ #ifdef IPV6 case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; 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; /* OK to have no pathname bound to the socket: happens on every connect() unless client calls bind() first. */ 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_LINK, 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); } char * Sock_ntop(const struct sockaddr *sa, socklen_t salen) { char *ptr; if ( (ptr = sock_ntop(sa, salen)) == NULL) err_sys("sock_ntop error"); /* inet_ntop() sets errno */ return(ptr); }
#include "unp.h" #ifdef HAVE_SOCKADDR_DL_STRUCT #include <net/if_dl.h> #endif /* include sock_ntop */ char * sock_ntop(const struct sockaddr *sa, socklen_t salen) { char portstr[8]; static char str[128]; /* Unix domain is largest */ 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); } /* end sock_ntop */ #ifdef IPV6 case AF_INET6: { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; 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; /* OK to have no pathname bound to the socket: happens on every connect() unless client calls bind() first. */ 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_LINK, 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); } char * Sock_ntop(const struct sockaddr *sa, socklen_t salen) { char *ptr; if ( (ptr = sock_ntop(sa, salen)) == NULL) err_sys("sock_ntop error"); /* inet_ntop() sets errno */ return(ptr); }
字节流套接字(例如 TCP 套接字)上的 read 和 write 函数所表现的行为不一样于一般的文件 I/O。字节流套接字上调用 read 或 write 输入或输出的字节数可能比请求的数量少,然而这不是出错的状态。这个现象的缘由在于内核中用于套接字的缓冲区可能已达到了极限。此时所需的是调用者再次调用 read 或 write 函数,以输入或输出剩余的字节
。
对于文本行交互的应用来讲,程序应该按照操做缓冲区而非按照操做文本行来编写。