1. 套接字与主机信息
1. 1 IP地址
IP地址是计算机进行网络连接和网络传输的必要资源。IP地址分为两类:
- IPv4:4字节IP地址
- IPv6:16字节IP地址
目前虽然各种软件都已经支持IPv6地址族,但是我们使用最多的依旧是IPv4地址族,下面我们只讲IPv4地址。
IP地址分为两部分:网络ID与主机ID,网络ID用来区分局域网,主机ID用来区分局域网内的主机。
根据网络ID所占字节数的不同,IP地址可以分为以下几类:
- A类地址:网络ID占1字节,主机ID占3字节
- B类地址:网络ID占2字节,主机ID占2字节
- C类地址:网络ID占3字节,主机ID占1字节
- D类地址:网络ID占4字节,为多播IP地址
1.2 端口号
在互联网中计算机使用IP地址唯一标识自己,那么运行于计算机上的各种应用程序如何标识自己呢?答案就是端口号。
端口号为16位无符号整型,取值范围为0~65535,典型端口取值范围为0~1023(用于一些典型应用:HTTP、FTP等)。
有以下注意事项:
- 不同应用程序使用的端口号不能重复
- TCP应用程序与UDP应用程序可以使用相同的端口号
1.3 网络编程中地址信息
在socket编程中,地址信息通常会绑定到对应的套接字上去,因此有以下结构: ```cpp // 地址信息 struct sockaddr_in { sa_family_t sin_family; // IP地址协议族 AF_INET AF_INET6 AF_LOCAL uint16_t sin_port; // 端口信息 struct in_addr sin_addr; // IP地址信息 char sin_zero[8]; // 填充字符 };
struct in_addr { in_addr_t s_addr; // IP地址信息 };
地址信息绑定到套接字上时,会采用另外的结构
```cpp
struct sockaddr
{
sa_family_t sin_family;
char sa_data[14];
}
2. 字节序
不同的CPU中,字节的存储顺序是不一致的,有的CPU会将数据的高位存储在内存的高位上,有的cpu则会将数据的低位存储在内存的高位上。由此便诞生了“大端序”与“小端序”。
2.1 大端序与小端序
下面为大端序与小端序的解释:详细见https://www.yuque.com/nullptr-d5qbl/pu03yc/eyavdm
- 大端序:数据低位位于内存高地址上
- 小端序:数据高位位于内存高地址上
那么在两台字节序不同的主机间传输数据,会发生什么呢?
。
难道我们每次发送数据都要手动进行转换?答案是否定的,我们仅需要将IP地址、端口信息进行转换,传输数据的转换操作系统会帮我们搞定的。
2.2 转换API
unsigned short ntohs(unsigned short); // 网络序 ---> 主机序
unsigned short htons(unsigned short); // 主机序 ---> 网络序
unsigned long ntohl(unsigned long); // 网络序 ---> 主机序
unsigned long htonl(unsigned long); // 主机序 ---> 网络序
3. 网络地址初始化
基于上述两个章节,我们已经明白了网络编程中 网络地址的结构体、IP地址和端口号的字节序,我们可以进行网络地址的初始化工作了。
3.1 IP地址转换
日常生活中,我们接触到的一般都是点分十进制的IP地址,例如:192.168.1.1,但struct sockaddr_in
结构中IP地址为无符号32位整形
,因此需要转换。
下面为常用的转换函数:
#include <arpa/inet.h>
/**
* @param[in] string 点分十进制IP地址字符串
* @return IP地址
*/
in_addr_t inet_addr(const char *string);
/**
* @param[in] string 点分十进制IP地址字符串
* @param[in] addr 网络地址结构中的IP地址
* @return 1:成功 0:失败
*/
int inet_aton(const char *string, struct in_addr *addr);
/**
* @param[in] addr 网络地址结构中的IP地址
* @return 点分十进制IP地址字符串
* @note 函数会申请内存保存字符串,因此不要连续两次调用,会覆盖掉第一次的值
*/
char *inet_ntoa(struct in_addr addr);
3.2 实例程序
#include <iostream>
#include <arpa/inet.h>
int main()
{
int sock_serv;
char serv_ip[] = "192.168.1.1";
char serv_port = "9090";
// 初始化套接字
sock_serv = socket(PF_INET, SOCK_STREAM, 0);
// 填充网络地址信息
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(atoi(serv_port));
// serv_sddr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_sddr.sin_addr.s_addr = inet_addr(serv_ip);
// 绑定套接字
bind(sock_addr, (sockaddr *)&serv_addr, sizeof(serv_addr));
}