大小端、字节顺序转换函数和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);
}