问题:网络编程接口中一些参数的意义是什么?
sock = socket(PF_INET, SOCK_STREAM, 0);

socket 参数详解

int socket(int domain, int type, int protocal);
参数意义
domain套接字中使用的协议族信息
type套接字数据传输类型信息
prorocol设备间通讯使用的协议信息
socket() 中的 domain 参数(协议族)
  • PF_INET → IPv4 互联网协议族
  • PF_INET6 → IPv6 互联网协议族
  • PF_LOCAL → 本地通讯的协议族
  • PF_PACKET → 底层数据收发协议
  • PF_IPX → Novell 专用协议(互联网分组交换协议)
  • ...
注意:不同协议中的地址表现形式可能不同,网络编程时地址类型必须和协议类型匹配
socket() 中的 type 和 protocol 参数
  • type : 用于指定协议类型

    • SOCK_STREAM : 流式数据 (TCP)
    • SOCK_UGRAM : 报文式数据(UDP)
  • protocol :用于指定协议族符合类型的具体协议

    • domain 和 type 几乎可以唯一确定一种协议,因此,这个参数通常为 0
    • 即:0 代表 domain 和 type 指定后的默认协议
关于端口号和 IP 地址
  • 端口号是一个 2 字节数据(无符号)
  • 0 - 1024 作为特定端口被预定义(分配给特定应用程序)
  • IP 地址是一个 4 字节无符号地址族 (可分为 5 类地址)

image.png

深入解析 IP 地址

IP 地址分为 网络标识主机标识 两部分
  • 网络标识:标识网络主机(设备)所在的网络
  • 主机标识:标识网络主机(设备)的具体地址

image.png

问题:一个 IP 地址就 4 个字节,那么如何区分网络标识和主机标识呢?

image.png

  • IP 地址子网掩码 配合使用区分 网络标识 和 主机标识
  • 子网掩码的表现形式也是一个 4 字节的整型数(无符号)
  • 子网掩码用于从 IP 地址中提取 网络标识 (& 操作)

image.png

深入理解子网掩码
设:子网掩码为 M.N.P.Q,则子网可用 IP 地址数量 n = (256 - M) * (256 - N) * (256 - P) * (256 - Q)

例:IP 地址 211.99.34.33,掩码 255.255.255.248,因此:211.99.34.33 所在子网有 8 个 IP 地址
所在子网地址: 211.99.34.32 
广播地址:211.99.34.39
6个可分配地址:211.99.34.33 ... 211.99.34.38

注:子网地址即为网络标识

image.png

ip 地址 211.99.34.33, 掩码 255.255.255.248
可知 211.99.34.33 所在子网有 8 个 IP 地址, 且 8 = 2^3(二的三次方),所以 Y = 32 - 3 = 29
可表示为 211.99.34.33 / 29 【简写形式】

注:29 为32位子网掩码的高位

算一算
IP 地址 192.168.3.44,掩码 255.255.255.0

问:
    子网地址是什么?广播地址是什么?可用地址有多少?简洁表示法是什么?

答:
    子网地址:192.168.3.44
    广播地址:192.168.3.255
    可用地址:254 [256 - 0(子网地址) - 255(广播地址)]
    简洁表示:192.168.3.44 / 24
特殊的地址
  • 0.0.0.0 / 0 - 保留,常用于代表 “缺省网络”
  • 127.0.0.0 / 8 - 回环地址,常用于本地软件会送测试
  • 255.255.255.255 / 32 - 广播地址
网络编程中的地址类型
int sock = 0;
struct sockaddr_in addr = {0};

sock = socket(PF_INET, SOCK_STREAM, 0);  // Protocol Family,协议族

if (sock == -1) {
    pritnf("socket error\n");
    return;
}

addr.sin_family = AF_INET;  // Address Family,地址族
addr.sin_addr.s_addr = inet_addr("192.168.3.241");
addr.sin_addr.port = htons(8899);

if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    printf("connect error\n");
    return -1;
}

问:(struct sockaddr*)&addr 的强制类型转换不会出问题吗?

地址数据类型解析

image.png

  • struct sockaddr 可理解为顶层地址类型父类,其与子类的内存布局相同
  • connect() 根据 addr.sin_family 来判断是哪一种地址类型并进行相应解析

IP 地址相关函数

#include <arpa/inet.h>

函数原型功能描述
in_addr_t inet_addr(const char* strptr);将 IP 字符串转换为符合网络字节序的整数
int inet_aton(const char *cp, struct_addr *inp);将 IP 字符串转换为符合网络字节序的整数,成功返回 1, 失败返回 0
char *inet_ntoa(struct in_addr in)将符合网络字节序的整数地址转换为字符串形式

编程实验:地址函数实验

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>

int main()
{
    unsigned int addr = inet_addr("1.2.3.4");
    struct in_addr addr1 = {0x09080706};
    struct in_addr addr2 = {0x05040302};
    char *s1 = inet_ntoa(addr1);
    char *s1_s = strcpy(malloc(32), s1);
    char *s2 = inet_ntoa(addr2);
    char *s2_s = strcpy(malloc(32), s2);

    printf("addr = %x\n", addr);

    printf("addr1 = %x\n", addr1.s_addr);
    printf("addr2 = %x\n", addr2.s_addr);

    printf("s1 = %s\n", s1);
    printf("s2 = %s\n", s2);               // 注意这里 !!
    printf("s1 == s2 : %d\n", s1 == s2);   // 注意这里 !!

    printf("s1_s = %s\n", s1_s);
    printf("s2_s = %s\n", s2_s);
    printf("s1_s == s2_s : %d\n", s1_s == s2_s);

    if (inet_aton("D.T.Software", &addr1)) {  // 注意这里 !!
        printf("addr1 = %x\n", addr1.s_addr);
    }
    
    free(s1_s);
    free(s2_s);

    return 0;
}

输出

addr = 4030201
addr1 = 9080706
addr2 = 5040302
s1 = 2.3.4.5
s2 = 2.3.4.5   // 注意,转换结果被覆盖 !!
s1 == s2 : 1   // 两次为同一个地址 !!
s1_s = 6.7.8.9
s2_s = 2.3.4.5
s1_s == s2_s : 0

遗留的问题:如何增强服务端能力,同时支持多个客户端?什么是多播?什么是广播?

TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧