IP(网络协议Internet Protocol):为了收发网络数据而给计算机分配的值。
端口号:为了区分程序中创建的套接字而分配给套接字的序号。
网络地址
- IPv4 4字节地址族(目前主要使用)
- IPv6 16字节地址族(为了应对IP地址耗尽而提出的标准,但现在仍未普及)
IPv4
IPv4标准的4字节地址分为网络地址和主机(计算机)地址,且分为A,B,C,D,E类型,E类为已预约地址一般不会使用。
1字节 | 1字节 | 1字节 | 1字节 | 首字节范围 | |
---|---|---|---|---|---|
A类 | 网络ID | 主机ID | 主机ID | 主机ID | 0~127:1.0.0.1—126.155.255.254 |
B类 | 网络ID | 网络ID | 网络ID | 主机ID | 128~191:128.0.0.1—191.255.255.254 |
C类 | 网络ID | 网络ID | 网络ID | 主机ID | 192~223:192.0.0.1—223.255.255.254 |
D类 | 网络ID | 网络ID | 网络ID | 网络ID | (D类不分网络和主机地址,前四位固定为1110) 224.0.0.1—239.255.255.254 |
端口号(port)
端口号用于区分套接字:当数据传输到主机上时,但计算机并不知道数据用于哪一个程序(如何分配套接字)。
端口号由16为组成,可分配端口号为0~65535,但0~1023位知名端口号,一般不使用。
TCP与UDP不会共用端口号:TCP使用了2333端口,UDP仍可使用2333端口,但其他TCP套接字就不可以使用2333端口。
数据传输
浏览IP地址的网络地址 → 将数据传到该网络地址的路由器 → 路由器接收数据后,根据主机地址寻找目标计算机 → 计算机收到数据后,根据端口号将数据进行分配
地址信息的表示
SOCKADDR_IN servAddr;//windows
struct sockaddr_in serv_addr;//Linux
......
bind(ServerSock,(SOCKADDR*)&servAddr,sizeof(servAddr));//windows
bind(serv_sock,(struct sockaddr *)&serv_addr,sizeof(serv_addr));//Linux
在使用bind()填写地址信息可以看到,servAddr是SOCKADDR_IN数据类型,在SOCKADDR_IN内,他保存了:
//Linux
struct sockaddr_in {
short sin_family; // 2 bytes e.g. AF_INET, AF_INET6
unsigned short sin_port; // 2 bytes e.g. htons(3490)
struct in_addr sin_addr; // 4 bytes see struct in_addr, below
char sin_zero[8]; // 8 bytes zero this if you want to use
};
sockaddr用其余14个字节来表示sa_data,而sockaddr_in把14个字节拆分成sin_port, sin_addr和sin_zero.
sockaddr和sockaddr_in包含的数据都是一样的,但他们在使用上有区别:程序员不应操作sockaddr,sockaddr是给操作系统用的,因此我们会在向bind()传参时,对SOCKADDR_IN(sockaddr_in)进行强制类型转换。
网络字节序
- 大端序 高位字节存放到低位地址
- 小端序 高位字节存放到高位地址
当数据传输并进行解析后,因为计算机布偶听的字节序会导致不同的情况:
00000000 00000000 00000000 00000001(大端序)
00000001 00000000 00000000 00000000 (小端序)
字节序转换
- unsigned short htons(unsigned short);
- unsigned short ntohs(unsigned short);
- unsigned short htonl(unsigned long);
- unsigned short ntohl(unsigned long);
从函数名称理解,如:htonl→ 将long整型数据主机字节序(host)转换为网络字节序
为了保证数据的准确传输与解析,必须保证收发机器都为相同的大端序(或将数据进行转换处理)
htons(),htonl()函数(windows)
这两个函数在Linux上并无区别:
unsigned short host_port= 0x1234;
unsigned short net_port;
unsigned long host_addr=0x12345678;
unsigned long net_addr;
//.....
net_port=htons(host_port);//net_port=0x3412
net_addr=htonl(host_addr);//net_addr=0x78563412;
inet_addr()函数(wiodows)
const char* addr="127.212.124.78";
unsigned long conv_addr=inet_addr(addr);//0x4e7cd47f
函数将IP地址转换为32位整数型,且可以检测无效的IP地址。
inet_ntoa()函数(wiodows)
struct sockaddr_in addr
char* strPtr;
addr.sin_addr.s_addr=htonl(0x1020304);
strPtr = inet_ntoa(addr.sin_addr);//1.2.3.4
将一个32位网络字节序的二进制IP地址转换成相应的点分十进制的IP地址(返回点分十进制的字符串在静态内存中的指针)。
代码:
网络地址初始化
//windows
#define ser_port 8888
SOCKADDR_IN servAddr;
char *ser_ip="211.217.168.13"
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;//地址族 IPv4
servAddr.sin_addr.s_addr = htonl(ser_ip);//基于字符串的IP地址初始化
servAddr.sin_port = htons(ser_port);//基于整形数据的端口号初始化
以上代码是windows内常见的网络地址初始化方法。
INADDR_ANY
//....
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
//....
在对ip地址初始化,我们可以使用INADDR_ANY,采用这种方式,会自动获取运行服务器端的计算机的IP地址而不必自己输入,优先考虑这种方式(除非客户端中带有一部分服务器端功能)。
在这里, 127.0.0.1是回送地址(loopback address),指计算机自身的IP地址,第一章中,服务器端和客户端在同一机器上运行,因此都为127.0.0.1。