2021-08-07

一、网络字节序 主机字节序
大端字节序:高地址存储低地址数据
小端字节序:高地址存储高地址数据
如:0X12345678数据地址由高->低
内存中的地址 由低->高
小端:0X78563412 大端:0X12345678
PC机大多为小端字节序–主机字节序
网络通信过程中为小端字节序–网络字节序

//判断机器字节序
#include<stdio.h>
void byteorder(){
	union {
		short value;
		char union_bytes[size0f(short)];
	}test;
	test.value = 0x0102;
	if((test.union_bytes[0] == 1) && test.union_bytes[1] == 2)){
		printf("big endian\n");
	}
	else if((test.union_bytes[0]==2) && test.union_bytes[1] == 1){
	printf("little endian\n");
	}
	else{
	printf("unknown...n");
	}
}

	Linux提供如下四个函数完成主机字节序和网络字节序之间的转换
```c
#include<netinet/in.h>
unsigned long int htol(unsigned long int hostlong); //长整形主机字节序转化为网络字节序
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netlong);

1.1 通用socket地址
在socket网络编程接口中标识socket地址的是结构体sockaddr

#include<bits/socket.h>
struct sockaddr{
	sa_family_t sa_family; //地址类型变量
	char sa_data[14];//存放socket地址值
};

地址类型和协议族类型对应
协议簇 地址簇 描述
PF_UNIX AF_NUIX UNIX本地域协议簇
PF_INET AF_INET TCP/IPv4协议簇
PF_INET6 AF_INET6 TCP/IPv6协议簇

宏PF_*和AF_*都定义在bits/socket.h头文件中,且两者有完全相同的值,所以二者通常混用
由于不同协议簇的地址有不同含义和长度,如:
协议簇 地址含义和长度
PF_UNIX 文件路径名 长度可达108字节
PF_INET 16bit端口号,32bitIPv4地址,共6字节
PF_INET6 16bit端口号,32bit流标识,128bitIPv6地址,32bit范围ID,26字节
因此14字节sa_data无法完全容纳多数协议簇和地址值,因此Linux定义了新的通用socket地址结构体

#include<bits/socket.h>
struct sockaddr_storage{
	sa_family_t sa_family;
	unsigned long int _sa_align;
	char __ss_padding[128-sizeof(__ss_align)];
	//不仅提供足够大的空间用于存放地址值,而且是内存对齐的
};

1.3 专用socket地址
上面的通用socket地址结构体不够友好,设置与获取IP地址和端口就需要进行位运算。
UNIX本地域协议簇专用socket地址结构体:

#include<sys/un.h>
struct sockaddr_un{
	sa_family_t sin_family; //地址簇 AF_UNIX
	char sun_path[108]; //文件路径
};

TCP/IP协议簇有sockaddr_in和sockaddr_in6两个专用socket地址结构体

struct sockaddr_in{
	sa_family_t sin_family; //地址簇AF_INET
	u_int16_t sin_port; //端口号,要用网络字节序标识
	struct in_addr sin_addr; //IPv4地址结构体
};
struct in_addr{
	u_int32_t s_addr; //IPv4地址 要用网络字节序标识
};
struct sockadddr_in6{
	sa_family_t sin6_family; //地址簇 AF_INET6
	u_int16_t sin6_port; //端口号 用网络字节序表示
	u_int32_t sin6_flowinfo; //流信息 设置为0
	struct in6_addr sin6_addr; //IPv6地址结构体
	u_int32_t sin6_scope_id; //scope ID处于试验阶段ruct
};
struct in6_addr{
	unsigned char sa_addr[16]; //IPV6地址 要用网络字节序
};

所有专用socket地址(以及sockaddr_storage)类型的变量在世纪使用时都需要转化为通用socket地址类型sockaddr(强制转换即可),因为所有socket编程接口使用的地址参数的类型都是sockaddr.

1.4 IP地址转换函数
通常,人们习惯用可读性好的字符串来表示IP地址,比如用点分十进制字符串表示IPv4地址,以及用十 六进制字符串表示IPv6地址。但编程中我们需要先把它们转化为整数(二进制数)方能使用。而记录日志 时则相反,我们要把整数表示的IP地址转化为可读的字符串。下面3个函数可用于用点分十进制字符串表示 的IPv4地址和用网络字节序整数表示的IPv4地址之间的转换:

#include<arpa/inet.h>
int_addr_t inet_addr(const char * strptr);
int inet_aton(const char*cp, struct in_addr* inp);
char *inet_ntoa(struct in_addr in);

inet_addr函数用于点分十进制字符串表示的IPv4地址转化为网络字节序整数表示的IPv4,它失败时返回INADDR_NONE。
inet_aton函数完成和inet_addr同样的功能,但是转化结果存储于参数inp指向的地址结构中,它成功返回1,失败返回0;
inet_ntoa函数将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址。但需要 注意的是,该函数内部用一个静态变量存储转化结果,函数的返回值指向该静态内存,因此inet_ntoa是不可 重入的。代码清单5-2揭示了其不可重入性

//不可重入
char*szValue1=inet_ntoa(1.2.3.4); 
char*szValue2=inet_ntoa(10.194.71.60); 
printf(“address 1:%s\n”,szValue1); 
printf(“address 2:%s\n”,szValue2);
//结果
//address1:10.194.71.60 
//address2:10.194.71.60

下面这对更新的函数也能完成和前面3个函数同样的功能,并且它们同时适用于IPv4地址和IPv6地址:

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

inet_pton函数将用字符串表示的IP地址src(用点分十进制字符串表示的IPv4地址或用十六进制字符串表 示的IPv6地址)转换成用网络字节序整数表示的IP地址,并把转换结果存储于dst指向的内存中。其中,af参 数指定地址族,可以是AF_INET或者AF_INET6。inet_pton成功时返回1,失败则返回0并设置errno[1]。 inet_ntop函数进行相反的转换,前三个参数的含义与inet_pton的参数相同,最后一个参数cnt指定目标存 储单元的大小。下面的两个宏能帮助我们指定这个大小(分别用于IPv4和IPv6):

#include<netinet/in.h> 
#define INET_ADDRSTRLEN 16 
#define INET6_ADDRSTRLEN 46

2.2 创建socket
在UNIC/Linux下所有的东西都是文件,socket也一样,就是可读可写可控制可关闭的文件描述符,下面使用socket系统调用可创建一个socket

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);

domain参数告诉系统使用哪个底层协议族。对TCP/IP协议族而言,该参数应该设置为 PF_INET(Protocol Family of Internet,用于IPv4)或PF_INET6(用于IPv6);对于UNIX本地域协议族而 言,该参数应该设置为PF_UNIX。
type参数指定服务类型。服务类型主要有SOCK_STREAM服务(流服务)和SOCK_UGRAM(数据 报)服务。对TCP/IP协议族而言,其值取SOCK_STREAM表示传输层使用TCP协议,取SOCK_DGRAM表 示传输层使用UDP协议。
值得指出的是,自Linux内核版本2.6.17起,type参数可以接受上述服务类型与下面两个重要的标志相与 的值:SOCK_NONBLOCK和SOCK_CLOEXEC。它们分别表示将新创建的socket设为非阻塞的,以及用fork 调用创建子进程时在子进程中关闭该socket。在内核版本2.6.17之前的Linux中,文件描述符的这两个属性都 需要使用额外的系统调用(比如fcntl)来设置。
protocol参数是在前两个参数构成的协议集合下,再选择一个具体的协议。不过这个值通常都是唯一的 (前两个参数已经完全决定了它的值)。几乎在所有情况下,我们都应该把它设置为0,表示使用默认协 议。
socket系统调用成功时返回一个socket文件描述符,失败则返回-1并设置errno。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值