《TCP IP网络编程》第三章

这篇具有很好参考价值的文章主要介绍了《TCP IP网络编程》第三章。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

2023.7.10


第3章、地址族与数据序列

3.1 分配给套接字的IP地址与端口号

        IP 是 Internet Protocol(网络协议)的简写,是为收发网络数据而分配给计算机的值。端口号并非赋予计算机的值,而是为了区分程序中创建的套接字而分配给套接字的序号。     

        为使计算机连接到网络并收发数据,必须为其分配 IP 地址。IP 地址分为两类。

- IPV4(Internet Protocol version 4)4 字节地址族
- IPV6(Internet Protocol version 6)6 字节地址族

        两者之间的主要差别是 IP 地址所用的字节数,目前通用的是 IPV4 , IPV6 的普及还需要时间。

        IP地址用于区分计算机,只要有IP地址就能向目标主机传输数据,但是只有这些还不够,我们需要把信息传输给具体的应用程序。                                                                                                          端口号就是在同一操作系统内为区分不同套接字而设置的。 端口号由 16 位构成,可分配的端口号范围是 0~65535 。但是 0~1023 是知名端口,一般分配给特定的应用程序,所以应当分配给此范围之外的值。                                                                                                                                   虽然端口号不能重复,但是 TCP 套接字和 UDP 套接字不会共用端接口号,所以允许重复。           总之,数据传输目标地址同时包含IP地址和端口号,只有这样,数据才会被传输到最终的目的应用程序。

3.2 地址信息的表示

        表示 IPV4 地址的结构体:

struct sockaddr_in
{
    sa_family_t sin_family;  //地址族(Address Family)
    uint16_t sin_port;       //16 位 TCP/UDP 端口号
    struct in_addr sin_addr; //32位 IP 地址
    char sin_zero[8];        //不使用
};

        该结构体中提到的另一个结构体 in_addr 定义如下,它用来存放 32 位IP地址:

struct in_addr
{
    in_addr_t s_addr; //32位IPV4地址
}

        结构体 sockaddr_in 的成员分析:

  • 成员 sin_family:每种协议适用的地址族不同,比如,IPV4 使用 4 字节的地址族,IPV6 使用 16 字节的地址族。
  • 该成员保存 16 位端口号,重点在于,它以网络字节序保存。
  • 该成员保存 32 为IP地址信息,且也以网络字节序保存。
  • 无特殊含义。只是为结构体 sockaddr_in 结构体变量地址值将以如下方式传递给 bind 函数。

3.3 网络字节序与地址转换

        不同的 CPU 中,4 字节整数值1在内存空间保存方式是不同的。

        有些 CPU 这样保存:

00000000 00000000 00000000 00000001

        有些 CPU 这样保存:

00000001 00000000 00000000 00000000

        两种一种是顺序保存,一种是倒序保存 。若不考虑顺序就收发数据容易出问题。

        CPU 保存数据的方式有两种,这意味着 CPU 解析数据的方式也有 2 种:

  • 大端序(Big Endian):高位字节存放到低位地址
  • 小端序(Little Endian):高位字节存放到高位地址

        在通过网络传输数据时必须约定统一的方式,这种约定被称为网络字节序,非常简单,统一为大端序。即,先把数据数组转化成大端序格式再进行网络传输。

        介绍一下帮助转换字节序的函数:

unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

        例如:htons(host to network short):把short型数据从主机字节序转换为网络字节序。

        通常,以s作为后缀的函数中,s代表2字节的short,因此用于端口号转换;以l作为后缀的函数中,l代表4个字节,因此用于IP地址转换。

        实验:

#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
    unsigned short host_port = 0x1234;//保存2个字节的数据
    unsigned short net_port;
    unsigned long host_addr = 0x12345678;//保存4个字节的数据
    unsigned long net_addr;
    //转换为网络字节序
    net_port = htons(host_port); 
    net_addr = htonl(host_addr);

    printf("Host ordered port: %#x \n", host_port);
    printf("Network ordered port: %#x \n", net_port);
    printf("Host ordered address: %#lx \n", host_addr);
    printf("Network ordered address: %#lx \n", net_addr);

    return 0;
}

        实验结果:

《TCP IP网络编程》第三章,书籍专栏,网络,tcp/ip,服务器,网络协议

         这是在小端 CPU 的运行结果。大部分人会得到相同的结果,因为 Intel 和 AMD 的 CPU 都是小端序为标准。


3.4 网络地址的初始化与分配

        本节介绍三个函数用于在字符串信息和网络字节序的整数型之间进行转换。

        第一个函数将字符串形式的 IP 地址转换为网络字节序形式的 32 位整数型数据:

#include <arpa/inet.h>
in_addr_t inet_addr(const char *string);
//成功时返回 32 位大端序整数型值,失败时返回 INADDR_NONE

       实验:

#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
    char *addr1 = "1.2.3.4";
    //1个字节能表示的最大整数是255,所以代码中 addr2 是错误的IP地址
    char *addr2 = "1.2.3.256";

    unsigned long conv_addr = inet_addr(addr1);
    if (conv_addr == INADDR_NONE)
        printf("Error occured! \n");
    else
        printf("Network ordered integer addr: %#lx \n", conv_addr);

    conv_addr = inet_addr(addr2);
    if (conv_addr == INADDR_NONE)
        printf("Error occured! \n");
    else
        printf("Network ordered integer addr: %#lx \n", conv_addr);
    return 0;
}

《TCP IP网络编程》第三章,书籍专栏,网络,tcp/ip,服务器,网络协议

         从运行结果看,inet_addr 不仅可以转换地址,还可以检测有效性。

        第二个函数inet_aton与 inet_addr 函数在功能上完全相同,也是将字符串形式的IP地址转换成整数型的IP地址。只不过该函数用了 in_addr 结构体,且使用频率更高。

#include <arpa/inet.h>
int inet_aton(const char *string, struct in_addr *addr);
/*
成功时返回 1 ,失败时返回 0
string: 含有需要转换的IP地址信息的字符串地址值
addr: 保存转换结果的 in_addr 结构体变量的地址值
*/

实验:

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
void error_handling(char *message);

int main(int argc, char *argv[])
{
    char *addr = "127.232.124.79";
    struct sockaddr_in addr_inet;
    //调用inet_aton函数将IPv4地址字符串转换为网络字节序的无符号长整型,并将结果存储在addr_inet.sin_addr的s_addr字段中
    if (!inet_aton(addr, &addr_inet.sin_addr))
        error_handling("Conversion error");
    else
        printf("Network ordered integer addr: %#x \n", addr_inet.sin_addr.s_addr);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

《TCP IP网络编程》第三章,书籍专栏,网络,tcp/ip,服务器,网络协议

         可以看出,已经成功的把转换后的地址放进了 addr_inet.sin_addr.s_addr 中。

        最后一个函数,与 inet_aton() 正好相反,它可以把网络字节序整数型IP地址转换成我们熟悉的字符串形式,函数原型如下:

#include <arpa/inet.h>
char *inet_ntoa(struct in_addr adr);
//成功时返回保存转换结果的字符串地址值,失败时返回 NULL 空指针

ps:返回值为 char 指针,返回字符串地址意味着字符串已经保存在内存空间,但是该函数未向程序员要求分配内存,而是再内部申请了内存保存了字符串。也就是说调用了该函数后要立即把信息复制到其他内存空间。因为,若再次调用 inet_ntoa 函数,则有可能覆盖之前保存的字符串信息。总之,再次调用 inet_ntoa 函数前返回的字符串地址是有效的。若需要长期保存,则应该将字符串复制到其他内存空间。

实验:

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    //定义了两个struct sockaddr_in类型的变量addr1和addr2,以及一个指向字符的指针str_ptr和一个字符数组str_arr。
    struct sockaddr_in addr1, addr2;
    char *str_ptr;
    char str_arr[20];
    //通过调用htonl函数将两个十六进制数0x1020304和0x1010101转换为网络字节序的无符号长整型,并将结果存储在addr1.sin_addr.s_addr和addr2.sin_addr.s_addr中,分别表示两个IPv4地址。
    addr1.sin_addr.s_addr = htonl(0x1020304);
    addr2.sin_addr.s_addr = htonl(0x1010101);
    //把addr1中的结构体信息转换为字符串的IP地址形式
    str_ptr = inet_ntoa(addr1.sin_addr);
    //调用strcpy函数将str_ptr指向的字符串复制到str_arr字符数组中。
    strcpy(str_arr, str_ptr);
    printf("Dotted-Decimal notation1: %s \n", str_ptr);
    //验证了:新的IP地址覆盖了旧IP地址
    inet_ntoa(addr2.sin_addr);
    printf("Dotted-Decimal notation2: %s \n", str_ptr);
    printf("Dotted-Decimal notation3: %s \n", str_arr);
    return 0;
}

《TCP IP网络编程》第三章,书籍专栏,网络,tcp/ip,服务器,网络协议

         

        下面介绍套接字创建过程中,常见的网络信息初始化方法

struct sockaddr_in addr;
char *serv_ip = "211.217,168.13";          //声明IP地址族
char *serv_port = "9190";                  //声明端口号字符串
memset(&addr, 0, sizeof(addr));            //结构体变量 addr 的所有成员初始化为0
addr.sin_family = AF_INET;                 //制定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); //基于字符串的IP地址初始化
addr.sin_port = htons(atoi(serv_port));    //基于字符串的IP地址端口号初始化

        上述网络地址信息初始化过程主要针对服务器端而非客户端。

        请求方法不同意味着调用的函数也不同。服务器端的准备工作通过bind函数完成,而客户端则通过connect函数完成。因此,函数调用前需准备的地址值类型也不同。服务器端声明sockaddr_in结构体变量,将其初始化为赋予服务器端IP和套接字的端口号,然后调用bind函数;而客户端则声明sockaddr_in结构体,并初始化为要与之连接的服务器端套接字的IP和端口号,然后调用connect函数。

习题:

1、通过 IPV4 网络 ID 、主机 ID 及路由器的关系说明公司局域网的计算机传输数据的过程

  1. IP地址分配:每台计算机在公司局域网中被分配了一个唯一的IPv4地址。IPv4地址由网络ID和主机ID组成。网络管理员负责分配IP地址,确保网络ID在公司局域网范围内唯一。

  2. 数据发送:当一台计算机想要向另一台计算机发送数据时,它会创建一个数据包并将目标IP地址设置为接收方计算机的IP地址。数据包还包含发送方计算机的IP地址作为源IP地址。

  3. 路由选择:发送方计算机通过查找其本地的路由表来确定应该将数据包发送到哪个路由器。路由器是网络中的关键设备,负责转发数据包。根据目标IP地址和路由表的匹配规则,发送方计算机选择一个合适的路由器。

  4. 数据包转发:选择的路由器接收到数据包后,它会检查目标IP地址,并根据自身的路由表将数据包转发到下一个目标。这个过程可能涉及多个路由器,每个路由器都负责将数据包传递给下一个目标,直到达到目标计算机所在的局域网。

  5. 目标计算机接收:当数据包到达目标计算机所在的局域网时,该计算机会通过比较目标IP地址和自身的IP地址来判断是否为自己的数据包。如果是,则该计算机接收数据包并进行后续处理。

2、计算机通过路由器和交换机连接到互联网,请说出路由器和交换机的作用。

        路由器表示连接到互联网的网络 ID,用于在不同网络间转发数据。交换机用于组织一个局域网内部的主机,局域网内部的主机可以通过交换机直接通信。如果局域网内的主机想要和其他网络的主机通信,需要通过路由器转发到目的网络,接收到的其他网络发来的数据先通过路由器接收,再由路由器根据主机号转发到交换机寻找具体的主机。

3、请解释大端序,小端序、网络字节序,并说明为何需要网络字节序。

        CPU 向内存保存数据有两种方式,大端序是高位字节存放低位地址,小端序是高位字节存放高位地址,网络字节序是为了方便传输的信息统一性,统一成了大端序。文章来源地址https://www.toymoban.com/news/detail-545317.html

到了这里,关于《TCP IP网络编程》第三章的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 编程的实践理论 第三章 函数理论

    如果我们解释规则的使用,我们总是允许发明新的语法。新的语法的 一个好的来源是名称(标识符),通过公理给出了规则是如何使用的。 通常,当我们介绍名称和公理时,我们要它们实现当前的目的。读者 被假定理解它们的作用域,就是它们的应用范围,在它的范围外不

    2024年02月07日
    浏览(86)
  • 《TCP IP网络编程》

            2023.6.28 正式开始学习网络编程。 每一章每一节的笔记都会记录在博客中以便复习。         网络编程又叫套接字编程。所谓网络编程,就是编写程序使两台连网的计算机相互交换数据。 为什么叫套接字编程? 我们平常将插头插入插座上就能从电网中获取电力,同

    2024年02月11日
    浏览(48)
  • TCP/IP网络编程(一)

    1.1.1 构建打电话套接字 以电话机打电话的方式来理解套接字。 **调用 socket 函数(安装电话机)时进行的对话:**有了电话机才能安装电话,于是就要准备一个电话机,下面函数相当于电话机的套接字。 **调用 bind 函数(分配电话号码)时进行的对话:**套接字同样如此。就想

    2024年02月03日
    浏览(52)
  • TCP/IP网络编程(二)

    本章将讨论如何优雅地断开相互连接的套接字。之前用的方法不够优雅是因为,我们是调用 close 或 closesocket 函数单方面断开连接的。 TCP中的断开连接过程比建立连接过程更重要,因为连接过程中一般不会出现大的变数,但断开过程有可能发生预想不到的情况,因此应准确掌

    2024年02月03日
    浏览(54)
  • TCP/IP网络编程(三)

    多播(Multicast)方式的数据传输是 基于 UDP 完成的 。因此 ,与 UDP 服务器端/客户端的实现方式非常接近。区别在于,UDP 数据传输以单一目标进行,而多播数据 同时传递到加入(注册)特定组的大量主机 。换言之, 采用多播方式时,可以同时向多个主机传递数据 。 14.1.1 多

    2024年02月03日
    浏览(47)
  • CMD与DOS脚本编程【第三章】

    预计更新 第一章. 简介和基础命令 1.1 介绍cmd/dos脚本语言的概念和基本语法 1.2 讲解常用的基础命令和参数,如echo、dir、cd等 第二章. 变量和运算符 2.1 讲解变量和常量的定义和使用方法 2.2 介绍不同类型的运算符和运算规则 第三章. 控制流程和条件语句 3.1 介绍if、else、for、

    2024年02月06日
    浏览(47)
  • 第三章 MATLAB的分支语句和编程设计

            在前面的章节中,我们开发了几个完全运转的 MATLAB 程序。但是这些程序都十分 简单,包括一系列的 MATLAB 语句,这些语句按照固定的顺序一个接一个的执行。像这 样的程序我们称之顺序结构程序。它首先读取输入,然后运算得到所需结果,打印出结 果,并退出。至

    2024年02月05日
    浏览(40)
  • 《Opencv3编程入门》学习笔记—第三章

    记录一下在学习《Opencv3编程入门》这本书时遇到的问题或重要的知识点。 一、图像的载入、显示和输出到文件 (一)OpenCV的命名空间 简单的OpenCV程序标配: (二)Mat类简析 表示从指定路径下把名为dota.jpg的图像载入到Mat类型的srcImage 变量中。 (三)图像的载入与显示概述

    2024年02月08日
    浏览(56)
  • TCP/IP网络编程(一) 理解网络编程和套接字

    网络编程和套接字概要 网络编程就是编写程序使两台联网的计算机相互交换数据 为了与远程计算机进行数据传输,需要连接因特网,而编程种的套接字就是用来连接该网络的工具。 构建套接字 1.调用soecket函数创建套接字 2.调用bind函数给套接字分配地址 3.调用listen函数将套

    2024年02月11日
    浏览(175)
  • 《TCP IP网络编程》第六章

    UDP 套接字的特点:         通过寄信来说明 UDP 的工作原理,这是讲解 UDP 时使用的传统示例,它与 UDP 的特点完全相同。寄信前应先在信封上填好寄信人和收信人的地址,之后贴上邮票放进邮筒即可。当 然,信件的特点使我们无法确认信件是否被收到。邮寄过程中也可能

    2024年02月16日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包