UNIX网络编程——socket概述和字节序、地址转换函数

一、什么是socket

socket可以看成是用户进程与内核网络协议栈的编程接口。
socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信。


socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及以后要讲的UNIX Domain Socket。然而,各种网络协议的地址格式并不相同,如下图所示:

         

IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,如下所示:

  1. struct sockaddr_in {  
  2.         uint8_t        sin_len; /*length of structure (16)*/  
  3.         sa_family_t    sin_family; /* address family: AF_INET */  
  4.         in_port_t      sin_port;   /* port in network byte order */  
  5.         struct in_addr sin_addr;   /* internet address */  
  6.         char           sin_zero[8]; /* pad bytes,  set to zero is ok */  
  7. };  
  1. struct in_addr{  
  2.         in_addr_t s_addr; /*32-bit IPV4 address*/  
  3. };  

sa_family_t是一个无符号短整型(unsigned short)。in_addr_t数据类型必须是一个至少32位的无符号整数类型,in_port_t必须是一个至少16位的无符号的整数类型。


IPv6地址用sockaddr_in6结构体表示,包括16位端口号、128位IP地址和一些控制字段。UNIX Domain Socket的地址格式定义在sys/un.h中,用sockaddr_un结构体表示。各种socket地址结构体的开头都是相同的,前16位表示整个结构体的长度(并不是所有UNIX的实现都有长度字段,如Linux就没有),后16位表示地址类型。IPv4、IPv6和UNIX Domain Socket的地址类型分别定义为常数AF_INET、AF_INET6、AF_UNIX。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。因此,socket API可以接受各种类型的sockaddr结构体指针做参数,例如bind、accept、connect等函数,这些函数的参数应该设计成void *类型以便接受各种类型的指针,但是sock API的实现早于ANSI C标准化,那时还没有void *类型,因此这些函数的参数都用struct sockaddr *类型表示,即通用地址结构,如下所示:

  1. struct sockaddr {  
  2.         uint8_t      sa_len;  
  3.         sa_family_t  sin_family;  
  4.         char         sa_data[14];  
  5. };   

sin_family:指定该地址家族
sa_data:由sin_family决定它的形式。


在传递参数之前要强制类型转换一下,例如:

struct sockaddr_in servaddr;

/* initialize servaddr *

/bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));


二、网络字节序

字节序
大端字节序(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代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

  1. #include <arpa/inet.h>  
  2. uint32_t htonl(uint32_t hostlong);  
  3. uint16_t htons(uint16_t hostshort);  
  4. uint32_t ntohl(uint32_t netlong);  
  5. uint16_t ntohs(uint16_t netshort);  

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。


下面写个小程序测试下主机的大小端:

  1. #include<stdio.h>  
  2. #include<arpa/inet.h>  
  3.   
  4. int main(void)  
  5. {  
  6.     unsigned int x = 0x12345678;  
  7.     unsigned char *p = (unsigned char *)&x;  
  8.     printf("%x %x %x %x\n", p[0], p[1], p[2], p[3]);  
  9.   
  10.     unsigned int y = htonl(x);  
  11.     p = (unsigned char *)&y;  
  12.     printf("%x %x %x %x\n", p[0], p[1], p[2], p[3]);  
  13.   
  14.     return 0;  
  15. }  

运行结果:

  1. huangcheng@ubuntu:~$ ./a.out  
  2. 78 56 34 12  
  3. 12 34 56 78  

即本主机是小端字节序,而经过htonl 转换后为网络字节序,即大端。


三、地址转换函数

前面提到的 sockaddr_in 结构体中的成员struct in_addr sin_addr表示32位的IP地址。但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换。


字符串转in_addr的函数:

  1. #include <arpa/inet.h>  
  2. int inet_aton(const char *strptr, struct in_addr *addrptr);  
  3. in_addr_t inet_addr(const char *strptr);  
  4. int inet_pton(int family, const char *strptr, void *addrptr);  
注意:转换而成的32位数是网络字节序的。
in_addr转字符串的函数:

  1. char *inet_ntoa(struct in_addr inaddr);  
  2. const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);  
注意:传入的32位数也是网络字节序的。
其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr。


下面写个小程序演示一下:

  1. #include<stdio.h>  
  2. #include<arpa/inet.h>  
  3.   
  4. int main(void)  
  5. {  
  6.   
  7.     unsigned int  addr = inet_addr("192.168.0.100"); //转换后是网络字节序(大端)  
  8.     printf("add=%u\n", ntohl(addr));  
  9.   
  10.     struct in_addr ipaddr;  
  11.     ipaddr.s_addr = addr;  
  12.     printf("%s\n", inet_ntoa(ipaddr));  
  13.   
  14.     return 0;  
  15. }  
运行结果:

  1. huangcheng@ubuntu:~$ ./a.out  
  2. add=3232235620  
  3. 192.168.0.100  

注意,在打印addr的时候先转换成主机字节序,否则输出可能是负数。


四、套接字类型

流式套接字(SOCK_STREAM)
提供面向连接的、可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接收。
数据报式套接字(SOCK_DGRAM)
提供无连接服务。不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。
原始套接字(SOCK_RAW)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值