sockaddr_in与sockaddr的区别,以及对网络字节序和主机字节序的理解和转换函数

一、sockaddr

sockaddr在/usr/include/bits/socket.h下,查看sockaddr的结构:

struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.     协议族 */
    char sa_data[14];           /* Address data.  地址+端口号*/
  };

sockaddr的缺陷:sa_data把目标地址和端口信息混在一起了。而sockaddr_in就解决了这一缺陷,将端口号和IP地址分开存储。

二、sockaddr_in

sockaddr_in在/usr/include/netinet/in.h下,
查看sockaddr_in的结构:

struct sockaddr_in
   {
     __SOCKADDR_COMMON (sin_);/* Common data: address family and length.     协议族 */
     in_port_t sin_port;                    /* Port number.  16位端口号*/
    struct in_addr sin_addr;            /* Internet address.  32位IP地址*/

     /* Pad to size of `struct sockaddr'.  用于填充的0字节*/
    unsigned char sin_zero[sizeof (struct sockaddr) -
                            __SOCKADDR_COMMON_SIZE -
                            sizeof (in_port_t) -
                           sizeof (struct in_addr)];
   };

typedef uint32_t in_addr_t;
 struct in_addr
 {
     in_addr_t s_addr;       //32位IPV4地址
  };

 /* Ditto, for IPv6.  */
struct sockaddr_in6
   {
    __SOCKADDR_COMMON (sin6_);
     in_port_t sin6_port;        /* Transport layer port # */
    uint32_t sin6_flowinfo;     /* IPv6 flow information */
     struct in6_addr sin6_addr;  /* IPv6 address */                          
   uint32_t sin6_scope_id;     /* IPv6 scope-id */
   };

三、两者之间的区别与联系

我们来看一下sockaddr_in与sockaddr的结构:
这里写图片描述
联系:二者的占用的内存大小是一致的,因此可以互相转化,从这个意义上说,他们并无区别。
区别:sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。
而sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作。使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。

举个简单的例子:

int main()
 {
     int sock = socket(AF_INET, SOCK_STREAM, 0);//获得fd
     if(sock < 0){                                                           
         printf("create sock error\n");
     }

    struct sockaddr_in my_socket;
    bzero(&server_socket,sizeof(server_socket));//初始化结构体
    my_socket.sin_family = AF_INET;//设置协议家族
    my_socket.sin_addr.s_addr = htonl(INADDR_ANY);//设置IP地址
    my_socket.sin_port = htons(PORT);//设置端口号

    bind(sock, (struct sockaddr*)&server_socket, sizeof(struct soc   kaddr_in));//绑定
...
}

可以看到在初始化类型为sockaddr_in的结构体时,调用了htons函数和inet_addr函数。
htons函数将端口号由主机字节转换为网络字节序的整数值。
inet_addr函数将一个IP字符串转换为一个网络字节序的整数值。

四、网络字节序与主机字节序

1.主机字节序
就是我们平常说的大端和小端模式,大端就是低地址存放高字节,小端就是低地址存放低字节。不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。
2.网络字节序
内存地址有大小端之分,网络数据流同样有大端小端之分。发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存。因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。
举个例子:4个字节的32bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这就是大端字节序。也就是TCP/IP首部中所有的二进制整数在网络中传输时都要求的这种次序。

主机字节序与网络字节序的转换函数

#include <arpa/inet.h>

/*将32位的长整数从主机字节序转换为网络字节序,*/ 
uint32_t htonl(uint32_t hostlong);
 /*将16位的短整数从主机字节序转换为网络字节序,*/
  uint16_t htons(uint16_t hostshort); 
 /*将32位的长整数从网络字节序转换为主机字节序,*/
  uint32_t ntohl(uint32_t netlong); 
 /*将16位的短整数从网络字节序转换为主机字节序,*/ 
 uint16_t ntohs(uint16_t netshort);

这样记忆,h代表host(本地主机),n代表net(网络),l是unsigned long(无符号长整型)。

如果是小端字节序,这些函数就会将参数转换为大端返回,如果是大端字节序,不做转换,直接返回。

我们一般为了简化编程,将IP地址设置为INADDR_ANY,如果需要使用特定的IP地址则需要使用inet_addr 和inet_ntoa完成字符串和in_addr结构体的互换,in_addr是SOCKADDR_IN成员,其代表IP地址。

五、inet_addr函数&inet_nota函数

//inet_addr函数
unsigned long inet_addr(const char* cp);//cp代表点分十进制

//inet_nota函数
char* inet_nota(struct in_addr in);

举个例子:

 SOCKADDR_IN sock;  
 sock.sin_family = AF_INET;  
 //将字符串转换为in_addr类型  
 sock.sin_addr.S_un.S_addr =  inet_addr("192.168.1.111");  
 sock.sin_port = htons(5000);  

 //将in_addr类型转换为字符串  
 printf("inet_ntoa ip = %s\n",inet_ntoa(sock.sin_addr));

结果:inet_ntoa ip =192.168.1.111

猜你喜欢

转载自blog.csdn.net/zwe7616175/article/details/80252048
今日推荐