大小端、字节顺序转换函数和IP地址格式转换函数

大小端

CPU大小端之分

大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中(高存低,低存高)

小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中(高存高,低存低)

假如32位宽(uint32_t)的数据0x12345678,从地址0x08004000开始存放:
在这里插入图片描述
再结合一张图进行理解:
在这里插入图片描述

常见字节序

常见的操作系统是小端,通讯协议是大端。

常见CPU的字节序
大端模式:PowerPC、IBM、Sun
小端模式:x86、DEC

ARM既可以工作在大端模式,也可以工作在小端模式。STM32属于小端模式。

大小端应用的地方很多,如通信协议、数据存储等。如果字节序不一致,就需要转换。这就引出下面网络编程中的四个函数

字节顺序转换函数

ntohs、ntohl、htons和htonl含义如下:
ntohs =net to host short int 16位
htons=host to net short int 16位
ntohl =net to host long int 32位
htonl=host to net long int 32位

网络字节顺序NBO(Network Byte Order):
按从高到低的顺序存储,在网络上使用同一的网络字节顺序,可避免兼容性问题(大端模式)

主机字节顺序HBO(Host Byte Order):
不同的机器HBO不相同,与CPU的设计有关,数据的顺序是由CPU决定的,而与操作系统无关;

如Intel x86结构下(小端模式),short型数0x1234表示为34 12,int型数0x12345678表示为78 56 34 12;

如IBM power PC结构下(大端模式),short型数0x1234表示为 12 34,int型数0x12345678表示为 12 34 56 78.

由于这个原因,不同体系结构的机器之间不能直接通信,所以要转换成一种约定的顺序,也就是网络字节顺序,其实就是如同power pc那样的顺序。在PC开发中有ntohl和htonl函数可以用来进行网络字节和主机字节的转换

Linux系统下定义

#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);

Windows系统下

ntohs()

将一个无符号短整形数从网络字节顺序转换为主机字节顺序。

#include <winsock.h>

u_short PASCAL FAR ntohs( u_short netshort);
/*
netshort:一个以网络字节顺序表达的16位数。
注释:本函数将一个16位数由网络字节顺序转换为主机字节顺序。
返回值:ntohs()返回一个以主机字节顺序表达的数。
*/

htons()

将主机的无符号短整形数转换成网络字节顺序。

#include <winsock.h>

u_short PASCAL FAR htons( u_short hostshort);
/*
hostshort:主机字节顺序表达的16位数。
注释:本函数将一个16位数从主机字节顺序转换成网络字节顺序。
返回值:htons()返回一个网络字节顺序的值。
*/

以上2个函数提供了主机字节顺序与网络字节顺序的转换,比如网络字节为00 01,u_short a。如何直接对应,a=0100; 为什么呢?因为主机是从高字节到低字节的,所以应该转化后a=ntohs(0001); 这样 a=0001

htonl()和ntohl()

htonl()表示将32位的主机字节顺序转化为32位的网络字节顺序,htons()表示将16位的主机字节顺序转化为16位的网络字节顺序(ip地址是32位的端口号是16位的)

ntohl()表示将32位的网络字节顺序转化为32位的主机字节顺序,ntohs()表示将16位的网络字节顺序转化为16位的主机字节顺序(ip地址是32位的端口号是16位的)

IP地址格式转换函数

对于人来说,我们更容易阅读的是点分十进制的 IP 地址,譬如 192.168.1.110、192.168.1.50,这其实是一种字符串的形式,但是计算机所需要理解的是二进制形式的 IP 地址,所以我们就需要在点分十进制字符串和二进制地址之间进行转换。

点分十进制字符串和二进制地址之间的转换函数主要有:inet_aton、inet_addr、inet_ntoa、inet_ntop、inet_pton 这五个,在我们的应用程序中使用它们需要包含头文件<sys/socket.h>、<arpa/inet.h>以及<netinet/in.h>。

inet_aton、inet_addr、inet_ntoa函数(已废弃)

这些函数可将一个 IP 地址在点分十进制表示形式和二进制表示形式之间进行转换,这些函数已经废弃了,基本不用这些函数了,但是在一些旧的代码中可能还会看到这些函数。完成此类转换工作我们应该使用下面介绍的这些函数。

inet_ntop、inet_pton函数

inet_ntop()、inet_pton()与 inet_ntoa()、inet_aton()类似,但它们还支持IPv6地址。它们将二进制Ipv4或Ipv6地址转换成以点分十进制表示的字符串形式,或将点分十进制表示的字符串形式转换成二进制Ipv4或Ipv6地址。使用这两个函数只需包含<arpa/inet.h>头文件即可

inet_pton()函数

inet_pton()函数原型如下所示:

int inet_pton(int af, const char *src, void *dst);

inet_pton()函数将点分十进制表示的字符串形式转换成二进制 Ipv4 或 Ipv6 地址。

将字符串 src 转换为二进制地址,参数 af 必须是 AF_INET 或 AF_INET6,AF_INET 表示待转换的 Ipv4地址,AF_INET6 表示待转换的是 Ipv6 地址;并将转换后得到的地址存放在参数 dst 所指向的对象中,如果参数 af 被指定为 AF_INET,则参数 dst 所指对象应该是一个 struct in_addr 结构体的对象;如果参数 af 被指定为 AF_INET6,则参数 dst 所指对象应该是一个 struct in6_addr 结构体的对象。

inet_pton()转换成功返回 1(已成功转换)。如果 src 不包含表示指定地址族中有效网络地址的字符串,则返回 0。如果 af 不包含有效的地址族,则返回-1 并将 errno 设置为 EAFNOSUPPORT。

inet_ntop()函数

inet_ntop()函数执行与 inet_pton()相反的操作,函数原型如下所示:

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数 af 与 inet_pton()函数的 af 参数意义相同。

参数 src 应指向一个 struct in_addr 结构体对象或 struct in6_addr 结构体对象,依据参数 af 而定。函数inet_ntop()会将参数 src 指向的二进制 IP 地址转换为点分十进制形式的字符串,并将字符串存放在参数 dts所指的缓冲区中,参数 size 指定了该缓冲区的大小。

inet_ntop()在成功时会返回 dst 指针。如果 size 的值太小了,那么将会返回 NULL 并将 errno 设置为
ENOSPC。

示例

Linux系统下
tcpclient.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define SERVER_PORT 8888 //服务器的端口号
#define SERVER_IP "192.168.31.200" //服务器的 IP 地址

int main(void)
{
   struct sockaddr_in server_addr = {0};
   char buf[512];
   int sockfd;
   int ret,ret1;
   int a[5]={1,2,3,4,5};

   /* 打开套接字,得到套接字描述符 */
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   if (0 > sockfd)
   {
       perror("socket error");
       exit(EXIT_FAILURE);
   }

   /* 调用 connect 连接远端服务器 */
   server_addr.sin_family = AF_INET;
   server_addr.sin_port = htons(SERVER_PORT); //端口号
   inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);//IP 地址
   ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
  
   if (0 > ret)
     {
       perror("connect error");
       close(sockfd);
       exit(EXIT_FAILURE);
     }
   printf("服务器连接成功...\n\n");
   
   /* 向服务器发送数据 */
   for ( ; ; )
   {
     // 清理缓冲区
     memset(buf, 0x0, sizeof(buf));
     
     printf("开始发数组a[5]\n"); 
     ret1 = send(sockfd, a, sizeof(a), 0);
     if(0 > ret1)
     {
       perror("send error");
       break;
     }
     printf("数组a[5]发送结束\n"); 

     // 接收用户输入的字符串数据
     printf("Please enter a string: ");
     fgets(buf, sizeof(buf), stdin);
     // 将用户输入的数据发送给服务器
     ret = send(sockfd, buf, strlen(buf), 0);
     
     if(0 > ret)
     {
       perror("send error");
       break;
     }
     //输入了"exit",退出循环
     if(0 == strncmp(buf, "exit", 4))
     break;
   }
  
   close(sockfd);
   exit(EXIT_SUCCESS);
}

tcpserver.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define SERVER_PORT 8888 //端口号不能发生冲突,不常用的端口号通常大于 5000

int main(void)
{
   struct sockaddr_in server_addr = {0};
   struct sockaddr_in client_addr = {0};
   char ip_str[20] = {0};
   int sockfd, connfd;
   int addrlen = sizeof(client_addr);
   char recvbuf[512];
   int b[5];
   int ret,ret1;

   /* 打开套接字,得到套接字描述符 */
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   if (0 > sockfd) 
   {
     perror("socket error");
     exit(EXIT_FAILURE);
   }

   /* 将套接字与指定端口号进行绑定 */
   server_addr.sin_family = AF_INET;
   server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
   server_addr.sin_port = htons(SERVER_PORT);
   ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
   if (0 > ret) 
   {
     perror("bind error");
     close(sockfd);
     exit(EXIT_FAILURE);
  }
 
   /* 使服务器进入监听状态 */
   ret = listen(sockfd, 50);
   if (0 > ret) 
   {
     perror("listen error");
     close(sockfd);
     exit(EXIT_FAILURE);
   }
 
   /* 阻塞等待客户端连接 */
   connfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
   if (0 > connfd) 
   {
     perror("accept error");
     close(sockfd);
     exit(EXIT_FAILURE);
   }
   printf("有客户端接入...\n");
   inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str));
   printf("客户端主机的 IP 地址: %s\n", ip_str);
   printf("客户端进程的端口号: %d\n", client_addr.sin_port);
 
   /* 接收客户端发送过来的数据 */
   for ( ; ; ) 
   {
     // 接收缓冲区清零
     memset(recvbuf, 0x0, sizeof(recvbuf));
     
     ret1 = recv(connfd, b, sizeof(b), 0);
     if(0 >= ret1) 
     {
       perror("recv a[5] error");
       close(connfd);
       break;
     }
     printf("from client int: %d,%d,%d\n", b[0],b[1],b[2],b[3],b[4]);

     // 读数据
     ret = recv(connfd, recvbuf, sizeof(recvbuf), 0);
     if(0 >= ret) 
     {
       perror("recv error");
       close(connfd);
       break;
     }
     // 将读取到的数据以字符串形式打印出来
     printf("from client: %s\n", recvbuf);
    
     // 如果读取到"exit"则关闭套接字退出程序
     if (0 == strncmp("exit", recvbuf, 4)) 
     {
      printf("server exit...\n");
      close(connfd);
      break;
     }
   }

   /* 关闭套接字 */
   close(sockfd);
   exit(EXIT_SUCCESS);
}
  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只嵌入式爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值