本章将介绍IPV4地址协议编程

SOCKADDR_IN结构体

Winsock中,将IP地址和端口号指定到sockaddr_in数据结构中

  1. typedef struct sockaddr_in {
  2. SHORT sin_family; // 使用IP地址族时需要指定为AF_INET
  3. USHORT sin_port; // 端口号
  4. IN_ADDR sin_addr; // IP地址
  5. CHAR sin_zero[8]; // 用于补齐,使结构体大小与SOCKADDR相等
  6. } SOCKADDR_IN, *PSOCKADDR_IN;

另一种数据结构

  1. typedef struct sockaddr {
  2. u_short sa_family;
  3. CHAR sa_data[14]; // 14字节的协议地址
  4. } SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;

SOCKADDR_IN 与 SOCKADDR 的区别

SOCKADDR_IN中的sin_zero[8]用于将其大小补到与SOCKADDR相等,可以用bzero()或memset()函数将其置为零,两者可以混用,只需要在使用时用指针进行转换时即可.
指向sockaddr_in 的指针和指向sockaddr的指针可以相互转换,这意味着如果一个函数所需参数类型是sockaddr时,你可以在函数调用的时候将一个指向 sockaddr_in的指针显示转换为指向sockaddr的指针;或者相反。

你只要记住,填值的时候使用sockaddr_in结构,而作为函数的参数传入的时候转换成sockaddr结构就行了,毕竟都是16个字符长。

IP地址结构体

  1. typedef struct in_addr {
  2. union {
  3. struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b;
  4. struct { USHORT s_w1,s_w2; } S_un_w;
  5. ULONG S_addr;
  6. } S_un;
  7. #define s_addr S_un.S_addr // can be used for most tcp & ip code
  8. #define s_host S_un.S_un_b.s_b2
  9. #define s_net S_un.S_un_b.s_b1
  10. #define s_imp S_un.S_un_w.s_w2
  11. #define s_impno S_un.S_un_b.s_b4
  12. #define s_lh S_un.S_un_b.s_b3
  13. } IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;

可以简单地认为该结构体是一个四字节的地址,无非是使用联合使之能够被更方便的使用.
大多数情况下使用 s_addr ,即联合中的最后一种形式来赋值.
可以使用下列函数方便地将带点的IP地址转换为4字节形式.

  1. unsigned long inet_addr(
  2. const char FAR * cp; // 参数cp为不带结束符用点隔开的地址字符串,比如 "127.0.0.1"
  3. );

字节顺序

不同机器的字节顺序不同,有大端小端两种.
大端字节序:高字节数据存放在内存低地址处,低字节数据存放在内存高地址处.
小端字节序:低字节数据存放在内存低地址处,高字节数据存放在内存高地址处.

在网络编程中,存在主机字节序(host-byte order)和 网络字节序(network-byte order).
当一个IP地址和端口号以多字节形式存在于计算机中时,它们是主机字节序.
当存在于网络中时,它们是网络字节序.
网络标准规定网络中的多字节值必须以大端字节序存在,因此需要做相应转换.

接下来的几个函数用于字节序的转换
主机字节序转换为网络字节序

  1. u_long htonl(u_long hostlong); // unsigned long形字节转换,返回值为转换结果
  2. int WSAHtonl(
  3. SOCKET s,
  4. u_long hostlong, // 主机字节序的 4 字节数据
  5. u_long FAR *lpnetlong // 转换结果保存到lpnetlong指向的内存中
  6. );
  7. u_short htons(u_short hostshort); // unsigned short形字节转换,返回值为转换结果
  8. int WSAHtons(
  9. SOCKET s,
  10. u_short hostshort, // 主机字节序的 2 字节数据
  11. u_short FAR *lpnetshort // 转换结果保存到lpnetlong指向的内存中
  12. );

相反的,网络字节序转换为主机字节序

  1. u_long ntohl(u_long netlong);
  2. WSANtohl(
  3. SOCKET s,
  4. u_long netlong,
  5. u_long FAR *lphostlong
  6. );
  7. u_short ntohs(u_short netshort);
  8. WSANtohs(
  9. SOCKET s,
  10. u_short netshort,
  11. u_short FAR *lphostshort
  12. );

综上,我们已经知道如何填写一个 IPV4 地址了,代码如下

  1. SOCKADDR_IN InternetAddr; // 存放IPV4地址的数据结构
  2. INT nPortID=5150; // 端口号,这里随意指定为5150
  3. InternetAddr.sin_family=AF_INET; // 使用IP地址族时需要指定为AF_INET
  4. InternetAddr.sin_addr.s_addr=inet_addr("136.149.3.29"); // 将IP地址"136.149.3.29"转换为4字节形式
  5. InternetAddr.sin_port=htons(nPortID); // 将端口号从主机字节序转换为网络字节序,这里参数做了隐式转换int->unsigned short

一般数字形式的IP不容易记忆,因此有一系列跟IP地址获取相关的函数,这在后面将会详细介绍
现在你已经有了IPV4地址协议的基本概念,你已经可以通过创建一个socket来写一个通讯了