一、什么是socket?
- socket可以看作是用户进程与内核网络协议栈的编程接口。
- socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信。
二、IPv4套接口地址结构
1 struct sockaddr_in
2 {
3 uint8_t sin_len;
4 sa_family_t sin_fimily; //AF_INET
5 n_port_t sin_port; //16-bit TCP or UDP port number
6 //Network Byte Ordered
7 struct in_addr sin_addr; //32_bit IPv4 address
8 //Network byte ordered
9 char sin_zero[8];
10 };
- IPv4套接口地址结构通常也称为“网际套接字地址结构”,它以“sockaddr_in”命名,定义在头文件<.netinet/in.h>中
- sin_len:整个sockaddr_in结构体长度,在4.3BSD-Reno版本之前的第一个成员是sin_family
- sin_family:指定该地址家族,在这里必须设为AF_INET
- sin_port:端口
- sin_addr:IPv4的地址
- sin_zero:暂不使用,一般将其设置为0
1 struct in_addr
2 {
3 in_addr_t s_addr; //32bit IP address
4 };
IPv4两种使用方法:serv.sin_addr表示的是struct in_addr结构;而serv.sin_addr.in_addr表示的是一个uint32整数。
当作为参数传递给任一个套接口函数时,套接口地址结构是通过指针传递的。但是通过指针来取得此参数的函数必须处理来自所支持的任何协议族的套接口地址结构。所以在<sys/socket.h>中定义一个通用地址接口:
当作为参数传递给任一个套接口函数时,套接口地址结构是通过指针传递的。但是通过指针来取得此参数的函数必须处理来自所支持的任何协议族的套接口地址结构。所以在<sys/socket.h>中定义一个通用地址接口:
1 struct sockaddr
2 {
3 uint8_t sa_len;
4 sa_fanily_t sa_family;
5 char sa_data[14];
6 };
IPv6套接口地址结构
以“sockaddr_in6”命名,定义在<netinet/in.h>头文件中
1 struct in6_addr
2 {
3 uint8_t s6_addr[16]; //128-bit IPv6 address
4 };
#define SIN6_LEN //如果系统支持结构中的长度成员,则必须定义SIN6_LEN
1 struct sockaddr_in6
2 {
3 uint8_t sin6_len;
4 sa_family_t sin6_fimily; //AF_INET6
5 in_port_t sin6_port; //16-bit TCP or UDP port number
6 //Network Byte Ordered
7 uint32_t sin6_flowinfo; //priority & flow lable
8
9 struct in6_addr sin6_addr; // IPv6 address
10 //Network byte ordered
11 };
地址转换换书在地址的文本表达和套接口地址结构中的二进制值之间进行转换。
IPv4使用:inet_addr 和 inet_ntoa
IPv4和IPv6都可使用:inet_pton 和 inet_ntop
大小端及网络字节序
“大端”和”小端”表示多字节值的哪一端存储在该值的起始地址处;小端存储在起始地址处,即是小端字节序;大端存储在起始地址处,即是大端字节序;具体的说:
①大端字节序(Big Endian):最高有效位存于最低内存地址处,最低有效位存于最高内存处;
②小端字节序(Little Endian):最高有效位存于最高内存地址,最低有效位存于最低内存处。
如下图:当以不同的存储方式,存储数据为0x12345678时:
判断字节序
可以通过下面的小程序测试自己的机器是大端字节序还是小端字节序
1 #include <stdio.h>
2 union
3 {
4 char ch;
5 int i;
6 }un;
7 int main(void)
8 {
9 un.i = 0x12345678;
10 if(un.ch == 0x12)
11 {
12 printf("big endian
");
13 }
14 else
15 {
16 printf("small endain
");
17 }
18 return 0;
19 }
在测试程序中,使用联合体的原因是:union型数据所占的空间等于其最大的成员所占的空间。对union型成员的存取都是相对于该联合体基地址的偏移量为0处开始,也就是联合体的访问不论对哪个变量的存取都是从union的首地址开始的。通过检测第一个字节存放的数据即可得出结果。
网络字节序
网络上传输的数据都是字节流,对于一个多字节数值,在进行网络传输的时候,先传递哪个字节?也就是说,当接收端收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理,是一个比较有意义的问题;
UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是
发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端法存放的;
所以说,网络字节序是大端字节序;
在实际中,当在两个存储方式不同的主机上传输时,需要借助字节序转换函数。
字节序转换函数
1 #include <arpa/inet.h>
2
3 //将主机字节序转换为网络字节序
4 unit32_t htonl (unit32_t hostlong);
5 unit16_t htons (unit16_t hostshort);
6 //将网络字节序转换为主机字节序
7 unit32_t ntohl (unit32_t netlong);
8 unit16_t ntohs (unit16_t netshort);
9
10 说明:h -----host;n----network ;s------short;l----long。
例如:
1 #include <stdio.h>
2 #include <arpa/inet.h>
3
4 int main()
5 {
6 unsigned int x = 0x12345678;
7 unsigned char *p = (unsigned char *)&x;
8 printf("%0x_%0x_%0x_%0x
",p[0],p[1],p[2],p[3]);
9
10 unsigned int y = htonl(x);
11 p = (unsigned char*)&y;
12 printf("%0x_%0x_%0x_%0x
",p[0],p[1],p[2],p[3]);
13
14 return 0;
15 }
unix网络编程中的地址转换函数
1 #include <arpa/inet.h>
2 int inet_aton(const char *strptr,struct in_addr *addrptr);//返回:若字符串有效则为1,否则为0
3 in_addr_t inet_addr(const char *strptr);//返回:若字符串有效则为32位二进制网络字节序的IPV4地址,否则为INADDR_NONE
4 char *inet_ntoa(struct in_addr inaddr);//返回:指向一个点分十进制串的指针
5 int inet_pton(int family,const char *strptr,void *addrptr);//返回:若成功则为1,若输入无效则为0,出错则为-1
6 const char*inet_ntop(int family,const void *addrptr,char *strptr,size_t len);//返回:若成功则为指向结果的指针。
7 //若出错则为NULL
inet_aton() 函数将 strptr 所指的点分十进制地址串转换成一个32位的网络字节序二进制值并通过addrptr来存储。strptr可以指定为4种形式的字符串:“a.b.c.d”,“a.b.c”,"b,c","a",这四种形式的字符串的最后一个字段都必须能表明右边的2进制地址,例如,如果是"b,c"这种形式的,则b应该要是一个24-bit的值。
inet_addr() 函数将 strptr 转换成32位的网络字节序二进制值并返回。但此函数的strptr无效时返回-1(返回类型为无符号的),所以-1是一个有效地址(255.255.255.255),因此此函数存在一定的问题。
inet_ntoa() 函数将网络字节序二进制值转换为点分十进制串。但是由于它把返回的串存储在静态内存中(即静态区),一旦重复调用此函数,之前返回的串指针指向的值也会改变,即静态区的存储值改变了,因此它是一个不可重入的函数。
inet_pton(), inet_ntop() 函数都需要指定协议族(AF_INET,AF_INET6),因此这两个函数同时支持IPv4,IPv6协议族。如果以不支持的协议族作为参数这两个函数就返回一个错误并将errno设置为EAFNOSUPPORT。inet_pton()将strptr指针所指的字符串转换,并通过addrptr存放结果。inet_ntop则进行相反的转换,len参数是目标存储单元的大小,为了助于指定len的大小,<netinet/in.h>中有两个宏定义:
1 #define INET_ADDRSTRLEN 16
2 #define INET6_ADDRSTRLEN 46
如果len太小,就返回一个空指针,并将errno设置为ENOSPC,inet_ntop函数的strptr参数不可以是一个空指针,调用者必须为目标存储单元分配内存并指定其大小。inet_ntop要求调用者传递一个指向某个二进制地址的指针,而改地址通常包含在一个套接字地址结构中,这就要求调用者必须知道这个结构的格式和地址族,即:
1 //IPv4
2 struct sockaddr_in addr;
3 inet_ntop(AF_INET,&addr.sin_addr,str,sizeof(str));
4
5 //IPv6
6 struct sockaddr_in6 addr6;
7 inet_ntop(AF_INET6,&addr6.sin6_addr,str,sizeof(str));
套接字类型
常用套接字类型
<1>流式套接字(SOCK_STREAM)---TCP
提供面向连接的、可靠的传输服务,数据无差错,无重复的发送,
且按发送顺序接收。
<2>数据报式套接字(SOCK_DGRAM)
提供无连接服务。不提供无差错保证,数据可能丢失或者重复,并且接收顺序混乱。
<3>原始套接字(SOCK_RAW)