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地址

下面我们举一个例子说明这两种ID的作用:
捕获.PNG

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地址信息 };

  1. 地址信息绑定到套接字上时,会采用另外的结构
  2. ```cpp
  3. struct sockaddr
  4. {
  5. sa_family_t sin_family;
  6. char sa_data[14];
  7. }

2. 字节序

不同的CPU中,字节的存储顺序是不一致的,有的CPU会将数据的高位存储在内存的高位上,有的cpu则会将数据的低位存储在内存的高位上。由此便诞生了“大端序”与“小端序”。

2.1 大端序与小端序

下面为大端序与小端序的解释:详细见https://www.yuque.com/nullptr-d5qbl/pu03yc/eyavdm

  • 大端序:数据低位位于内存高地址上
  • 小端序:数据高位位于内存高地址上

那么在两台字节序不同的主机间传输数据,会发生什么呢?
捕获.PNG


难道我们每次发送数据都要手动进行转换?答案是否定的,我们仅需要将IP地址、端口信息进行转换,传输数据的转换操作系统会帮我们搞定的。

2.2 转换API

  1. unsigned short ntohs(unsigned short); // 网络序 ---> 主机序
  2. unsigned short htons(unsigned short); // 主机序 ---> 网络序
  3. unsigned long ntohl(unsigned long); // 网络序 ---> 主机序
  4. unsigned long htonl(unsigned long); // 主机序 ---> 网络序

3. 网络地址初始化

基于上述两个章节,我们已经明白了网络编程中 网络地址的结构体、IP地址和端口号的字节序,我们可以进行网络地址的初始化工作了。

3.1 IP地址转换

日常生活中,我们接触到的一般都是点分十进制的IP地址,例如:192.168.1.1,但struct sockaddr_in结构中IP地址为无符号32位整形,因此需要转换。
下面为常用的转换函数:

  1. #include <arpa/inet.h>
  2. /**
  3. * @param[in] string 点分十进制IP地址字符串
  4. * @return IP地址
  5. */
  6. in_addr_t inet_addr(const char *string);
  7. /**
  8. * @param[in] string 点分十进制IP地址字符串
  9. * @param[in] addr 网络地址结构中的IP地址
  10. * @return 1:成功 0:失败
  11. */
  12. int inet_aton(const char *string, struct in_addr *addr);
  13. /**
  14. * @param[in] addr 网络地址结构中的IP地址
  15. * @return 点分十进制IP地址字符串
  16. * @note 函数会申请内存保存字符串,因此不要连续两次调用,会覆盖掉第一次的值
  17. */
  18. char *inet_ntoa(struct in_addr addr);

3.2 实例程序

  1. #include <iostream>
  2. #include <arpa/inet.h>
  3. int main()
  4. {
  5. int sock_serv;
  6. char serv_ip[] = "192.168.1.1";
  7. char serv_port = "9090";
  8. // 初始化套接字
  9. sock_serv = socket(PF_INET, SOCK_STREAM, 0);
  10. // 填充网络地址信息
  11. struct sockaddr_in serv_addr;
  12. memset(&serv_addr, 0, sizeof(serv_addr));
  13. serv_addr.sin_family = AF_INET;
  14. serv_addr.sin_port = htons(atoi(serv_port));
  15. // serv_sddr.sin_addr.s_addr = htonl(INADDR_ANY);
  16. serv_sddr.sin_addr.s_addr = inet_addr(serv_ip);
  17. // 绑定套接字
  18. bind(sock_addr, (sockaddr *)&serv_addr, sizeof(serv_addr));
  19. }