1、什么是socket编程
socket能够当作是用户进程与内核网络协议栈的编程接口。
socket不只能够用于本机的进程间通讯,还能够用于网络上不一样主机的进程间通讯。
ubuntu
socket API是一层抽象的网络编程接口,适用于各类底层网络协议,如IPv四、IPv6,以及之后要讲的UNIX Domain Socket。然而,各类网络协议的地址格式并不相同,以下图所示:
小程序
IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,以下所示:网络
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; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ char sin_zero[8]; /* pad bytes, set to zero is ok */ };
struct in_addr{ in_addr_t s_addr; /*32-bit IPV4 address*/ };
IPv6地址用sockaddr_in6结构体表示,包括16位端口号、128位IP地址和一些控制字段。UNIX Domain Socket的地址格式定义在sys/un.h中,用sockaddr_un结构体表示。各类socket地址结构体的开头都是相同的,前16位表示整个结构体的长度(并非全部UNIX的实现都有长度字段,如Linux就没有),后16位表示地址类型。IPv四、IPv6和UNIX Domain Socket的地址类型分别定义为常数AF_INET、AF_INET六、AF_UNIX。这样,只要取得某种sockaddr结构体的首地址,不须要知道具体是哪一种类型的sockaddr结构体,就能够根据地址类型字段肯定结构体中的内容。所以,socket API能够接受各类类型的sockaddr结构体指针作参数,例如bind、accept、connect等函数,这些函数的参数应该设计成void *类型以便接受各类类型的指针,可是sock API的实现早于ANSI C标准化,那时尚未void *类型,所以这些函数的参数都用struct sockaddr *类型表示,即通用地址结构,以下所示:socket
struct sockaddr { uint8_t sa_len; sa_family_t sin_family; char sa_data[14]; };
sin_family:指定该地址家族
sa_data:由sin_family决定它的形式。
函数
在传递参数以前要强制类型转换一下,例如:
测试
struct sockaddr_in servaddr;ui
/* initialize servaddr *spa
/bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));
设计
2、网络字节序
字节序
大端字节序(Big Endian)
最高有效位(MSB:Most Significant Bit)存储于最低内存地址处,最低有效位(LSB:Lowest Significant Bit)存储于最高内存地址处。
小端字节序(Little Endian)
最高有效位(MSB:Most Significant Bit)存储于最高内存地址处,最低有效位(LSB:Lowest Significant Bit)存储于最低内存地址处。
主机字节序
不一样的主机有不一样的字节序,如x86为小端字节序,Motorola 6800为大端字节序,ARM字节序是可配置的。
网络字节序
网络字节序规定为大端字节序
为使网络程序具备可移植性,使一样的C代码在大端和小端计算机上编译后都能正常运行,能够调用如下库函数作网络字节序和主机字节序的转换。
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。若是主机是小端字节序,这些函数将参数作相应的大小端转换而后返回,若是主机是大端字节序,这些函数不作转换,将参数原封不动地返回。
下面写个小程序测试下主机的大小端:
#include<stdio.h> #include<arpa/inet.h> int main(void) { unsigned int x = 0x12345678; unsigned char *p = (unsigned char *)&x; printf("%x %x %x %x\n", p[0], p[1], p[2], p[3]); unsigned int y = htonl(x); p = (unsigned char *)&y; printf("%x %x %x %x\n", p[0], p[1], p[2], p[3]); return 0; }
运行结果:
huangcheng@ubuntu:~$ ./a.out 78 56 34 12 12 34 56 78
即本主机是小端字节序,而通过htonl 转换后为网络字节序,即大端。
3、地址转换函数
前面提到的 sockaddr_in 结构体中的成员struct in_addr sin_addr表示32位的IP地址。可是咱们一般用点分十进制的字符串表示IP地址,如下函数能够在字符串表示和in_addr表示之间转换。
字符串转in_addr的函数:
#include <arpa/inet.h> int inet_aton(const char *strptr, struct in_addr *addrptr); in_addr_t inet_addr(const char *strptr); int inet_pton(int family, const char *strptr, void *addrptr);注意:转换而成的32位数是网络字节序的。
char *inet_ntoa(struct in_addr inaddr); const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);注意:传入的32位数也是网络字节序的。
下面写个小程序演示一下:
#include<stdio.h> #include<arpa/inet.h> int main(void) { unsigned int addr = inet_addr("192.168.0.100"); //转换后是网络字节序(大端) printf("add=%u\n", ntohl(addr)); struct in_addr ipaddr; ipaddr.s_addr = addr; printf("%s\n", inet_ntoa(ipaddr)); return 0; }运行结果:
huangcheng@ubuntu:~$ ./a.out add=3232235620 192.168.0.100
注意,在打印addr的时候先转换成主机字节序,不然输出多是负数。
4、套接字类型
流式套接字(SOCK_STREAM)提供面向链接的、可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接收。数据报式套接字(SOCK_DGRAM)提供无链接服务。不提供无错保证,数据可能丢失或重复,而且接收顺序混乱。原始套接字(SOCK_RAW)