1. 1. 1. 计算机网络与因特网
    1. 1.1. 1.1 什么是Internet
      1. 1.1.1. 计算设备
        1. 1.1.1.1. 通信链路
        2. 1.1.1.2. 分组交换机
          1. 1.1.1.2.1. 路由器
          2. 1.1.1.2.2. 链路层交换机
      2. 1.1.2. 协议
      3. 1.1.3. 服务描述
      4. 1.1.4. 协议
    2. 1.2. 1.2 网络边缘
      1. 1.2.0.0.1. 应用进程之间通讯模式:
      2. 1.2.0.0.2. 网络边缘:采用网络设施的面向连接服务
  • 1.3. 1.3网络核心
    1. 1.3.1. 电路交换
      1. 1.3.1.1. 电路交换网络中的复用
        1. 1.3.1.1.1. FDM
        2. 1.3.1.1.2. TDM
      2. 1.3.1.2. 局限性
    2. 1.3.2. 分组交换
      1. 1.3.2.0.1. 存储转发传输
  • 1.3.3. 网络分类
  • 1.4. 1.4接入网和物理媒体
    1. 1.4.1. 接入网类型和环境
      1. 1.4.1.1. 家庭接入
        1. 1.4.1.1.1. DSL因特网接入
        2. 1.4.1.1.2. 电缆因特网接入
        3. 1.4.1.1.3. 光纤到户(FTTH)
        4. 1.4.1.1.4. 拨号和卫星
      2. 1.4.1.2. 企业接入
      3. 1.4.1.3. 广域无线接入
    2. 1.4.2. 物理媒体
  • 1.5. 1.7协议层次和服务模型
    1. 1.5.1. 协议和服务
      1. 1.5.1.1. 层次化方式实现复杂网络功能:
      2. 1.5.1.2. 服务和服务访问点
    2. 1.5.2. 协议栈
      1. 1.5.2.0.1. 应用层:网络应用
      2. 1.5.2.0.2. 传输层:主机之间的数据传输
      3. 1.5.2.0.3. 网络层:为数据报从源到目的的选择路由
      4. 1.5.2.0.4. 链路层:相邻网络节点[网卡?]间的数据传输
      5. 1.5.2.0.5. 物理层:在线路上传送bit
  • 1.5.3. ISO/OSI参考模型
    1. 1.5.3.0.1. 表示层
    2. 1.5.3.0.2. 会话层:
  • 1.5.4. 各个层次的协议数据单元
  • 2. 2.应用层
    1. 2.1. 网络应用程序体系架构
      1. 2.1.0.1. 客户-服务器(C/S)体系架构
      2. 2.1.0.2. 对等体(P2P)体系结构
      3. 2.1.0.3. C/S和P2Peye.com体系结构的混合体
        1. 2.1.0.3.1. Napster
        2. 2.1.0.3.2. 即时通信
  • 2.2. 进程通信
    1. 2.2.0.1. 安全TCP——SSL
  • 2.3. HTTP
    1. 2.3.0.0.1. HTTP概况
    2. 2.3.0.0.2. HTTP连接
    3. 2.3.0.0.3. HTTP请求报文
    4. 2.3.0.0.4. HTTP响应报文
  • 2.4. FTP
    1. 2.4.0.1. 主动模式
    2. 2.4.0.2. 被动模式
  • 2.5. SMTP
    1. 2.5.0.0.1. SMTP协议
  • 2.6. DNS
  • 3. 3.传输层
    1. 3.1. 概述
    2. 3.2. 端口号、复用与分用
      1. 3.2.1. 端口号
      2. 3.2.2. 复用和分用
    3. 3.3. TCP 与 UDP
      1. 3.3.1. 首部
        1. 3.3.1.0.1. UDP
        2. 3.3.1.0.2. TCP
    4. 3.3.2. 对比
  • 3.4. TCP实现原理
    1. 3.4.1. 流量控制
      1. 3.4.1.1. 滑动窗口机制
    2. 3.4.2. 拥塞控制
    3. 3.4.3. 三次握手四次挥手
      1. 3.4.3.1. 握手
      2. 3.4.3.2. 挥手
  • 4. 4. 网络层
    1. 4.1. 概述
      1. 4.1.1. 数据平面和控制平面
    2. 4.2. IPV4
      1. 4.2.1. 编址
        1. 4.2.1.1. 分类编址
        2. 4.2.1.2. 划分子网
      2. 4.2.2. 数据报发送
        1. 4.2.2.1. 主机发送 IP 数据报
        2. 4.2.2.2. 路由器转发 IP 数据报
      3. 4.2.3. 数据报格式
    3. 4.3. 路由选择协议
      1. 4.3.1. RIP - 路由信息协议
      2. 4.3.2. OSPF - 开放最短路径优先
      3. 4.3.3. BGP - 边界网关协议
    4. 4.4. ICMP - 互联网控制消息协议
      1. 4.4.1. 应用
    5. 4.5. DHCP - 动态主机配置协议
    6. 4.6. NAT - 网络地址转换
      1. 4.6.1. 类型
      2. 4.6.2. NAT 的类型
    7. 4.7. SDN - 软件定义网络
      1. 4.7.1. 工作原理
  • 5. 5. 数据链路层
    1. 5.1. 概述
    2. 5.2. 封装成帧
    3. 5.3. 差错检测
      1. 5.3.1. CRC - 循环冗余检测
    4. 5.4. 多路访问协议
      1. 5.4.1. FDM - 频分多路复用
      2. 5.4.2. TDM - 时分多路复用
      3. 5.4.3. CDM - 码分多路复用
      4. 5.4.4. CSMA - 载波侦听多路访问
        1. 5.4.4.1. CSMA/CD(带碰撞检测的 CSMA)
        2. 5.4.4.2. CSMA/CA(带碰撞避免的 CSMA)
    5. 5.5. ARP - 地址解析协议
      1. 5.5.1. 工作原理
      2. 5.5.2. 消息格式
  • 6. 6. 套接字编程
    1. 6.1. 接口协议
    2. 6.2. Socket 编程
      1. 6.2.1. 服务器端步骤
      2. 6.2.2. 客户端步骤
      3. 6.2.3. TCP 编程
        1. 6.2.3.0.1. 数据结构socketaddr_in
  • 7. 实验:TCP 编程 - 五则运算
    1. 7.0.1. 实验要求
    2. 7.0.2. server 端代码
    3. 7.0.3. client 端代码
    4. 7.0.4. 注意事项
  • 计算机网络

    1. 计算机网络与因特网

    1.1 什么是Internet

    因特网是一个世界范围的计算机网络,即它是一个互联了遍及全球数十亿计算设备的网络

    –》是一堆网络通过网络互联设备连接成的大网络(所有网络中的一种)


    计算机网络:两台以上具有独立操作系统的计算机通过某些介质连接
    成的相互共享软硬件资源的集合体。

    计算设备

    用因特网术语来说,所有这些设备都成为主机端系统

    端系统通过通信链路分组交换机连接到一起。

    通信链路

    不同类型的通信链路由不同类型的物理媒体组成(电缆,铜线,光纤,无线电频谱)。不同链路以不同传输速度传输数据。当一台端系统向另一台端系统发送数据时,发送端系统将数据分段,并为每段加上首部字节,形成的信息包用计算机网络的术语来说叫分组。分组通过网络发送到目的端系统,在哪里被装配成初始数据。

    分组交换机

    分组交换机从它的一条入通信链路接受到达的分组,并从它的一条出通信链路转发该分组。

    分组交换机最著名的两种是:路由器链路层交换机

    路由器
    • 通常用于网络核心中
    链路层交换机
    • 通常用于接入网中。交换机主要在数据链路层工作。

    协议

    端系统,分组交换机和其他因特网部件都要运行一系列协议,这些协议控制新特王中信息的接收和发送。TCP/IP协议是因特网中最为重要的两个协议。

    因特网的主要协议统称为tcp/ip协议。

    服务描述

    我们从”为应用提供服务的基础设施“的角度来描述因特网。一些在线社交网络,视频会议,以及基于位置的推荐系统等,这些应用程序涉及多个相互交换数据的端系统,故他们被称为分布式应用程序

    重要的是,因特网应用程序运行在端系统上,即他们并不运行在网络核心中的分组交换机中。

    与因特网相连的端系统提供了一个套接字借口,该接口规定了运行在一个端系统上的程序请求因特网基础设施向运行在另一个端系统上的特定目的程序交付数据的方式。

    因特网套接字接口是一套发送程序必须遵守的规则集合。

    协议

    协议定义了在两个或多个通信实体之间交换的报文的格式和顺序,以及报文发送和/或接收一条报文或为他事务所采取的动作。

    掌握计算结网络领域知识的过程就是理解网络协议的构成,原理和工作方式的过程。

    1.2 网络边缘

    image-20221031210610129

    网络核心作用:数据交换(全球范围内切换开关,达到瞬时的连接,进行数据交换)(分布式系统相互配合)

    接入:把网络边缘接入到网络核心

    网络边缘:有网络应用,是网络存在的理由

    应用进程之间通讯模式:

    1.CS模式(客户端-服务端模式)—-扩展性差(请求响应达到一定程度,会崩)

    2.对等模式(peer-peer[p2p])—-某些节点即是客户端也是服务端。 扩展性强(迅雷)

    网络边缘:采用网络设施的面向连接服务

    面向连接:两方主机在会话前做好准备,即为连接建立状态。(两端主机知道但是中间的网络不知道)

    TCP服务:可靠的按顺序地传输数据

    UDP服务:无连接服务,不可靠,但及时

    1.3网络核心

    电路交换

    电路交换网络中,端系统见通信会话期间,预留了端系统见沿路通信所需要的资源(缓存,链路传输速率)(分组交换中不可预留) —-性能保障

    电路交换网络中的复用

    链路中的电路是通过频分复用(FDM)或时分复用(TDM)来实现。

    FDM

    链路的频谱由跨越链路创建的所有连接共享。在连接期间链路为每条连接专用一个频段,该频段的宽度成为带宽

    TDM

    时间被划分成固定期间的帧,并且每个帧又被划分为固定数量的时隙。创建一条连接时,网络在每个帧中为该连接指定一个时隙,这些时隙专门 由该连接单独使用。

    局限性

    电路交换不适合计算机之间的通信:

    1.连接建立时间长

    2.计算机之间的通信有突发性,如果使用线路交换,则浪费的片较多

    3.可靠性不高

    分组交换

    1
    2
    3
    在源和目的地之间,每个分组都通过通信链路和分组交换机传送。
    (交换机主要有两类:路由器,链路层交换机)
    分组以等于该链路最大传输速率的速度通过通信链路。
    存储转发传输

    多数分组交换机在链路的输入的输入端使用存储转发传输机制

    指在交换机能够开始向输出链路传输该分组的第一个比特之前,必须接收到整个分组。

    —-更适用于突发请求的情况

    网络分类

    image-20221101001825924

    1.4接入网和物理媒体

    这里接入网指将端系统物理连接到边缘路由器的网络边缘路由器是端系统到任何其他远程端系统的路径上第一台路由器。

    接入网类型和环境

    家庭接入

    DSL,电缆,FTTH,拨号和卫星

    宽带住宅接入有两种流行的类型:数字用户线(DSL)电缆

    DSL因特网接入

    DSL利用现成的本地电话基础设施。

    用户从提供本地电话接入的本地电话公司处获得DSL因特网接入。每个用户的DSL调制解调器使用现成的电话线与位于电话公司的本地中心局的数字用户线接入复用器(DSLAM)交换数据。

    电缆因特网接入

    电缆因特网接入利用了有线电视公司现有的有线电视基础设施。

    住宅从提供有线电视的公司获得了电缆因特网接入,光缆将电缆头端连接到地区枢纽,从这里使用传统的同轴电缆到达各家各户和公寓。

    modem

    电缆因特网接入需要特殊的调制解调器,称为电缆调制解调器(cable modem)。如同DSL调制解调器,电缆调制解调器是一种外部设备,通过以太网端口连接到家庭pc

    光纤到户(FTTH)

    从本地中心局到直接到家庭提供了一根光纤路径。

    拨号和卫星

    在某些乡村环境,无法提供DSL,电缆和FTTH的情况下,能够使用卫星链路将住宅以超过1Mbps的速率与因特网相连。使用传统电话线的拨号接入与DSL基于相同的模式:家庭的调节解调器经过电话线连接到ISP的调节解调器。

    企业接入

    以太网和WiFi

    公司,学校和越来越多的家庭,使用局域网(LAN)将端系统连接到边缘路由器。尽管有不同类型的局域网技术,以太网是目前为止最受欢迎的接入技术。

    以太网用户使用双绞铜线与一台以太网交换机相连。(有线)

    然而越来越多的人从其他物品无线接入因特网。一个无线LAN用户通常必须位于接入点的几十米范围内。基于IEEE 802.11技术的无线LAN接入,通俗点说就是WiFi,目前几乎无处不在。(无线)

    广域无线接入

    5G和LTE(dd)

    iphone和安卓等设备越来越多的用来在移动中发送信息。这些设备应用了与蜂窝移动电话相同的无线基础设施,通过蜂窝网提供商运营的基站来发送和接收分组。与WiFi不同的是,用户需要位于基站的数万米范围内。

    物理媒体

    物理媒体的例子包括:双绞铜线(以太网),同轴铜线(HFC),多模光纤缆,陆地无线电频谱和卫星无线电频谱。

    物理媒体分为:异引型媒体非异引型媒体

    1.7协议层次和服务模型

    协议和服务

    层次化方式实现复杂网络功能:
    • 将网络复杂的功能分层功能明确的层次,每一层实现了其中一个或一组功能,功能中有其上层可以使用的功能:服务
    • 本层协议实体相互交互执行本层的协议动作,目的是实现本层功能,通过接口为上层提供更好的服务
    • 在实现本层协议的时候,直接利用了下层所提供的服务
    • 本层的服务:借助下层服务实现的本层协议实体之间交互带来的新功能(上层可以利用的)+更下层提供的服务

    协议的目的是为了给上层提供更好的服务。

    服务和服务访问点

    **服务(service)**:低层向上层实体提供它们之间的通信的能力

    • 服务用户(service user)
    • 服务提供者(service provider)

    **服务访问点 SAP(Service Access Point)**:上层使用下层提供的服务时通过层间的接口地点,例如port

    总结:本层协议的实现需要借助下层提供的服务,而本层协议实现的目的是为了向上层提供服务

    协议栈

    应用层:网络应用
    • 为人类用户或者其他应用进程提供网络应用服务
    • FTP,SMTP,HTTP,DNS
    传输层:主机之间的数据传输
    • 在网络层提供的端到端的通信基础上,细分为进程到进程 ——进程到进程
    • 将不可靠的通信变成可靠通信
    • TCP,UDP
    网络层:为数据报从源到目的的选择路由
    • 主机主机之间的通信,端到端通信,不可靠 ——端到端
    • IP,路由协议
    链路层:相邻网络节点[网卡?]间的数据传输
    • 两个相邻2点的通信,点到点通信,可靠或不可靠 ——点到点
    • 点对点协议PPP,802.11(wifi),Ethernet
    • 传输以帧为单位的数据
    • 总结:在物理层的基础上,在相邻两点间以点对点的形式传输以帧为单位的数据
    物理层:在线路上传送bit

    ISO/OSI参考模型

    应用层-表示层-会话层-传输层-网络层-链路层-物理层

    表示层
    • 允许应用解释传输的数据,e.g:加密,压缩,机器相关的表示转换
    会话层:
    • 数据交换的同步,检查点,恢复
    • 会话管理,维持会话

    注意:互联网协议栈没有这两层!

    这些服务,如果需要的话,必须被应用实现。在技术栈里面由应用层实现

    各个层次的协议数据单元

    应用层:报文(message)

    传输层:报文段(segment):TCP段,UDP数据报

    网络层:分组packet(如果无连接方式:数据报datagram)

    数据链路层:帧(frame)

    物理层:位(bit)


    image-20240604171256657

    2.应用层

    网络应用程序体系架构

    客户-服务器(C/S)体系架构

    服务器:

    • 一直运行
    • 固定的IP地址和周知(默认)的端口号(约定)
    • 扩展性:服务器场
      • 只依赖数据中心进行扩展
      • 扩展性差,性能成断崖式下降

    客户端:

    • 主动与服务器通信
    • 与互联网有间歇性的连接
    • 可能是动态IP地址
    • 不直接与其他客户端通信
    对等体(P2P)体系结构
    • (几乎)没有一直运行的服务器
    • 任意端系统之间可以进行通信
    • 每一个节点即是客户端又是服务端
      • 自扩展性-新peer节点带来新的服务能力,当然也带来新的服务请求
    • 参与的主机间歇性连接且可以改变IP地址
      • 难以管理
    • 例子:GnutElla,迅雷
    C/S和P2Peye.com体系结构的混合体
    Napster
    • 文件搜索:集合
      • 主机在中心服务器上注册其资源
      • 主机向中心服务器查询资源位置
    • 文件传输:P2P
      • 任意Peer节点之间
    即时通信
    • 在线检测:几种
      • 当用户上线时,向中心服务器注册其IP地址
      • 用户与中心服务器联系,以找到其在线好友的位置
    • 两个用户之间聊天:P2P

    进程通信

    进程:在主机上运行的应用程序

    客户端进程:发起通信的进程

    服务器进程:等待连接的进程

    在同一个主机内,使用进程间通信机制通信(操作系统定义)

    不同主机,通过交换报文(Message)来通信

    • 使用OS提供的通信服务
    • 按照应用协议交换报文
      • 借助传输层提供的服务

    注意:P2P架构的应用也有客户端进程和服务器进程之分

    1. 对进程进行编址

      • 一个进程:用IP+Port+协议。 例如127.0.0.1:80 TCP
      • 本质上,一对主机进程之间的通信由两个端节点构成
    2. 传输层提供的服务-层间信息的代表

      • 如果Socket API每次传输报文,都携带很多信息,太过繁琐不易于管理

      • 用个代号标识通信的双方或单方:socket

      • TCP Socket:

        • TCP服务,两个进程之间的通信需要之前建立连接
          • 两个进程通常会持续一段时间,通信关系稳定
        • 可以用一个整数标识两个应用实体时间的通信关系,本地标识
        • TCP Socket:源IP,源端口,目标IP,目标端口

        对于使用面向连接服务(TCP)的应用而言,套接字是4元组的一个具有本地意义的标识

      • UDP Socket

        • UDP服务,两个进程之间的通信之前无序建立连接
          • 每个报文都是独立传输的‘前后报文可能给不同的分布式进程
        • 因此,只能用一个整数表示本应用实体的标识
          • 因为这个报文可能传给另一个分布式进程
        • UDP Socket:本IP,本地端口
        • 但是传输报文的时候必须提供对方IP,Port

        对于使用无连接服务(UDP)的应用而言,套接字是2元组的一个具有本地意义的标识

    3. 如何使用传输层提供的服务实现应用

      • 定义应用层协议:报文格式,解释,时序等
      • 编制程序,通过API调用网络基础设施提供通次你程序传报文,解析报文,实现应用时序等
    安全TCP——SSL
    • 在TCP上面实现,提供加密的TCP连接
    • 私密性,数据完整性,端到端的鉴别
    • SSL socket API
      • 应用通过API将铭文交给socket,SSL将其加密在互联网上传输
      • 详见第八章
    image-20240604193412884

    HTTP

    HTTP概况

    使用TCP:

    • 客户发起一个与服务器的TCP连接(建立套接字),端口号为80
    • 服务器接收客户的TCP连接
    • 在浏览器(HTTP客户端)与Web服务器(HTTP服务端server)交换HTTP报文
    • TCP连接关闭

    HTTP 是无状态的:服务器并不维护关于客户的任何信息

    HTTP连接

    非持久HTTP

    • 最多只有一个对象在TCP连接上发送
    • 下载多个对象需要多个TCP连接
    • HTTP/1.0使用非持久连接

    持久HTTP

    • 多个对象可以在一个(在客户端和服务器之间的)TCP连接上传输
    • HTTP/1.1默认使用持久连接
    HTTP请求报文
    1
    2
    3
    4
    5
    6
    GET /somdir/page.html HTTP/1.1
    Host: www.someschool.edu
    User-agent: Mozilla/4.0
    Connection: close
    Accept-language:fr
    --最后有一个回车换行符,表示报文结束
    HTTP响应报文
    1
    2
    3
    4
    5
    6
    7
    8
    9
    HTTP/1.1 200 OK
    Connection: close
    Date: Tue, 09 Aug 2011 15:44:04 GMT
    Server: Apache/2.2.3 (CentOS)
    Last-Modified:Tue, 09 Aug 2011 15:11:03 GMT
    Content-Length: 6821
    Cpntent-Type:text/html

    (data···)

    FTP

    ​ ——控制连接与数据连接分开

    • FTP客户端与FTP服务器通过端 port 21联系,并使用TCP为传输协议
    • 客户端通过控制连接获得身份确认
    • 客户端通过控制连接发送命令浏览远程目录
    • 受到一个文件传输命令时,服务器打开一个到客户端的数据连接(port 20)
    • 一个文件传输完成之后,服务器关闭连接
    image-20240604195448084
    主动模式
    • 客户端发送 port 命令
    • 服务器根据 port 命令指定的客户端地址和端口号发起数据连接
    被动模式
    • 客户端发起 PASV 命令
    • 服务器返回监听的地址和端口号
    • 客户端发起数据连接

    SMTP

    三个主要组成部分:

    • 用户代理
    • 邮件服务器
    • 简单邮件传输协议:SMTP
    SMTP协议
    • 使用TCP在客户端和服务器之间传送报文,端口号为 25
    • 直接传输:从发送方服务器到接收方服务器
    • 传输的三个阶段:
      • 握手
      • 传输报文
      • 关闭
    • 命令/响应交互
      • 命令:ASCII文本
      • 响应:状态码和状态信息
    • 报文必须为7位ASCII码

    DNS

    DNS的主要思路

    • 分层的、基于域的命令规则
    • 若干分布式的数据库完成名字到IP的转换
    • 运行在UDP之上端口号为53的应用服务
    • 核心的Internet功能,但以应用层协议实现
      • 在网络边缘处理复杂性

    DNS记录

    DNS:保存资源记录(RR)的分布式数据库

    image-20231013174016038

    TTL:生存时间,决定了资源记录应当从缓存中删除的时间

    • Type = A
      • Name为主机
      • Value为IP地址
    • Type = CNAME
      • name为规范名字的别名
      • value为规范名字
    • Type = NS
      • name 为域名(如foo.com)
      • Value 为该域名的权威服务器的域名
    • Type= MX
      • Value为那么对应的邮件服务器的名字

    DNS大致工作过程

    • 应用调用 解析器(resolver)
    • 解析器作为客户 向Name Server发送查询报文(封装在UDP段中)
    • Name Server返回相应报文(name/ip)

    3.传输层

    概述

    • 物理层、数据链路层和网络层共同解决了将主机通过异构网络互联起来所面临的问题。实现了主机到主机的通信。
    • 但实际上在计算机网络中进行通信的真正实体是位于通信两端主机中的进程
    • 如何为运行在不同主机上的应用进程提供直接的通信服务是传输层的任务,传输层协议又称为端到端协议
    • 传输层向高层用户屏蔽了下面网络核心的细节(如网络拓扑、所采用的路由选择协议等),它使应用进程看见的就好像是两个传输层实体有一条端到端的逻辑通信信道
    • 根据应用需求的不同,因特网的传输层为应用层提供了两种不同的运输协议,即面向连接的TCP和无连接的UDP

    端口号、复用与分用

    端口号

    • TCP/IP 体系的传输层使用端口号来区分应用层的不同应用进程
      • 端口号使用 16 比特表示,取值范围为 0-65535
        • 熟知端口号:0-1023,LANA 把这些端口号制定给了 TCP/IP 协议中最重要的一些应用程序,例如:FTP使用 21/20,HTTP 使用 80,DNS 使用 53
        • 登记端口号:1024-49151,为没有熟知的端口号的应用进程使用,需要登记避免重复。例如 Microsoft RDF远程桌面使用 3389
        • 短暂端口号:49152-65535,留给客户进程选择暂时使用。

    复用和分用

    运输层端口号、复用与分用:

    image-20240614212356037

    TCP 与 UDP

    首部

    UDP
    image-20240614213059106
    TCP
    image-20240614213124008

    首部结构:

    image-20240614215542592

    对比

    • UDP
      • 无连接
      • 支持一对一,一对多,多对一和多对多交互通信
      • 对应用层交付的报文直接打包
      • 尽最大努力交付,也就是不可靠;不适用流量控制和拥塞控制
      • 首部开销小,仅8字节
    • TCP
      • 面向连接
      • 每一条 TCP 连接只能有两个端点 EP,只能是一对一
      • 面向字节流
      • 可靠传输,使用流量控制和拥塞控制
      • 首部最小 20 字节,最大 60字节

    TCP实现原理

    流量控制

    一般来说,我们总希望数据传输得更快,但是如果发送包把数据发送的过快,接收方可能来不及接受,造成数据的丢失。

    流量控制:让发送方的发送速率不要太快,让接收方来得及接受。

    滑动窗口机制
    • TCP 接收方利用自己的接收窗口大小来限制发送窗口的大小
    • TCP 发送方收到接收方的零窗口通知后,应启动持续计时器,持续计时器超时后,向接收方发送零窗口探测报文。

    拥塞控制

    1. 慢启动(Slow Start)
      • 目的:逐步探测网络容量,避免突然增加负载导致拥塞。
      • 过程:连接初始时,拥塞窗口(cwnd)从一个MSS(最大报文段大小)开始,每收到一个ACK,cwnd增加一个MSS。cwnd呈指数增长,直到达到慢启动阈值(ssthresh)。
    2. 拥塞避免(Congestion Avoidance)
      • 目的:在接近网络容量时,平缓地增加发送速率,避免引发拥塞。
      • 过程:当cwnd达到ssthresh后,每个RTT(往返时间),cwnd增加一个MSS/cwnd。cwnd呈线性增长。
    3. 快速重传(Fast Retransmit)
      • 目的:快速检测和重传丢失的报文段,避免等待超时。
      • 过程:当收到三个重复ACK时,立即重传丢失的报文段,而不等待重传超时。
    4. 快速恢复(Fast Recovery)
      • 目的:在快速重传后迅速恢复传输速率,避免进入慢启动阶段。
      • 过程:在快速重传后,将ssthresh设置为cwnd的一半,并将cwnd设置为ssthresh加三个MSS。每收到一个重复ACK,cwnd增加一个MSS;收到新的ACK后,进入拥塞避免阶段。
    image-20240614214233257

    三次握手四次挥手

    握手
    tcp

    最终效果:

    • ack = seq + 1
    • 最后一次 seq = ack
    image-20240614215033238
    挥手
    挥手

    最终效果:

    • ack = seq + 1
    • 最后一次 seq = ask
    image-20240614215054178

    4. 网络层

    概述

    • 网络层的主要任务是实现网络互联,进而实现数据包在各个网络之间的传输。
    • 要实现网络层任务,需要解决以下几个主要问题:
      • 网络层向传输层提供怎样的服务(”可靠传输”还是”不可靠传输”)
      • 网络层寻址问题
      • 路由选择问题
    • 因特网是目前全世界用户数量最多的互联网,它使用 TCP/IP 协议栈
    • 由于 TCP/IP 协议栈的网络层使用网络协议 IP ,它是整个协议栈的核心协议,因此在 TCP/IP 协议栈中网络层常称为网络层

    数据平面和控制平面

    • 数据平面
      • 本地的,每个路由器自身的功能
      • 决定抵达路由器输入端口的数据报如何转发到输出端口
    • 控制平面
      • 整个网络范围
      • 决定数据报在端到端路径上的路由器之间如何路由
      • 两种数据平面的实现方式
        • 传统的路由算法:在路由器内实现
        • 软件定义网络(Software-defined networking,SDN):在远程服务器上实现

    IPV4

    编址

    IPv4 地址的编址方式经历了如下三个历史阶段:

    • 分类编址
    • 划分子网
    • 无分类编址
    分类编址
    image-20240614220017156
    划分子网
    • 32 比特的子网掩码可以表名分类 IP 地址的主机号被借用了几个比特作为子网号

      • 使用连续的比特 1 对应网络号和子网号
      • 使用连续的比特 0 对应主机号
    • 给定一个分类的 IP 地址和其相应的子网掩码,就可以知道子网划分的细节:

      • 划分出的子网数量

      • 每个子网可分配的 IP 地址数量

      • 每个子网的网络地址和广播地址

      • 每个子网可分配的最小和最大地址

    数据报发送

    主机发送 IP 数据报

    判断目的主机是否与自己在同一个网络

    • 若在同一个网络,则属于直接交付
    • 若不在同一个网络,则属于间接交付,传输给主机所在网络的默认网关(路由器),由哦默认网关帮忙转发
    路由器转发 IP 数据报
    1. 检查 IP 数据报首部是否出错
      1. 若出错,则直接丢弃该 IP 数据报并通告源主机
      2. 若没有出错,则进行转发
    2. 根据 IP 数据报的目的地址在路由表中查找匹配的条目
      1. 若找到匹配的条目,则转发给条目中指示的下一条
      2. 若找不到,则丢弃该 IP 数据报并通告源主机

    数据报格式

    image-20240616002021305

    路由选择协议

    image-20240616000314784

    RIP - 路由信息协议

    路由信息协议 RIP(Routing Information Protocol)是内部网关协议 IGP 中最先得到广泛使用的协议之一。

    • RIP 要求自治系统 AS 内的每一个路由器都要维护从他自己到 AS 内其他每个网络的距离记录
    • RIP 使用跳数(Hop Count)作为度量来衡量到达目的网络的距离
    • RIP 认为好的路由就是距离短的路由,也就是所谓通过路由器数量最少的路由。

    OSPF - 开放最短路径优先

    开放最短路径优先 OSPF(Open Shortest Path First),视为克制RIP的缺点在1989年开发出来的。

    • 使用了 Dijkstra 提出的最短路径算法 SPF
    • OSPF 是基于链路状态的,而不像 RIP 那样是基于距离向量的
      • 链路状态是指本路由器都和哪些路由器相邻,以及相应链路的”cost”。
    • OSPF 采用 SPF算法计算路由,在算法上保证了不会产生路由环路
    • OSPF 不限制网络规模。更新效率高,收敛速度快

    BGP - 边界网关协议

    因特网采用分层次的路由选择协议

    • 内部网关协议 IGP (例如路由信息协议 RIP 或开放最短路径优先 OSPF)
      • 设法使分组在一个自治系统内尽可能有效地从源网络传输到目的网络
      • 无需考虑自治系统外部其他方面的策略
    • 外部网关协议 EGP (例如边界网关协议 BGP)
      • 在不同自治系统内,度量路由的”cost”可能不同,因此,对于自治系统之间的路由选择,使用”cost”作为度量是不可行的

    ICMP - 互联网控制消息协议

    ICMP(Internet Control Message Protocol,互联网控制消息协议)是网络层协议,主要用于在网络设备(如路由器、主机)之间传递控制消息和错误报告。它在IP协议中帮助诊断网络问题并提供有关网络通信状态的信息。

    ICMP消息封装在IP包中传输,但它并不用于传输用户数据,而是用于传递控制信息。常见的ICMP消息类型有回显请求(Echo Request)和回显应答(Echo Reply),用于诊断网络连接的工具如 pingtraceroute 就是基于这些消息工作的。

    应用

    • ping
      • ping命令利用ICMP的Echo Request和Echo Reply消息来测试主机之间的连通性和响应时间。
    • traceroute

      • traceroute工具利用ICMP的时间超过(Time Exceeded)消息来确定数据包在到达目的地之前经过的每一个路由器,帮助诊断网络路径问题。
    • 错误报告

      • 路由器和主机使用ICMP消息来报告网络错误,如目标不可达、时间超过等,帮助网络管理员快速定位和解决问题。

    DHCP - 动态主机配置协议

    DHCP((Dynamic Host Configuration Protocol)是一种网络管理协议,用于自动为网络中的设备分配IP地址、子网掩码、网关和DNS服务器等网络配置信息。它简化了网络管理员的工作,减少了手动配置的复杂性和错误。

    • DHCP除了IP地址,还可以分配客户的第一跳路由器地址(网关);DNS服务器的IP地址或域名;子网掩码
    • DHCP是应用层协议

    NAT - 网络地址转换

    NAT(Network Address Translation)是一种在计算机网络中将私有(内部)IP地址映射为公共(外部)IP地址的技术。它由路由器或防火墙设备实现,主要用于解决IPv4地址匮乏问题,并提高网络安全性。

    类型

    NAT 的类型

    • 静态NAT(Static NAT)

      • 静态NAT为每个内部私有IP地址分配一个唯一的公共IP地址。这种映射是固定的,不会改变。

      • 优点:适用于需要外界访问内部特定服务器的场景,如Web服务器。

      • 缺点:需要大量的公共IP地址,扩展性差。

    • 动态NAT(Dynamic NAT)

      • 动态NAT从一个公共IP地址池中动态地为内部私有IP地址分配公共IP地址。每次内部设备请求外部连接时,NAT设备从池中选取一个公共IP地址进行映射。

      • 优点:节省公共IP地址。

      • 缺点:分配的公共IP地址是动态的,外部无法固定访问内部特定设备。

    • 端口地址转换(PAT,Port Address Translation),也称为NAT重载(NAT Overload)

      • PAT是最常用的NAT类型,它允许多个内部私有IP地址共享一个公共IP地址。通过使用不同的端口号来区分不同的内部设备。
      • 优点:极大地节省了公共IP地址,适用于大多数家庭和小型企业网络。
      • 缺点:依赖于端口号的唯一性,可能会遇到端口耗尽的问题。

    SDN - 软件定义网络

    软件定义网络(Software-Defined Networking, SDN)是一种网络架构,它通过将网络控制平面和数据平面分离,实现网络的集中控制和灵活管理。SDN的核心思想是将网络设备的控制逻辑从硬件中抽离出来,通过软件进行集中管理和配置。

    • SDN并不是一个具体的技术,它是一种网络设计理念,规划了网络的各个组成部分(软件、硬件、转发面和控制面)及相互之间的互动关系.
    • SDN的核心思想是建立一个通用转发体系:每个交换设备上包含一个流表(flow table);流表由一个逻辑上中心化的控制器来计算和分发

    工作原理

    • 控制与数据平面分离

      • 传统网络设备将控制平面和数据平面集成在一起,而 SDN 通过将这两者分离,使得网络设备仅负责数据包的转发,而控制逻辑由集中控制器管理。
    • 集中控制

      • SDN 控制器维护全局网络视图,能够根据网络状态和策略动态调整网络配置。

      • 控制器通过南向接口协议(如 OpenFlow)向网络设备下发转发规则。

    • 可编程性

      • 网络管理员可以通过编写应用程序来定义网络行为和策略,这些应用程序通过北向接口与控制器交互,灵活控制网络流量。

    5. 数据链路层

    概述

    数据链路层的职责是把数据报从一个节点传送到与该节点有直接物理链路相连的另一个节点。

    数据链路:

    • 使用点对点信道的数据链路层
      • 点对点链路:链路两端各一个节点,一个发送一个接收
      • 三个重要问题:封装成帧差错检测可靠传输、(流量控制)
    • 使用广播信道的数据链路层
      • 广播链路:多个节点连接到一个共享的广播信道
      • 解决多路访问问题:如何协调多个发送和接受节点之间对共享广播信道的访问;相关技术是多路访问协议(也称为多址访问协议)
        • 频分多路复用、时分多路复用、码分多路复用
        • 共享式以太网的媒体接入控制协议 CSMA/CD
        • 802.11 局域网的媒体接入控制协议 CSMA/CA
    • 数据链路层的互联设备
      • 网桥和交换机的工作原理
      • 集线器(物理层互联设备)与交换机

    封装成帧

    • 封装成帧是指数据链路层给上层交付的协议数据单元添加帧头和帧尾
      • 包含重要的控制信息
      • 作用之一是帧定界
    • 透明传输是指数据链路层对上层交互的数据没有任何限制
      • 面向字节的物理链路使用字节填充实现透明传输
      • 面向比特的物理链路使用比特填充的方式实现透明传输
    • 考虑到帧的传输效率和差错控制等,应当使帧的数据部分长度尽可能大,但每一种数据链路层协议规定了帧的数据部分长度上限:最大传送单元 MTU(Maximum Transfer Unit)

    差错检测

    • 奇偶校验:最基本的方法
    • Internet校验和:常用于运输层
    • 循环冗余检测:常用于链路层

    前两个计算量小但是差错检测率不高,一般采用循环冗余检测 CRC 方法

    CRC - 循环冗余检测

    CRC(Cylic Redundancy Check) 即多项式编码,把要发送的比特串看作为是系数为0或1的一个多项式,对比特串的操作看作为多项式。一般在数据链路层,由网络适配器(网卡)实现。

    具体步骤如下:

    1. 数据表示
      • 将待传输的数据视为一个二进制多项式。例如,数据 11010011101100 可以表示为多项式 x^13+x^12+x^10+x^8+x^7+x^6+x^4+x^3+x^2
    2. 选择生成多项式
      • 选择一个预定义的生成多项式(也称为多项式除数),通常记作 G(x)。例如,一个常用的生成多项式是 CRC-32 中的 G(x)=x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1
    3. 数据扩展
      • 将数据多项式 D(x)乘以 x^n,其中 n 是生成多项式的阶数(即最高幂次)。这相当于在数据后面添加 n 个零。例如,对于一个4位的生成多项式 G(x),数据 D(x) 会扩展为 D(x)⋅x^4
    4. 求余
      • 使用二进制长除法对扩展后的数据 D(x)⋅x^n 除以生成多项式 G(x),得到余数 R(x)。这个余数就是CRC校验码。
    5. 附加校验码
      • 将余数 R(x) 附加到原始数据后形成传输数据。
    6. 接收方验证
      • 接收方接收到数据后,用相同的生成多项式 G(x) 对接收到的数据多项式进行除法运算。如果余数为零,则认为数据传输无误;否则,数据存在错误
    image-20240616021457439 image-20240616021507807

    多路访问协议

    信道划分协议主要分为三类:频分多路复用(FDM)时分多路复用(TDM)码分多路复用(CDM)。此外,还有 随机访问协议轮询协议 等替代方案。

    FDM - 频分多路复用

    原理
    频分多路复用将信道的频谱划分为多个频率段,每个频率段分配给不同的节点使用。每个节点在它的频率段内进行通信,不同节点的信号在频率上互不干扰。

    特点

    • 每个节点都有固定的频率带宽。
    • 适用于持续的数据传输。
    • 带宽利用率可能较低,因为频谱是固定分配的,无论节点是否有数据要发送。

    示例
    无线电广播中,各个电台使用不同的频率进行广播。

    TDM - 时分多路复用

    原理
    时分多路复用将时间划分为多个时隙(Time Slots),每个时隙分配给不同的节点使用。每个节点在它的时隙内进行通信,不同节点的信号在时间上互不干扰。

    特点

    • 每个节点都有固定的时间时隙。
    • 适用于连续数据流。
    • 时间利用率可能较低,因为时隙是固定分配的,无论节点是否有数据要发送。

    示例
    电话系统中的时分多路复用(TDM)技术。

    CDM - 码分多路复用

    原理
    码分多路复用使用不同的码序列(Code Sequences)来区分不同的节点。所有节点在同一时间、同一频率上进行通信,但使用不同的码序列。接收端使用相同的码序列来提取特定节点的数据。

    工作流程

    1. 数据编码:每个发送节点将自己的数据与一个独特的伪随机码(pseudo-random code)进行编码。这个码通常是比特序列。
    2. 信号叠加:所有编码后的信号在同一频谱上传输,形成一个复合信号。
    3. 数据解码:接收节点使用与发送节点相同的伪随机码对接收到的复合信号进行解码,从中提取出属于自己的数据。

    特点

    • 高效利用频谱资源。
    • 能够承受一定的干扰和噪声。
    • 需要复杂的编码和解码技术。

    CSMA - 载波侦听多路访问

    载波监听多路访问(Carrier Sense Multiple Access, CSMA) 是一种用于共享媒体网络的媒体接入控制协议。CSMA 的基本思想是网络节点在传输数据之前首先监听信道(Carrier Sense),只有当信道空闲时才开始传输数据。CSMA 通过这种方式减少数据包碰撞的概率。

    CSMA 协议在多种网络技术中得到了广泛应用,特别是在早期的局域网和一些无线网络中。具体应用如下:

    • 早期以太网(Ethernet):以太网(有线)采用 CSMA/CD(带碰撞检测的 CSMA)协议来管理信道访问。
    • 无线局域网(Wi-Fi):无线网络中采用 CSMA/CA(带碰撞避免的 CSMA)协议。
    CSMA/CD(带碰撞检测的 CSMA)
    • 监听信道:节点在发送数据之前先监听信道是否空闲。
    • 发送数据:如果信道空闲,节点开始发送数据。如果信道忙,则等待一段时间后重试。
    • 检测碰撞:在数据发送过程中,节点同时监听信道。如果检测到碰撞(多个节点同时发送导致信号冲突),停止发送数据。
    • 处理碰撞:检测到碰撞的节点发送一个短的干扰信号(Jam Signal)以通知其他节点发生了碰撞。
    • 重试发送:节点等待一个随机的退避时间(Backoff Time)后重试发送数据。
    CSMA/CA(带碰撞避免的 CSMA)
    • 监听信道:节点在发送数据之前先监听信道是否空闲。
    • 随机退避:如果信道空闲,节点等待一个随机的退避时间以减少碰撞概率。如果信道忙,节点继续监听直到信道空闲。
    • 发送数据:节点在退避时间结束后发送数据。
    • 确认接收(ACK):接收节点成功接收数据后,发送一个确认帧(ACK)给发送节点。如果发送节点在预定时间内没有收到ACK,则认为数据丢失,重新发送。
    image-20240616024328288

    ARP - 地址解析协议

    地址解析协议(Address Resolution Protocol, ARP) 是一种用于IPv4网络的协议,它负责将IP地址解析为对应的MAC地址,以便在局域网中进行数据传输。ARP 是网络层和数据链路层之间的重要桥梁,确保数据包能够在局域网中正确地找到目标设备。

    工作原理

    ARP的工作过程可以分为以下几个步骤:

    • ARP请求(ARP Request)

      • 当一个设备(比如计算机A)需要知道目标设备(比如计算机B)的MAC地址时,它会在网络上广播一个ARP请求。这个请求包含了目标设备的IP地址。
    • ARP请求的广播

      • ARP请求是一个广播帧,即发送到同一局域网内的所有设备。广播MAC地址通常为 FF:FF:FF:FF:FF:FF
    • ARP响应(ARP Response)

      • 网络中的每个设备都会接收到这个ARP请求,但只有目标设备(计算机B)会回应。目标设备会发送一个包含其MAC地址的ARP响应,直接发送给请求设备(计算机A)。
    • 更新ARP缓存

      • 计算机A在收到ARP响应后,会将目标设备的IP地址和MAC地址映射关系存储在其ARP缓存(ARP Cache)中,以便后续通信使用。

    消息格式

    ARP消息一般包含以下几个关键字段:

    • 硬件类型(Hardware Type):通常为以太网(Ethernet),值为1。
    • 协议类型(Protocol Type):通常为IPv4,值为0x0800。
    • 硬件地址长度(Hardware Address Length):通常为6(MAC地址长度)。
    • 协议地址长度(Protocol Address Length):通常为4(IPv4地址长度)。
    • 操作码(Operation Code):1表示ARP请求,2表示ARP响应。
    • 发送方硬件地址(Sender Hardware Address):发送方的MAC地址。
    • 发送方协议地址(Sender Protocol Address):发送方的IP地址。
    • 目标硬件地址(Target Hardware Address):目标设备的MAC地址(ARP请求中为空)。
    • 目标协议地址(Target Protocol Address):目标设备的IP地址。

    6. 套接字编程

    接口协议

    • TCP/IP协议存在于OS中,网络服务通过OS提供
    • TCP/IP要尽量避免让接口使用某一个厂商的OS中特有的特征(而其他厂商没有)
    • TCP/IP和应用程序之间的接口应该不精确指明:
      • 不规定接口的细节
      • 只建议需要的功能集
      • 允许系统设计者选择有关API的具体实现细节

    Socket 编程

    Socket是一个主机本地应用程序所创建的, 为操作系统所控制的接口, Client/server模式的通信接口。应用进程通过这个接口,使用传输层提供的服务, 跨网络发送(接收)消息到(从)其他应用进程

    服务器端步骤

    TCP:

    1. 创建Socket:调用socket()函数创建一个套接字。
    2. 绑定地址:调用bind()函数将套接字绑定到一个本地地址(IP地址和端口号)。
    3. 监听连接:调用listen()函数使套接字进入监听状态,准备接收客户端的连接请求。
    4. 接受连接:调用accept()函数从队列中取出一个已完成的连接,当没有连接时会阻塞。
    5. 数据传输:通过send()recv()函数发送和接收数据。
    6. 关闭连接:调用close()函数关闭套接字。

    UDP:

    1. 创建Socket:调用socket()函数创建一个数据报套接字。
    2. 绑定地址:调用bind()函数将套接字绑定到一个本地地址(IP地址和端口号)。
    3. 接收数据:调用recvfrom()函数接收数据,该函数会返回数据和客户端地址。
    4. 发送数据:调用sendto()函数向客户端发送数据。
    5. 关闭Socket:调用close()函数关闭套接字。

    客户端步骤

    1. 创建Socket:调用socket()函数创建一个套接字。
    2. 连接服务器:调用connect()函数将套接字连接到服务器端的地址。
    3. 数据传输:通过send()recv()函数发送和接收数据。
    4. 关闭连接:调用close()函数关闭套接字。

    TCP 编程

    image-20240617101903565
    数据结构socketaddr_in

    ip地址和port捆绑关系的数据结构(标识进程的端节点)

    1
    2
    3
    4
    5
    6
    struct socketaddr_in{
    short sin_family; //地址族
    u_short sin_port; //port
    struct in_addr sin_addr; //ip地址
    char sin_zero[8]; //对齐
    }

    域名和IP地址的数据结构

    1
    2
    3
    4
    5
    6
    7
    8
    struct hostent{
    char *h_name;
    char **h_aliases;
    int h_addrtype;
    int h_length; //地址长度
    char **h_addr_list;
    #define h_addr h_addr_list[0];
    }
    image-20231017133434632 image-20231017133540916

    实验:TCP 编程 - 五则运算

    实验要求

    https://uestc.feishu.cn/wiki/wikcn7KBvz8W5ibZNQ5DPEo97Lg

    image-20240617203834409

    server 端代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    #include <stdio.h>
    #include <unistd.h>
    #include <stdint.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <signal.h>

    #define BUFFER_SIZE 1024
    volatile sig_atomic_t sigint_flag = 0;

    void psych_handle_sigint(int sig) {
    printf("[srv] SIGINT is coming!\n");
    sigint_flag = 1;
    }


    void psych_srv_calc(int psych_connfd) {
    uint32_t pdu[6];
    int64_t psych_result;
    while(1) {
    ssize_t size_read = read(psych_connfd, pdu, sizeof(pdu));
    if (size_read == 0) { // 客户端已关闭连接
    break;
    } else if (size_read < 0) {
    perror("Read error");
    break;
    }

    uint32_t op_code = ntohl(pdu[0]);
    int64_t op1 = ((int64_t)ntohl(pdu[1]) << 32) | ntohl(pdu[2]);
    int64_t op2 = ((int64_t)ntohl(pdu[3]) << 32) | ntohl(pdu[4]);
    char *op;
    switch (op_code) {
    case 0x00000001: // ADD
    psych_result = op1 + op2;
    op = "+";
    break;
    case 0x00000002: // SUB
    psych_result = op1 - op2;
    op = "-";
    break;
    case 0x00000004: // MUL
    psych_result = op1 * op2;
    op = "*";
    break;
    case 0x00000008: // DIV
    if (op2 == 0) {
    psych_result = 0; // Avoid division by zero
    } else {
    psych_result = op1 / op2;
    }
    op = "/";
    break;
    case 0x00000010: // MOD
    if (op2 == 0) {
    psych_result = 0; // Avoid division by zero
    } else {
    psych_result = op1 % op2;
    }
    op = "%";
    break;
    default:
    psych_result = 0; // Unknown operation
    break;
    }

    uint32_t rep_pdu[2];
    rep_pdu[0] = htonl((uint32_t)(psych_result >> 32)); // 结果高32位
    rep_pdu[1] = htonl((uint32_t)psych_result); // 结果低32位

    //psych_result = htonl(psych_result);
    ssize_t size_write = write(psych_connfd, rep_pdu, sizeof(rep_pdu));
    if (size_write < 0) {
    perror("Write error");
    break;
    }
    printf("[rqt_res] %ld %s %ld = %ld\n", op1, op, op2, psych_result);
    }
    }



    int main(int argc, char **argv) {
    if (argc != 3) {
    fprintf(stderr, "Usage: %s <IP> <Port>\n", argv[0]);
    exit(EXIT_FAILURE);
    }

    const char* psych_server_ip = argv[1];
    int psych_server_port = atoi(argv[2]);
    int psych_listenfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in psych_serv_addr,psych_cli_addr;
    socklen_t psych_cli_len = sizeof(psych_cli_addr);

    memset(&psych_serv_addr, 0, sizeof(psych_serv_addr));
    psych_serv_addr.sin_family = AF_INET;
    psych_serv_addr.sin_port = htons(psych_server_port);
    inet_pton(AF_INET, psych_server_ip, &psych_serv_addr.sin_addr);

    bind(psych_listenfd, (struct sockaddr *)&psych_serv_addr, sizeof(psych_serv_addr));
    listen(psych_listenfd, 10);

    printf("[srv] server[%s:%d] is initializing!\n", psych_server_ip, psych_server_port);

    // Handle SIGINT
    struct sigaction psych_sigIntHandler;
    psych_sigIntHandler.sa_flags = 0;
    psych_sigIntHandler.sa_handler = psych_handle_sigint;
    sigemptyset(&psych_sigIntHandler.sa_mask);
    sigaction(SIGINT, &psych_sigIntHandler, NULL);

    int psych_connfd;
    while(!sigint_flag) {
    int psych_connfd = accept(psych_listenfd, (struct sockaddr *)&psych_cli_addr, &psych_cli_len);
    if (psych_connfd < 0) {
    if (errno == EINTR) {
    continue; // Interrupted by signal, check the flag and possibly continue
    }else{
    perror("Accept failed");
    break;
    }
    }

    char psych_client_ip[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &psych_cli_addr.sin_addr,psych_client_ip, INET_ADDRSTRLEN);
    printf("[srv] client[%s:%d] is accepted!\n", psych_client_ip, ntohs(psych_cli_addr.sin_port));
    psych_srv_calc(psych_connfd);
    close(psych_connfd);
    printf("[srv] client[%s:%d] is closed!\n", psych_client_ip, ntohs(psych_cli_addr.sin_port));
    }

    close(psych_listenfd);
    printf("[srv] listenfd is closed!\n");
    printf("[srv] server is going to exit!\n");

    return 0;
    }

    client 端代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    #include <stdio.h>      // 标准输入输出函数
    #include <unistd.h> // Unix标准函数定义
    #include <stdint.h> // 整型数的确切宽度
    #include <sys/socket.h> // 套接字接口函数
    #include <netinet/in.h> // 因特网地址结构体
    #include <arpa/inet.h> // IP地址转换函数
    #include <stdlib.h> // 标准库函数
    #include <string.h> // 字符串处理函数
    #include <errno.h> // 错误号定义

    #define BUFFER_SIZE 1024

    const uint32_t op_codes[] = {0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010}; // +, -, *, /, %
    const char *op_strings[] = {"ADD", "SUB", "MUL", "DIV", "MOD"};
    const char *op_1_strings[] = {"+", "-", "*", "/", "%"};
    char op_1;

    // Get the opcode from the operation string
    uint32_t psych_get_opcode(const char *op) {
    for (int i = 0; i < 5; i++) {
    if (strcmp(op, op_strings[i]) == 0) {
    return op_codes[i];
    }
    }
    return 0;
    }

    // Get the op_1 from the operation string
    char psych_get_op_1(uint32_t op_code) {
    char op_1 = '\0';
    switch (op_code) {
    case 0x00000001: // ADD
    op_1 = '+';
    break;
    case 0x00000002: // SUB
    op_1 = '-';
    break;
    case 0x00000004: // MUL
    op_1 = '*';
    break;
    case 0x00000008: // DIV
    op_1 = '/';
    break;
    case 0x00000010: // MOD
    op_1 = '%';
    break;
    default:
    break;
    }
    //printf("op_1: %c\n", op_1);
    return op_1;
    }


    void psych_cli_calc(int psych_sock) {

    char line[BUFFER_SIZE];
    uint32_t op_code;
    int64_t op1, op2;
    char op[10], op_1;


    while(1){
    if(fgets(line, BUFFER_SIZE, stdin) == NULL){
    perror("Error reading input");
    break;
    }

    if(strncmp(line, "EXIT", 4) == 0){
    printf("[cli] command EXIT received\n");
    break;
    }

    // Parse the input
    if (sscanf(line, "%s %ld %ld", op, &op1, &op2) != 3) {
    printf("Invalid input\n");
    continue;
    }

    op_code = psych_get_opcode(op);
    op_1 = psych_get_op_1(op_code);

    uint32_t pdu[6];
    pdu[0] = htonl(op_code);
    pdu[1] = htonl((uint32_t)(op1 >> 32));
    pdu[2] = htonl((uint32_t)op1);
    pdu[3] = htonl((uint32_t)(op2 >> 32));
    pdu[4] = htonl((uint32_t)op2);


    ssize_t size_write = write(psych_sock, pdu, sizeof(pdu));
    if (size_write < 0) {
    perror("Failed to send data");
    break;
    }

    uint32_t rep_pdu[2]; // 用于接收两个uint32_t组成的数据
    int64_t psych_result;

    ssize_t size_read = read(psych_sock, rep_pdu, sizeof(rep_pdu));
    if (size_read < 0) {
    perror("Failed to receive message");
    break;
    } else if (size_read != sizeof(rep_pdu)) {
    fprintf(stderr, "Incomplete data received\n");
    break;
    }

    // 将接收到的两个uint32_t组合成一个int64_t
    psych_result = ((int64_t)ntohl(rep_pdu[0]) << 32) | ntohl(rep_pdu[1]);

    // 假设op1, op_1, op2是已知的变量,这里直接使用
    printf("[rep_rcv] %ld %c %ld = %ld\n", op1, op_1, op2, psych_result);
    }
    }


    int main(int argc, char const *argv[]) {
    if(argc != 3) {
    printf("Usage: %s <ip> <port>\n", argv[0]);
    return 1;
    }

    //set the server ip and port
    const char* psych_server_ip = argv[1];
    int psych_server_port = atoi(argv[2]);

    struct sockaddr_in psych_serv_addr;
    int psych_sock = 0;


    //create socket
    if((psych_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0){
    perror("socket creation failed");
    exit(EXIT_FAILURE);
    }

    //set the server address
    memset(&psych_serv_addr, '0', sizeof(psych_serv_addr));
    psych_serv_addr.sin_family = AF_INET;
    psych_serv_addr.sin_port = htons(psych_server_port);

    //convert the ip address to binary form
    if(inet_pton(AF_INET, psych_server_ip, &psych_serv_addr.sin_addr) <= 0) {
    printf("\nInvalid address/ Address not supported \n");
    close(psych_sock);
    return -1;
    }

    // Connect to the server
    if (connect(psych_sock, (struct sockaddr *)&psych_serv_addr, sizeof(psych_serv_addr)) < 0) {
    printf("\nConnection Failed \n");
    printf("Error: %s\n", strerror(errno));
    close(psych_sock);
    return -1;
    }
    printf("[cli] server[%s:%d] is connected!\n", psych_server_ip, psych_server_port);

    // Business logic
    psych_cli_calc(psych_sock);

    //close the socket
    close(psych_sock);
    printf("[cli] connfd is closed\n");
    printf("[cli] client os going to exit!\n");
    return 0;
    }

    注意事项

    • pdu 的编写与字节序转换
    • sigint 软中断的处理

    注:最后 client 的 pdu 构造还是有点问题,感觉是因为我传的uint32_t[2],题目要求的是uint64_t,但是不知道为什么 server 也那样传的就没问题。