《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
- 《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
- 分配给套接字的IP地址和端口号
- 网络地址
- 网络地址分类和主机地址边界
- 用于区分套接字的端口号
- 数据传输过程示例
- 地址信息的表示
- 表示IPv4地址的结构体
- 结构体sockaddr_in的成员分析
- 成员sin_family
- 成员sin_port
- 成员sin_addr
- 成员sin_zero
- 网络字节序与地址变换
- 字节序与网络字节序
- 字节序转换
- 网络地址的初始化与分配
- 将字符串信息转换为网络字节序的整数型
- 网络地址初始化
- 客户端地址信息初始化
- INADDR_ANY
- 向套接字分配网络地址
- 基于Windows的实现
- 函数htons、htonl在Windows中的使用
- 函数inet_addr、inet_ntoa在Windows中的使用
- WSAStringToAddress & WSAAddressToString
- 习题
- (1)IP地址族IPv4和IPv6有何区别?在何种背景下诞生了IPv6?
- (2)通过IPV4网络ID、主机ID及路由器的关系说明向公司局域网中的计算机传输数据的过程。
- (3)套接字地址分为IP地址和端口号。为什么需要IP地址和端口号?或者说,通过IP可以区分哪些对象?通过端口号可以区分哪些对象?
- (4)请说明IP地址分类方法,并说出下面这些IP地址的分类。
- (5)计算机通过路由器或交换机连接到互联网。请说出路由器和交换机的作用。
- (6)什么是知名端口?其范围是多少?知名端口中具有代表性的HTTP和FTP端口号各是多少?
- (7)bind函数
- (8)请解释大端序、小端序、网络字节序,并说明为何需要网络字节序。
- (9)大端计算机希望将4字节整型数据12传到小端序计算机。请说出数据传输过程中发生的字节序变换过程。
- (10)怎么表示回送地址?其含义是什么?如果向回送地址传输数据将会发生什么情况?
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
分配给套接字的IP地址和端口号
IP是为收发网络数据而分配给计算机的值。端口号是为区分程序中创建的套接字而分配给套接字的序号。
网络地址
IP地址有两种表达形式:
- IPv4:4字节地址族
- IPv6:16字节地址族
一定要记住IPv4和IPv6不只是在地址长度不同,在其具体的协议实现上是有很大程度的不同的。IPv6出现的主要目的是为了解决由于计算机数量的暴增导致IP地址可能出现不足的问题的,现在IPv6的地址范围可以让地球上任何一个沙子都拥有IP地址。
让我们继续说回到IPv4上,IPv4标准的4字节IP地址分为网络地址和主机(指计算机)地址,且分为A、B、C、D、E等类型。
类型 | 地址范围 | 网络地址位数 | 主机地址位数 | 可分配的网络数量 | 每个网络可分配的主机数量 |
---|---|---|---|---|---|
A | 1.0.0.0 - 126.255.255.255 | 8 | 24 | 128 | 16,777,216 |
B | 128.0.0.0 - 191.255.255.255 | 16 | 16 | 16,384 | 65,536 |
C | 192.0.0.0 - 223.255.255.255 | 24 | 8 | 2,097,152 | 256 |
D | 224.0.0.0 - 239.255.255.255 | 未分配 | 未分配 | 未分配 | 未分配 |
网络地址是用来标识一个特定网络的。它告诉路由器和其他网络设备,数据应该被发送到哪个网络。
主机地址是用来标识网络中具体设备的。它必须在网络中是唯一的,以确保数据能够被正确地发送到正确的设备。
现在我来举个例子来具体理解一下网络地址和主机地址的含义。
假设向WWW.SEMI.COM公司传输数据,该公司内部构建了局域网,把所有计算机连接起来。因此,首先应向SEMI.COM网络传输数据,也就是说,并非一开始就浏览所有4字节IP地址,进而找到目标主机;而是仅浏览4字节IP地址的网络地址,先把数据传到SEMI.COM的网络。SEMI.COM网络(构成网络的路由器)接收到数据后,浏览传输数据的主机地址(主机ID)并将数据传给目标计算机。
网络地址分类和主机地址边界
只需通过地址的第一个字节即可判断网络地址占用的字节数,因为我们根据地址的边界区分网络地址,如下所示:
- A类地址的首字节范围:0~127
- B类地址的首字节范围:128-191
- C类地址的首字节范围:192~223
还有如下这种表述方式:
- A类地址的首位以0开始
- B类地址的前2位以10开始
- C类地址的前3位以110开始
正因如此,通过套接字收发数据时,数据传到网络后即可轻松找到正确的主机。
用于区分套接字的端口号
端口号是计算机为了区分程序中创建的不同套接字,而分配给套接字的序号,由16位组成,端口号唯一,可配分的范围在0~ 65535,其中0~10223是知名端口,一般分配给特定应用程序,所以应当分配范围之外的值。
端口号与套接字是一一对应关系,端口号与程序的不同通信功能是一一对应关系。
TCP套接字和UDP套接字不会共用端口号,所以允许重复。
数据传输过程示例
下面是基于IP地址的数据传输过程图:
主要步骤:
- 主机向203.211.217.202和203.211.172.103传输数据。
- 其中203.211.217和203.211.172是网络ID,通过网络ID可以把数据传输到指定的网络(路由器或交换机)。
- 202和103是主机ID,网络(路由器或交换机)通过主机ID将数据传输到指定的设备上。
- 操作系统收到数据后,根据数据包里的端口号,将数据传输到对应的程序上。
地址信息的表示
应用程序中使用的IP地址和端口号以结构体的形式给出了定义。
表示IPv4地址的结构体
此结构体将作为地址信息传递给bind函数。
struct sockaddr_in
{
sa_family_t sin_family; // 地址族
uint16_t sin_port; // 16位TCO/UDP端口号
struct in_addr sin_addr; // 32位IP地址
char sin_zero[8]; // 不使用
};
该结构体中提到的另一个结构体 in_addr 定义如下,它用来存放32位IP地址。
struct in_addr
{
in_addr_t s_addr; // 32位IPv4地址
};
这些数据类型可以参考 POSIX,它是为 UNIX 系列操作系统设立的标准,它定义了一些其他数据类型,如下表所示:
数据类型名称 | 数据类型说明 | 声明的头文件 |
---|---|---|
int8_t | signed 8-bit int | sys/types.h |
uint8_t | unsigned 8-bit int (unsigned char) | sys/types.h |
int16_t | signed 16-bit int | sys/types.h |
uint16_t | unsigned 16-bit int(unsigned short) | sys/types.h |
int32_t | signed 32-bit int | sys/types.h |
uint32_t | unsigned 32-bit int(unsigned long) | sys/types.h |
sa_family_t | 地址族 | sys/socket.h |
socklen_t | 长度 | sys/socket.h |
in_addr_t | IP地址,声明为uint32_t | netinet/in.h |
in_port_t | 端口号,声明为uint16_t | netinet/in.h |
看到这么长的类型表,有人不禁会问,为什么要搞出这么长的类型名呢?
其中一个很大的原因就是移植性的问题,如果适用于一个32位计算机的代码搬到64位的计算机上运行可定会出现由于位数不同导致的int被解释为不同的字节大小,这种问题是万万不可发生的。因此如果使用int32_t类型的数据,就能保证任何时候都占用4字节,即使转到不同字节的计算机上。
结构体sockaddr_in的成员分析
成员sin_family
每种协议族适用的地址族均不同。比如,IPv4使用4字节地址族,IPv6使用16字节地址族。
地址族(Address Family) | 含义 |
---|---|
AF_INET | IPv4网络协议中使用的地址族 |
AF_INET6 |IPv6网络协议中使用的地址族
AF_LOCAL| 本地通信中采用的UNIX协议的地址族
AF_LOCAL是为了说明具有多种地址族而添加的。
成员sin_port
该成员保存16位端口号,重点在于,它以网络字节序保存。
成员sin_addr
该成员保存32位地址信息,且也以网络字节序保存。为理解好该成员,应同时观察结构体in_addr。但结构体in_addr明为uint32_t,因此只需当作32位整数型即可。
成员sin_zero
无特殊含义。只是为使结构体sockaddr_in的大小和sockaddr结构体保持一致而插入的成员。必需填充为0,否则无法得到想要的结果。后面会另外讲解sockaddr。
从之前介绍的代码也可看出,sockaddr_in结构体变量地址值将以如下方式传递给bind函数。稍后将给出关于bind函数的详细说明,希望各位重点关注参数传递和类型转换部分的代码。
struct sockaddr_in serv_addr;
if(bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error");
此处重要的是第二个参数的传递。实际上,bind函数的第二个参数期望得到sockaddr结构体变量地址值包括地址族、端口号、IP地址等。
struct sockaddr
{
sa_family_t sin_family; // 地址族
char sa_data[14]; // 地址信息
};
这个结构体结构相对于sockaddr_in来说,它将后三个成员都放入sa_data之中。而这对于包含地址信息非常麻烦,继而有了新的结构体sockaddr_in。但是最后还是要转换为sockaddr型的结构体变量,再传递给bind函数即可。
网络字节序与地址变换
字节序与网络字节序
CPU内存保存数据有两种方式:
- 大端序: 高位字节存放到低位地址
- 小端序: 高位字节存放到高位地址
示例:
0x1234567中,0x12是最高位字节,0x67是最低位字节,大端序中先保存最高位。
0x1234567中,0x12是最高位字节,0x67是最低位字节,小端序中先保存最低位。
0x12和0x34构成的大端序系统值与0x34和0x12构成的小端序系统值相同。换言之,只有改变数据保存顺序才能被识别为同一值。
如果大端序系统传输数据0x1234时未考虑字节序问题,而直接以0x12、0x34的顺序发送。结果接收端以小端序方式保存数据,因此小端序接收的数据变成0x3412,而非0x1234,就会出现问题。
正因如此,在通过网络传输数据时约定统一方式,这种约定称为网络字统一节序(Network Byte Order),非常简单——统一为大端序。因此,所有计算机接受数据时应识别该数据时网络字节格式,小端序系统传输数据时应转换为大端序的排列方式。
字节序转换
为了统一标准,在网络传输前,得先把主机数据数组转化为大端序的网络字节序格式,下面是四种转换字节序的函数:
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
s指short,l指long,h指主机(host)字节序,n指网络(network)字节序。
因此,htons指,把short类型数据从主机字节序转换为网络字节序;ntohl指,把long类型数据从网络字节序转换为主机字节序。
示例程序:
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
unsigned short host_port = 0x1234;
unsigned short net_port;
unsigned long host_addr = 0x12345678;
unsigned long net_addr;
net_port = htons(host_port);
net_addr = htonl(host_addr);
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;
}
Intel 和 AMD 系列的 CPU 都采用小端序标准。
网络地址的初始化与分配
将字符串信息转换为网络字节序的整数型
sockaddr_in中保存地址信息的成员为32位整数型。因此,为了分配IP地址,需要将其表示为32位整数型数据。
对于IP地址的表示,我们熟悉的是点分十进制表示法(Dotted Decimal Notation),而非整数型数据表示法。幸运的是,有个函数会帮我们将字符串形式的IP地址转换成32位整数型数据。此函数在转换类型的同时进行网络字节序转换。
#include<arpa/inet.h>
in_addr_t inet_addr(const char *cp);
成功时返回32位大端序整数型值,失败时返回INADDR_NONE。
参数 cp 是一个指向包含 IPv4 地址点分十进制字符串的字符指针,例如 “192.168.1.1”。inet_addr函数会将其转换为32位整数型数据并返回。当然,该整数型值满足网络字节序。另外,该函数的返回值类型in_addr_t在内部声明为32位整数型。下列示例表示该函数的调用过程。
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
char *addr1 = "1.2.3.4";
char *addr2 = "1.2.3.256";
unsigned long conv_addr = inet_addr(addr1);
if (conv_addr == INADDR_NONE)
printf("Error occurred! \n");
else
printf("Network ordered integer addr: %#lx \n", conv_addr);
conv_addr = inet_addr(addr2);
if (conv_addr == INADDR_NONE)
printf("Error occurred! \n");
else
printf("Network ordered integer addr: %#lx \n\n", conv_addr);
return 0;
}
从运行结果可以看出,inet_addr函数不仅可以把IP地址转成32位整数型,而且可以检测无效的IP地址。另外,从输出结果可以验证确实转换为网络字节序。
inet_aton函数与inet_addr函数在功能上完全相同,也将字符串形式IP地址转换为32位网络字节序整数并返回。只不过该函数利用了in_addr结构体,且其使用频率更高。
#include <arpa/inet.h>
int inet_aton(const char * string, struct in_addr * addr);
成功时返回1(true),失败时返回0(false)。
参数:
- string:需转换的IP地址信息的字符串指针
- 将保存转换结果的in_addr结构体变量的指针
通过以下示例来了解inet_aton函数调用过程。
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
void error _handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char *argv[])
{
char *addr = "127.232.124.79";
struct sockaddr_in addr_inet;
if (!inet_aton(addr, &addr_inet.sin_addr))
error _handling("Conversion error");
else
printf("Network ordered integer addr: %#x \n", addr_inet.sin_addr.s_addr);
return 0;
}
下面再介绍一个做的功能完全和上述函数相反的函数:
#include <arpa/inet.h>
char * inet_ntoa(struct in_addr adr);
成功时返回转换的字符串地址值,失败时返回-1。
该函数将通过参数传入的整数型IP地址转换为字符串格式并返回。但调用时需小心,返回值类型为char指针。返回字符串地址意味着字符串已保存到内存空间,但该函数未向程序员要求分配内存,而是在内部申请了内存并保存了字符串。也就是说,调用完该函数后,应立即将字符串信息复制到其他内存空间。因为,若再次调用inet_ntoa函数,则有可能覆盖之前保存的字符串信息。总之,再次调用inet_ntoa函数前返回的字符建地此值是有效的。若需要长期保存,则应将字符串复制到其他内存空间。
下面给出上述函数调用示例:
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
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);
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;
}
网络地址初始化
常用的初始化方法:
struct sockaddr_in addr;
char *serv_ip = "211.217.168.13";
char *serv_port = "9190";
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(serv_ip);
addr.sin_port = htons(atoi(serv_port));
客户端地址信息初始化
客户端声明sockaddr_in结构体,初始化为要与之连接的服务器端套接字的IP和端口号,然后调用connect函数。
INADDR_ANY
每次创建服务器端套接字都要输入IP地址会有些繁琐,此时可如下初始化地址信息。
struct sockaddr_in addr;
// char *serv_ip = "211.217.168.13";
char *serv_port = "9190";
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(serv_port));
与之前方式最大的区别在于,利用常数INADDR_ANY分配服务器端的IP地址。若采用这种方式,则可自动获取运行服务器端的计算机IP地址,不必亲自输入。而且,若同一计算机中已分配多个IP地址(多宿主(Multi-homed)计算机,一般路由器属于这一类),则只要端口号一致就可以从不同IP地址接收数据。因此,服务器端中优先考虑这种方式。而客户端中除非带有一部分服务器端功能,否则不会采用。
向套接字分配网络地址
既然已讨论了sockaddr_in结构体的初始化方法,接下来就把初始化的地址信息分配给套接字。bind函数负责这项操作。
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr * myaddr, socklen_t addrlen);
成功时返回0,失败时返回-1。
参数:
- sockfd:要分配地址信息(IP地址和端口号)的套接字文件描述符。
- myaddr:存有地址信息的结构体变量地址值。
- addrlen:第二个结构体变量的长度。
如果此函数调用成功,则将第二个参数指定的地址信息分配给第一个参数中的相应套接字。
基于Windows的实现
函数htons、htonl在Windows中的使用
这些函数的用法与Linux平台下的使用没有区别。
#include <stdio.h>
#include <winsock2.h>
void ErrorHanding(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char *argv[])
{
WSADATA wsaData;
unsigned short host_port = 0x1234;
unsigned short net_port;
unsigned long host_addr = 0x12345678;
unsigned long net_addr;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHanding("WSAStartup() error!");
net_port = htons(host_port);
net_addr = htonl(host_addr);
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);
WSACleanup();
return 0;
}
运行结果:
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 3>gcc endian_conv_win.c -lwsock32 -o endian_conv
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 3>endian_conv
Host ordered port: 0x1234
Network ordered port: 0x3412
Host ordered address: 0x12345678
Network ordered address: 0x78563412
函数inet_addr、inet_ntoa在Windows中的使用
测试程序:
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
void ErrorHanding(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
int main(int argc, char *argv[])
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHanding("WSAStartup() error!");
/* inet_addr 函数调用示例 */
{
char *addr = "127.212.124.78";
unsigned long conv_addr = inet_addr(addr);
if (conv_addr == INADDR_NONE)
printf("Error occurred! \n");
else
printf("Network ordered integer addr: %#lx \n", conv_addr);
}
/* inet_ntoa 函数调用示例 */
{
struct sockaddr_in addr;
char *strPtr;
char strArr[20];
addr.sin_addr.s_addr = htonl(0x1020304);
strPtr = inet_ntoa(addr.sin_addr);
strcpy(strArr, strPtr);
printf("Dotted-Decimal notation3 %s\n", strArr);
}
WSACleanup();
return 0;
}
Windows中不存在inet_aton函数。
运行结果:
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 3>gcc inet_adrconv_win.c -lwsock32 -o inet_adrconv
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 3>inet_adrconv
Network ordered integer addr: 0x4e7cd47f
Dotted-Decimal notation3 1.2.3.4
与Linux完全相同。下面是示例程序:
SOCKET hSocket;
SOCKADDR_IN serverAddr;
char *serverPort = "9190";
// 创建服务端套接字
hSocket = socket(PF_INET, SOCK_STREAM, 0);
// 地址信息初始化
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(INADDR_ANY);
serverAddr.sin_port = htons(atoi(serverPort));
// 分配地址信息
bind(hServerSock, (struct sockaddr *)&serverAddr, sizeof(serverAddr))
WSAStringToAddress & WSAAddressToString
下面介绍Winsock2中增加的2个转换函数。它们在功能上和inet_ntoa和inet_addr全相同,但优点在于只持多种协议,在IPv4和IPv6中均可适用。当然它们也有缺点,使用inet_ntoa、inet_addr可以很容易地在Linux和Windows之间切换程序。而将要介绍的这2个函数则依赖于特定平台,会降低兼容性。
#include <winsock2.h>
INT WSAStringToAddress(
LPTSTR AddressString, INT AddressFamily, LPWSAPROTOCOL_INFO lpProtocolInfo,
LPSOCKADDR lpAddress, LPINT lpAddressLength
);
成功时返回0,失败时返回SOCKET_ERROR。
参数:
- AddressString:含有IP和端口号的字符串地址值
- AddressFamily:第一个参数中地址所属的地址族信息
- lpProtocolInfo:设置协议提供者(Provider),默认为NULL
- lpAddress:保存地址信息的结构体变量地址值
- lpAddressLength:第四个参数中传递的结构体长度所在的变量地址值
上述函数中新出现的各种类型几乎都是针对默认数据类型的typedef声明。
WSAAddressToString与WSAStringToAddress在功能上正好相反,它将结构体中的地址信息转换成字符串形式。
#include <winsock2.h>
INT WSAAddressToString(
LPSOCKADDR lpsaAddress, DWORD dwAddressLength,
LPWSAPROTOCOL_INFO lpProtocolInfo, LPSTR lpszAddressString,
LPDWORD lpdwAddressStringLength);
成功时返回0,失败时返回 SOCKET_ERROR。
参数:
- lpsaAddress:需要转换的地址信息结构体变量地址值
- dwAddressLength:第一个参数中结构体的长度
- lpProtocolInfo:设置协议提供者,默认为NULL
- lpszAddressString:保存转换结果的字符串地址值
- lpdwAddressStringLength:第四个参数中存有地址信息的字符串长度
以下是这两个函数的示例:
#undef UNICODE
#undef _UNICODE
#include <stdio.h>
#include <winsock2.h>
int main(int argc, char *argv[])
{
char *strAddr = "203.211.218.102:9190";
char strAddrBuf[50];
SOCKADDR_IN servAddr;
int size;
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
size = sizeof(servAddr);
WSAStringToAddress(strAddr, AF_INET, NULL, (SOCKADDR *)&servAddr, &size);
size = sizeof(strAddrBuf);
memset(strAddrBuf, 0, sizeof(strAddrBuf));
WSAAddressToString((SOCKADDR *)&servAddr, sizeof(servAddr), NULL, strAddrBuf, (LPDWORD)&size);
printf("Second conv result: %s \n", strAddrBuf);
WSACleanup();
return 0;
}
注意:WSAAddressToString API 的最后一个参数是 unsigned long 类型的指针,要显式转换。
用命令行编译程序:gcc conv_addr_win.c -lwsock32 -o conv_addr
遇到报错:
C:\WINDOWS\TEMP\ccjFwpSG.o:conv_addr_win.c:(.text+0x7a): undefined reference to `__imp_WSAStringToAddressA'
C:\WINDOWS\TEMP\ccjFwpSG.o:conv_addr_win.c:(.text+0xd2): undefined reference to `__imp_WSAAddressToStringA'
collect2.exe: error: ld returned 1 exit status
错误解释:
这个错误表示链接器无法找到__imp_WSAStringToAddressA函数的定义。WSAStringToAddressA是Windows Sockets API中的一个函数,用于将一个IP地址字符串转换为一个sockaddr结构。在使用了WSAStringToAddressA函数的程序中,如果没有正确地链接Winsock库,就会出现这个错误。
解决方法:
确保你的程序链接了正确的Winsock库。在使用GCC或类似的编译器时,可以通过在编译命令中加入-lws2_32来链接Winsock库。
运行结果:
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 3>gcc conv_addr_win.c -o conv_addr -lws2_32
C:\Users\81228\Documents\Program\TCP IP Project\Chapter 3>conv_addr
Second conv result: 203.211.218.102:9190
习题
(1)IP地址族IPv4和IPv6有何区别?在何种背景下诞生了IPv6?
IPV4是4字节地址族,IPV6是16字节地址族。
IPV6的诞生是为了应对2010年前后IP地址耗尽的问题而提出的标准。
(2)通过IPV4网络ID、主机ID及路由器的关系说明向公司局域网中的计算机传输数据的过程。
向目标主机传输数据,首先向目标IP所属的网络传输数据。此时使用的是IP地址中的网络ID。数据传到路由器,路由器将参照IP地址的主机号查找路由表,从对应的网口发送数据,数据经过路由器转发最终送到目标主机。
(3)套接字地址分为IP地址和端口号。为什么需要IP地址和端口号?或者说,通过IP可以区分哪些对象?通过端口号可以区分哪些对象?
IP地址是为了区分网络上的主机。端口号是区分同一主机下的不同的socket,以确保程序进程都能准确收发数据。
(4)请说明IP地址分类方法,并说出下面这些IP地址的分类。
214.121.212.102 - C类
120.101.122.89 - A类
129.78.102.211 - B类
(5)计算机通过路由器或交换机连接到互联网。请说出路由器和交换机的作用。
路由器:是连接两个或多个网络的硬件设备,在网络间起网关的作用,是读取每一个数据包中的地址然后决定如何传送的专用智能性的网络设备。(作用于网络层,寻址,转发(依靠 IP 地址))
交换机:是一种用于电(光)信号转发的网络设备。(一般作用于链路层,过滤,转发(依靠 MAC 地址))
(6)什么是知名端口?其范围是多少?知名端口中具有代表性的HTTP和FTP端口号各是多少?
知名端口(Well-known PROT)是指预定分配给特定操作的端口。其范围是0~1023。
HTTP:80。
FTP:21。
(7)bind函数
bind函数原型:
int bind(SOCKET s, const struct sockaddr *name, int namelen);
调用:
bind(hServerSock, (struct sockaddr *)&serverAddr, sizeof(serverAddr))
为什么bind中第二个参数是sockaddr,但是传入的是sockaddr_in?
答:
bind函数第二个参数类型sockaddr结构体,很难分配IP地址和端口号的空间大小(因为结构体sockaddr并非只为IPv4设计,所以要兼容),所以sockaddr结构体内将IP地址和端口号合并到一起,用一个成员 sa_data 表示。因此对于不同类型网络的IP地址和PORT号的分配是可先通过sockaddr_in完成,之后再强制转换成sockaddr类型(因为两个结构体的长度相同,都是16字节,强制转换类型时不会丢失字节,也没有多余的字节)。
(8)请解释大端序、小端序、网络字节序,并说明为何需要网络字节序。
小端序是把高位字节存储到高位地址上;大端序是把高位字节存储到低位地址上。因为保存栈的方式有差异,所以对网络传输数据的过程制定了标准,这就是“网路字节序”。而且,在网络字节序中,数据传输的标准是“大端序”。
(9)大端计算机希望将4字节整型数据12传到小端序计算机。请说出数据传输过程中发生的字节序变换过程。
因为网络字节序的顺序标准是“大端序”,所以大端序的计算机在网络传输中不需要先转换字节顺序,直接传输。但是接受数据的是小端序计算机,因此,要经过网络转本地序的过程,再保存到存储设备上。
(10)怎么表示回送地址?其含义是什么?如果向回送地址传输数据将会发生什么情况?
127.0.0.1 / localhost。
回送地址表示计算机本身。
因此,如果将数据传送到IP地址127.0.0.1,数据不进行网络传输而是直接返回。