《TCP/IP网络编程》第3章知识点汇总

3. 地址族与数据序列(bind())

3.1 IP地址和端口号

3.1.1 IP地址

IP 是 Internet Protocol (网络协议)的简称

为使计算机连接到网络并收发数据,必须向其分配IP地址,IP地址分为2类,IPv4 和 IPv6,分别是4字节地址族和16字节地址族

目前同样的是IPv4标准,它被分为A B C D E 五类。其中E类IP地址以“1111”开始,为将来使用保留。 其中240.0.0.0~255.255.255.254作为保留地址,255.255.255.255作为广播地址。

在这里插入图片描述

网络地址(网络ID) 是为区分网络而设置的一部分IP地址。传输数据到某个ip地址时,首先找到它的网络ID,将数据发送到该网络上(实际上是发送到组成该网络的路由器或交换机上),再根据主机ID将数据转发给目标ip地址

在这里插入图片描述

3.1.2 端口号

计算机运行中可能同时存在许多个套接字,要把接收到的数据传给正确的套接字就需要端口号
通过NIC网卡接收的数据内有端口号,操作系统根据该端口号将数据分配给正确的套接字

计算机中一般配有NIC (Network Interface Card,网络接口卡)数据传输设备,也简称为网卡,负责接收网络上的数据包,通过和自己本身的物理地址(MAC地址)相比较决定是否为本机应接信息。
关于MAC地址和IP地址的解释,可参考这篇文章:https://www.zhihu.com/question/21546408

端口号由16位组成,范围是0 ~ 65535,但0 ~ 1023是Well-known PORT,一般分配给特定应用程序,个人使用时应当避开
UDP和TCP端口号不共通,例如TCP使用了端口号9190,同时允许UDP也使用。

数据传输目标地址要同时包括 IP地址 和 端口号

3.2 地址信息的表示(bind函数)

3.2.1 sockaddr_in

int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
//bind函数用于绑定地址信息,地址用到了结构体 sockaddr_in,然后强转为结构体sockaddr
//serv_addr就是一个sockaddr_in结构体变量
memset(&serv_addr, 0, sizeof(serv_addr));//必须的,将sin_zero初始化为0
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));

sockaddr_in结构体定义如下

struct sockaddr_in
{
    sa_family_t sin_family;//地址族(Address Family),sa_family_t是无符号整数类型
    uint16_t sin_port;//16位TCP/UDP端口号,uint16_t是无符号16位,即unsigned short
    struct in_addr sin_addr;//32位IP地址
    char sin_zero[8];//不使用
};

其中,in_addr结构体如下

struct in_addr
{
    in_addr_t s_addr;//32为IPv4地址,in_addr_t是uint32_t
};

uint16_t,in_addr_t等类型可以参考POSIX(Portable Operating System Interface,可移植操作系统接口)。POSIX是为UNIX系列操作系统设立的标准,它定义了一些数据类型。

在这里插入图片描述

3.2.2 sockaddr_in成员说明和sockaddr

1. sin_family

在这里插入图片描述

socket()中定义的是协议族PF_INET,bind()定义的是地址族AF_INET,都是IPv4中使用的
2. sin_port

保存16位端口号,是以网络字节序保存 对于字符串“9190”,要使用htons(atoi(“9190”))

3. sin_addr

保存32位IP地址,也是以网络字节序保存。它是struct in_addr类型,里面只有一个成员,即 in_addr_t(uint32_t)类型的 s_addr

4. sin_zero

无特殊含义,只是为了使sockaddr_in的大小和sockaddr大小一致,必须填充为0,否则会出错;

struct sockadddr
{
    sa_family_t sin_family;//地址族
    char sa_data[14];//地址信息
}

sa_data包含IP地址和端口号,即32+16位,剩余部分填充0,这是bind函数要求的;sockaddr使用麻烦,因此出现了sockaddr_in,使用时常用sockaddr_in(同样未使用部分填充0),再转为sockaddr

3.3 网络字节序与地址变换

3.2.1 字节序(Order)和网络字节序

一个地址4字节,32bit,0x的1位代表4bit

**大端序:**高位字节存放在低位地址
假设从0x20号开始存储0x12345678,那么 0x20 0x21 0x22 0x23,依次存放 0x12 0x34 0x56 0x78

**小端序:**高位字节存放在高位地址
假设从0x20号开始存储0x12345678,那么 0x20 0x21 0x22 0x23,依次存放 0x78 0x56 0x34 0x12

不同的CPU可采用不同的主机字节序,如Intel系列以小端序保存数据

在这里插入图片描述

大端序和小端序传输数据时会因为存储顺序的变化导致“认知错误”,为了解决这一问题,提出了“网络字节序”。

网络字节序就是约定通过网络传输的数据都为大端序

小端序在发送和接收数据时要经过大端序的转换

3.2.2 字节序转换

指的是主机字节序和网络字节序的转换
n是network,h是host,s是short,l是long,有以下4种转换

htons	主机字节序转为网络字节序,是unsigned short
htonl	主机字节序转为网络字节序,是unsigned long
ntohs	网络字节序转为主机字节序,是unsigned short
ntohl	网络字节序转为主机字节序,是unsigned long

注意,linux系统中long是4字节

测试

#include <stdio.h>
#include <arpa/inet.h>

int main(void)
{
    //x86架构下的linux以小端序存储
    unsigned short host_port = 0x1234;
    unsigned long host_addr = 0x12345678;
    unsigned short net_port;
    unsigned long net_addr;

    net_port = htons(host_port);
    net_addr = htonl(host_addr);

    //#X表示以16进制输出
    printf("Host ordered port: %#x \n", host_port);
	printf("Network ordered port: %#x \n", net_port);
	printf("Host ordered address: %#lx \n", host_addr);
	printf("Network ordered address: %#lx \n", net_addr);

    return 0;
}

在这里插入图片描述

lntel和lAMD系列的CPU都采用小端序标准
除了向sockaddr_in结构体变量填充数据外,其他情况无需考虑字节序问题,会自动转换。

3.4 网络地址的初始化和分配

3.4.1 ip地址转为网络字节序

inet_addr

例如ip地址 127.0.0.1(点分十进制),它是一个字符串形式,而网络字节序是一个32为无符号整型——需要用到inet_addr函数转换

//inet_addr函数:将字符串转为网络字节序
in_addr_t inet_addr(const char* string);//sockaddr_in.sin_addr.s_addr就是in_addr_t类型
//成功时返回32位大端序整数型值,失败时返回INADOR_NONE
//不仅可以用于转换字符串,还可以用来检测无效的IP地址

例如:

"1.2.3.4"	经过inet_addr转换后,得到0x4030201(最高位0未输出)
"1.2.3.256"	经过inet_addr转换后,返回的是INADDR_NONE
inet_aton

inet_aton和inet_addr功能完全一致,只是用到了addr_in结构体,且使用频率更高

#include <arpa/inet.h>
int inet_aton(const char* string, struct in_addr* addr);
//成功时返回1(true) ,失败时返回0(false)

示例

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>

int main(void)
{
    char *addr = "127.232.124.79";
    struct sockaddr_in addr_inet;

    if(!inet_aton(addr, &addr_inet.sin_addr))
        printf("conversion error\n");
    else 
        printf("Network ordered integer addr: %#x \n", addr_inet.sin_addr.s_addr);

    return 0;
}

输出:0x4f7ce87f

inet_ntoa

linux中的,windows没有

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

int main(void)
{
    struct sockaddr_in addr1, addr2;
    char *str_ptr;
    char str_arr[20];

    addr1.sin_addr.s_addr = htonl(0x1020304);
    addr2.sin_addr.s_addr = htonl(0x1010101);

    str_ptr = inet_ntoa(addr1.sin_addr);//用的是struct addr_in结构体
    strcpy(str_arr, str_ptr);
    printf("Dotted-Decimal notation1: %s \n", str_ptr);

    //这里是验证字符串空间被覆盖的问题
    inet_ntoa(addr2.sin_addr);
    printf("Dotted-Decimal notation2: %s \n", str_ptr);
	printf("Dotted-Decimal notation3: %s \n", str_arr);

    return 0;
}

结果:

Dotted-Decimal notation1: 1.2.3.4 //第一次输出str_ptr,正常输出IP地址
Dotted-Decimal notation2: 1.1.1.1 //第二次输出str_ptr,方向str_ptr所指空间已被新的IP地址覆盖
Dotted-Decimal notation3: 1.2.3.4 //输出被拷贝的字符串

注意的是,通过inet_ntoa返回的字符串,一定要实行一次深拷贝。因为下一次使用inet_ntoa将覆盖掉那一块空间

INADDR_ANY

客户端的 addr.sin_addr.s_addr 需要指定具体的IP地址,但服务器端可以使用INADDR_ANY

clnt_addr.sin_addr.s_addr = inet_addr("1.2.3.4")//客户端
addr.sin_addr.s_addr = htonl(INADDR_ANY);//服务器端

INADDR_ANY是一个常数,采用这一方式可以自动获取运行服务器端的计算机IP地址。对于多宿主(拥有多个IP地址)计算机,只要端口号一致,就可以从多个IP地址接收数据

为什么服务器端套接字也需要IP地址:
一台计算机可能有多个IP地址,它和NIC的数量相等。即便是服务器,也要确定是哪一个NIC传来的数据。如果只有一个NIC,就可以使用INADDR_ANY

127.0.0.1是回送地址(loopback address),指的是计算机本身地址。如果将数据传送到IP地址127.0.0.1,数据不进行网络传输而是直接返回

3.4.2 bind() 整体回顾

int serv_sock;
struct sockaddr_in serv_addr;
char *serv_port = "9190";
//1. 创建服务器端套接字(监听套接字)
serv_sock = socket(PF_INET,SOCK_STREAM,0);
//2. 地址信息初始化
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(serv_port));
//3. 分配地址信息
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

bind()函数调用成功,则将第二个参数指定的地址信息分配给第一个参数中的相应套接字

3.5 windows平台

windows下没有inet_aton函数,但inet_addr和inet_ntoa函数使用方法和linux下一致,且不不需要额外引入头文件

Winsock2引入了2个新的转换函数,分别是WSAStringToAddress和WSAAddressToString

1. WSAStringToAddress

将字符串填入结构体中

INT WSAStringToAddress(LPTSTR AddressString, INT AddressFamily, LPWSAPROTOCOL_INFO lpProtocolInfo,
                      LPSOCKADDR lpAddress, LPINT lpAdressLength);
//成功时返回0,失败时返回SOCKET_ERROR

AddressString 含有IP和端口号的字符串地址值。
AddressFamily 第一个参数中地址所属的地址族信息。
lpProtocollnfo 设置协议提供者(Provider ),默认为NULL。
lpAddress 保存地址信息的结构体变量地址值。
lpAddressLength 第四个参数中传递的结构体长度所在的变量地址值。

2. WSAAddressToString

INT WSAAddressToString(LPSOCKADDR lpsaAddress, DWORD dwAddressLength, LPWSAPROTOCOL_INFO lpProtocolInfo,
                      LPSTR lpszAddressString, LPDWORD lpdwAddressStringLength);
//成功时返回0,失败时返回SOCKET_ERROR

lpsaAddress 需要转换的地址信息结构体变量地址值。
dwAddressLength 第一个参数中结构体的长度。
lpProtocolInfo 设置协议提供者(Provider),默认为NULL。
lpszAddressString 保存转换结果的字符串地址值。
lpdwAddressStringLength 第四个参数中存有地址信息的字符串长度。

3. 测试

#undef UNICODE
#undef _UNICODE
/*
#undef用于取消之前定义的宏。根据项目环境, VC++会自主声明这2个宏,这样在WSAStringToAddress和WSAAddressToString函数中,参数就将转换成unicode形式,给出错误的运行结果。所以插入了这2句宏定义。
在vscode中不存在这个问题
*/
#include <stdio.h>
#include <winsock2.h>

int main(void)
{
    char *strAddr = "203.211.218.102:9190";
    char strAddrBuf[50];
    WSADATA wsaData;
    SOCKADDR_IN servAddr;
    int size;

    //1. WSAStartup
    WSAStartup(MAKEWORD(2,2), &wsaData);

    //2. 测试WSAStringToAddress:字符串转SOCKADDR_IN
    //WSAStringToAddress的最后一个参数是LPINT lpAdressLength,LPINT是int *类型
    size = sizeof(servAddr);
    WSAStringToAddress(strAddr, AF_INET, NULL, (SOCKADDR*)&servAddr, &size);
    printf("SOCKADDR_IN.sin_addr.s_addr is %#x; sin_port is %#x\n", servAddr.sin_addr.s_addr, servAddr.sin_port);

    //3. 测试WSAAddressToString:SOCKADDR_IN转字符串
    //WSAAddressToString的最后一个参数是LPDWORD lpdwAddressStringLength,如果传int*可能报错,此时手动转换一下
    size = sizeof(strAddrBuf);
    WSAAddressToString((SOCKADDR*)&servAddr, sizeof(servAddr), NULL, strAddrBuf, (long unsigned int*)&size);
    printf("Address To String is %s\n", strAddrBuf);

    return 0;
}

最终输出

SOCKADDR_IN.sin_addr.s_addr is 0x66dad3cb; sin_port is 0xe623
Address To String is 203.211.218.102:9190

SOCKADDR_IN 和 struct sockaddr_in是一样的:
typedef struct sockaddr_in SOCKADDR_IN;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值