selph
selph
Published on 2022-02-12 / 371 Visits
0
0

TCPIP网络编程ch03--地址族与数据序列

分配给套接字的IP地址和端口

IP:Internet Protocol(网络协议),为了收发数据而分配给计算机的值

IP地址也叫网络地址,分为两类:IPv4(4字节)和IPv6(16字节)

IPv4地址分为A、B、C、D类,前面固定的是网络地址,后面可变的是主机地址,数据会先传到网络里,网络(交换机、路由器)接收到数据会根据主机地址传给指定计算机

IPv4分类首位首字节范围网络地址字节数主机地址字节数
A类地址00-12713
B类地址10128-19122
C类地址110192-22331

端口号是为了区分程序中创建的套接字而分配给套接字的序号

端口号用于区分应用程序,是在同一操作系统内为区分不同套接字而设置的,一个端口号只能有一个套接字,TCP套接字和UDP套接字不会共用端口号

端口号由16位组成,范围是0-65535,其中0-1023是分配给特定程序的

地址信息的表示

IP地址和端口号在应用程序中以结构体的形式给出定义

Linux 套接字地址结构体:

typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;
  };

struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);	/*协议族 sin_family Address Family */
    in_port_t sin_port;			/*16位端口号   Port number.  */
    struct in_addr sin_addr;	/*32位IP地址 Internet address.  */
	// 下面这个不使用
    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr)
			   - __SOCKADDR_COMMON_SIZE
			   - sizeof (in_port_t)
			   - sizeof (struct in_addr)];
  };
  • sin_family:保存协议族,常见有AF_INET,AF_INET6,AF_LOCAL(UNIX协议地址族)
  • sin_port:端口号,以网络字节序保存
  • sin_addr:IP地址,以网络字节序保存,当作32位整数就行
  • sin_zero:无特殊含义,为了让sockaddr_in和sockaddr结构体大小相同而插入的成员

绑定地址的时候使用bind函数:

sockaddr_in serv_addr;
...
if(bind(serv_sock,(const sockaddr *)&serv_addr,sizeof(serv_addr)) == -1){   

这里的第二个参数需要的是sockaddr的值,sockaddr结构体如下:

struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);	/* Common data: address family and length.  */
    char sa_data[14];		/* Address data.  */
  };

因为直接向sockaddr填入有些麻烦,所以通过sockaddr_in来进行转换

网络字节序与地址转换

数据在内存中有两种保存/解析方式:

  • 大端序:高位字节存放到低位
  • 小端序:高位字节存放到高位
数据内存大端序内存小端序
0x123456780x12 0x34 0x56 0x780x78 0x56 0x34 0x12

Intel和amd系列CPU以小端序方式保存数据

因为有两种解析方式,所以网络传输约定统一使用网络字节序--大端序,小端序系统传输数据时应将数据转化位大端序排序方式

字节序转换函数:htons,ntohs,htonl,ntohl,函数名是h,n,to,l,s字符的组合:

  • h表示主机字节序
  • n表示网络字节序
  • s指的是short类型(2字节,用于端口号转换)
  • l指的是long类型(4字节,用于IP地址转换)

例如:htons:把short类型数据从主机字节序转化为网络字节序

数据收发过程中两种字节序会自动进行转换,只有填充sockaddr_in结构体的时候需要手动转换一下

网络地址的初始化和分配

将字符串信息转换成网络字节序的整数型,sockaddr_in里保存地址信息的成员是32位整数型,有个函数可以帮助进行转换:

in_addr_t inet_addr (const char *__cp);
int inet_aton (const char *__cp, struct in_addr *__inp);	//成功返回1 失败返回0
char *inet_ntoa (struct in_addr __in);

其中inet_addr仅仅是转换字节序,inet_aton功能相同,不过可以直接把返回值返回到in_addr结构体里(参数里返回),inet_ntoa和inet_aton功能相反,将网络字节序地址转换成字符串形式

套接字常见的网络地址初始化方法:

struct sockaddr_in addr = {0};
char* serv_ip = "123.112.34.1";
char* serv_port = "1080";
addr.sin_family = AF_INET;		// 指定IPv4
addr.sin_addr.s_addr = inet_addr(serv_ip);	// 基于字符串的IP地址初始化,也可以使用INADDR_ANY表示全部IP地址
addr.sin_port = htons(aoti(serv_port));		// 基于字符串的port初始化

基于Windows的实现

Windows的htons和htonl的用法与Linux一样

Windows中转换IP字节序只有inet_addr和inet_ntoa,用法与Linux一样

向套接字分配网络地址的过程和Linux相同,bind函数用法是一样的


在Winsock2中新增了两个转换函数:WSAStringToAddress和WSAAddressToString

功能上与inet_ntoa和inet_addr完全相同,但支持多种协议,IPv4和IPv6都适用

使用和Linux相同的一组函数处理网络地址更方便在Windows和Linux上移植


Comment