Linux网络编程基础(1)

网络编程预备知识

主机字节序与网络字节序

现代CPU的累加器一次都能至少装载4字节(32位机),即一个整数。这四个字节在内存中的排序影响着它被累加器装载成的整数的值。这就是字节序问题。字节序分为大端字节序和小端字节序。大端字节序表示一个整数的高位地址存储在内存的低地址位上,地位地址存储再内存的高地址处。

网络数据流地址:发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存,因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。例如UDP段格式,地址0-1是16位的源端口号,如果这个端口号是1000(0x3e8),则地址0是0x03,地址1是0xe8,也就是先发0x03,再发0xe8,这16位在发送主机的缓冲区中也应该是低地址存0x03,高地址存0xe8。但是,如果发送主机是小端字节序的,这16位被解释成0xe803,而不是1000。因此,发送主机把1000填到发送缓冲区之前需要做字节序的转换。同样地,接收主机如果是小端字节序的,接到16位的源端口号也要做字节序的转换。如果主机是大端字节序的,发送和接收都不需要做转换。同理,32位的IP地址也要考虑网络字节序和主机字节序的问题。

现代PC大多采用小端字节序,又被称为主机字节序。TCP/IP规定网络数据流采用大端字节序。当格式化的数据在两台使用不同字节序的主机之间传递时,接收端必然错误的解释之。所以在发送前,发送端总要把发送的数据转换成大端字节序,而接收端知道对方传送来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序判断是否对接收的数据进行转换。

可以根据程序判断下主机的字节序
在这里插入图片描述
看下结果:
在这里插入图片描述

Linux提供四个函数来完成主机字节序和网络字节序的转换

#include <netinet/in.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);

htonl 表示“host to network long”,即将长整型(32bit)的主机字节序转换成网络字节序。
其余同理。
这四个函数中长整型用来转换IP地址,短整型用来转换端口号。如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

IP地址转换函数

人们习惯用点分十进制表示IPv4地址或用点分十六进制表示IPv6地址。但在编程中我们需要将它转换为二进制数方便使用,而记录日志时则相反,需要将整数表示的IP地址转换为可读的字符串。

常用同函数:
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

inet_pton用于将字符表示的IP地址src转换成网络字节序整数表示的IP地址,并把结果存储与dst指向的内存中,af参数是指定地址族,为AF_INET或者AF_INET6。函数成功时返回1,失败返回0并设置errno。

inet_pton用于相反的转换,af参数同上,src将网络字节序整数表示的IP地址转换成字符表示的IP地址dst,最后一个参数size表示目标存储单元大小,可用宏表示 #define INET_ADDRSTRLEN 16 或者 INET6_ADDRSTRLEN 46。
成功时返回目标存储单元地址,失败返回NULL并设置errno。

socket地址(数据结构)

在这里插入图片描述
strcut sockaddr 很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

Linux为各个版本族提供了专门的socket地址结构体:

#include <sys/un.h>
//UNIX本地域协议族
struct sockaddr_un
{
	sa_family_t sin_family; //地址族:AF_UNIX
	char sun_path[108];//文件路径名
};

//IPV4
struct sockaddr_in
{
	sa_family_t sin_family;//地址族:AF_UNIX
	u_int16_t sin_port;//端口号,要用网络字节序表示
	struct in_addr sin_addr;
};
struct in_addr
{
	u_int32_t s_addr;//IPV4地址,要用网络字节序表示
};

//IPV6
struct sockaddr_in
{
	sa_family_t sin6_family;//地址族:AF_UNIX6
	u_int16_t sin_port;//端口号,要用网络字节序表示
	u_int1632_t sin6_flowinfo;//流消息,设置为0
	struct in_addr6 sin6_addr;
	u_int32_t sin6_scope_id;//scope ID
};
struct in_addr6
{
	unsigned char sa_addr[16];//IPV6地址,要用网络字节序表示
};

注意所有的专用socket地址类型变量在实际使用时需要转换通用socket地址类型sockaddr(强制转换即可),因为所有socket编程接口使用的地址参数类型都是sockaddr。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值