通信与TCP核心知识

一顺序

OSI七层模型 → TCP/IP四层模型 → 物理层/数据链路层基础 → IP协议 → ARP协议 → ICMP协议 → TCP协议

二OSI七层模型与TCP/IP四层模型

2.1OSI七层模型(理论模型)

层级 名称 核心功能 典型设备 / 协议
7 应用层 为用户应用程序提供网络服务 HTTP、HTTPS、FTP、SMTP、DNS
6 表示层 数据格式转换、加密解密、压缩解压缩 SSL/TLS、JPEG、ASCII
5 会话层 建立、管理和终止会话 会话控制协议
4 传输层 端到端的通信控制 TCP、UDP
3 网络层 路由选择、分组转发 IP、ICMP、ARP、RIP、OSPF
2 数据链路层 帧的封装与解封装、差错检测 以太网、PPP、MAC 地址、交换机
1 物理层 比特流的传输 网线、光纤、集线器、网卡

2.2TCP/IP四层模型(实际使用模型)

OSI 模型过于理想化,实际互联网使用的是 TCP/IP 模型,它将 OSI 的七层合并为四层:

TCP/IP 四层 对应 OSI 七层 核心功能
应用层 应用层 + 表示层 + 会话层 为应用程序提供服务
传输层 传输层 端到端通信
网际层 网络层 路由选择和分组转发
网络接口层 数据链路层 + 物理层 比特流和帧的传输

2.3数据封装与解封装过程

  • 发送方 :数据从应用层向下传递,每经过一层就加上该层的首部(Header),这个过程叫封装
  • 接收方 :数据从物理层向上传递,每经过一层就去掉该层的首部,这个过程叫解封装

三IP协议(网际层核心)

3.1IP地址

  • 作用:唯一标识互联网中的一台主机
  • IPv4 地址:32 位,通常用点分十进制表示,如192.168.1.1
  • 分类:
    • A 类:0.0.0.0 ~ 127.255.255.255(大型网络)
    • B 类:128.0.0.0 ~ 191.255.255.255(中型网络)
    • C 类:192.0.0.0 ~ 223.255.255.255(小型网络)
    • D 类:224.0.0.0 ~ 239.255.255.255(组播)
    • E 类:240.0.0.0 ~ 255.255.255.255(保留)
  • 特殊 IP 地址:
    • 127.0.0.1:回环地址,用于本机测试
    • 0.0.0.0:表示所有网络接口
    • 255.255.255.255:广播地址

3.2子网划分

  • 问题:IP 地址分类过于死板,导致 IP 地址浪费
  • 解决:通过子网掩码将一个大网络划分为多个小网络
  • 子网掩码:32 位,连续的 1 表示网络位,连续的 0 表示主机位
  • 示例:
    • C 类默认子网掩码:255.255.255.0(/24)
    • 划分为 4 个子网:255.255.255.192(/26)

3.3路由

  • 作用:当源主机和目的主机不在同一个网络时,将 IP 数据报从源主机转发到目的主机
  • 路由器:工作在网络层,根据路由表转发 IP 数据报
  • 路由表:包含目的网络地址、下一跳地址、出接口等信息

3.4IP数据报结构

IP 数据报首部固定为 20 字节,可选字段最长 40 字节,因此 IP 首部最大长度为 60 字节。

协议 它的角色 负责的核心问题 类比生活
TCP 协议 内层包装(大主管) 负责质量。确保数据不丢、不乱、完整。 公司里的商务总监,负责跟客户死磕合同,确保一字不差。
IP 协议 外层包装(跑腿员) 负责地址和路线。负责把包裹送到地方。 公司里的外卖小哥,只管照着地址开车送货,不关心盒子里装的是啥。

四ARP协议(地址解析协议)

IP 协议只知道对方的"名字(IP地址)",而 ARP 协议是帮 IP 协议查到对方的"身份证号(MAC地址)",从而让数据真正能在物理网线里传过去。

  • IP 地址(虚拟的、暂时的): 就像你的收件地址 (比如"北京路1号3网段5号工位")。它会随着你搬家(换网络)而改变。IP 协议只认这个地址。

  • MAC 地址(物理的、永久的): 就像你的身份证号 。由网卡生产厂商烧录在芯片里,全球唯一,走到哪都不变。网线、网卡、交换机这些物理硬件,只认 MAC 地址

六ICMP协议(互联网控制消息协议)

还记得我们之前说的"套娃"吗?TCP 是装在 IP 大箱子外壳里面的"内层小盒子"

这就导致了一个致命的逻辑问题:如果外层的 IP 箱子在路上直接被路由器给拆了、丢了,里面的 TCP 盒子根本连重传的机会都没有!

  • TCP 是"端到端"的: 它只住在起始端(你的电脑)和终点端(服务器)。路上的路由器根本不运行 TCP 协议。

  • ICMP 是"网络层"的: 它和 IP 协议是一伙的,专门管马路上的事(路由器和路由器之间、路由器和你的网卡之间)

当包裹在马路上出事时,远在终点的 TCP 根本不知道,这时候必须靠沿途的路由器用 ICMP 协议 大喊一声:"出事啦!

七TCP

八C#中写通信

  • SerialPort 走串口线(COM口)
  • Socket 走 TCP/IP 网络(网口、WiFi、光纤)

1.TCP

在 C# 的 System.NetSystem.Net.Sockets 命名空间里,你主要打交道的是以下三大家族:

① 传输层双雄:TCP vs UDP (最底层的控制)

在 C# 中,如果你想做底层通信,你会直接使用:

  • TcpClient / TcpListener (或者更底层的 Socket): * 为什么选它? 因为正如前面所说,它是"金牌大管家"。你用 C# 写的聊天软件、联机游戏网关、ERP 系统,必须保证数据一个字都不能错、不能丢、顺序不能乱

    • 代价: 速度稍微慢一点点,因为要建立连接(三次握手)。
  • UdpClient

    • 为什么选它? UDP 是 TCP 的死对头("马大哈"协议),它只管发,丢不丢无所谓。在 C# 中写实时视频通话、网络语音、FPS 实时竞技游戏(如射击位置同步)时,会选择 UDP。因为这时候"快"比"百分之百准确"更重要。

② 应用层霸主:HTTP / WebSocket (现代开发最常用)

绝大多数 C# 程序员在日常工作中,其实根本不需要直接去摆弄 TCP 的 Socket。大家用的是基于 TCP 封装好的高级玩具

  • HttpClient 用来调用 Web API(获取 JSON 数据)。你不需要管 TCP 怎么握手,你只需要给它一个 URL。

  • WebSocket / SignalR: 用来实现网页、客户端的即时大屏刷新、消息推送。它解决了传统 TCP 粘包问题,直接给你处理好了一个个"消息帧"。

8.1. TcpClient 的三大核心部件

在 C# 里用 TcpClient 写通信,你其实只需要掌握三个东西:

  • Connect()(踩油门): 负责向服务器发起 TCP 三次握手。你只要给它一个 IP 和 端口(Port),它只要不报错,就说明你和服务器之间的"专属管道"已经铺设成功了。

  • GetStream()(接通水管): 这是 TcpClient 最灵魂的方法。它会返回一个 NetworkStream 对象。在 C# 里,"流(Stream)"就代表着一条畅通无阻的水管

  • Read() / Write()(接水/灌水): * 你想发数据?往 Stream 里 Write(写)二进制字节。

    • 你想收数据?从 Stream 里 Read(读)二进制字节。

8.2实例

cs 复制代码
using System;
using System.Net.Sockets;
using System.Text;

class Program
{
    static void Main()
    {
        try
        {
            // 1. 创建客户端并连接服务器(这一步操作系统自动帮你完成了 TCP 三次握手!)
            // 自动把对方的 IP 填入外层的 IP 协议大箱子
            TcpClient client = new TcpClient("127.0.0.1", 8888);
            Console.WriteLine("连接服务器成功!");

            // 2. 获取网络水管(NetworkStream)
            NetworkStream stream = client.GetStream();

            // 3. 发送消息:把大白话字符串转成计算机认的字节数组(Byte[])
            string message = "你好,服务器!";
            byte[] dataToSend = Encoding.UTF8.GetBytes(message);
            stream.Write(dataToSend, 0, dataToSend.Length); // 灌水,数据发射出去!
            Console.WriteLine($"已发送: {message}");

            // 4. 接收服务器的回应
            byte[] buffer = new byte[1024]; // 准备一个 1024 字节的脸盆接水
            int bytesRead = stream.Read(buffer, 0, buffer.Length); // 阻塞等待,直到水流进来
            
            string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            Console.WriteLine($"收到服务器回复: {response}");

            // 5. 关机,拆除管道
            stream.Close();
            client.Close();
        }
        catch (Exception e)
        {
            Console.WriteLine($"发生错误: {e.Message}");
        }
    }
}

TcpClient 对象有什么用

连,发,收

  1. 连接相关
  • new TcpClient(ip, port):直接构造并自动发起连接(三次握手)。
  • client.Connect(ip, port):先创建对象,再手动连接。
  • client.Connected:判断是否连上。
  • client.Close() / Dispose():断开连接、释放端口Microsoft Learn。
  1. 数据收发(最重要)
  • NetworkStream stream=client.GetStream():拿到读写流 ,用来发数据、收数据Microsoft Learn。
    • stream.Write(字节数组):发数据给服务器Microsoft Learn。
    • stream.Read(字节数组):读服务器发过来的数据Microsoft Learn。
  1. 辅助属性
  • Available:当前有多少数据可读。
  • Client:拿到底层 Socket,做更底层操作。

简单说:TcpClient = 连接管理器 + 数据收发器

8.3两大2问题

因为 TCP 的本质特性 会在应用层(你的 C# 代码里)引发两个著名的现象:

🕳️ 巨坑一:面向字节流 ------ 著名的"粘包"与"分包"

这是初学者 99% 会崩溃的地方。 假设你在客户端连续调用了两次 stream.Write()

  • 第一次发:"Hello"

  • 第二次发:"World"

你在服务器端用 stream.Read() 去接水的时候,你一厢情愿地以为会收到两次,第一次是 Hello,第二次是 World。

然而现实是: 服务器可能一次性接到了 "HelloWorld"粘包 );或者第一次接到了 "Hell",第二次接到了 "oWorld"分包)。

原因: 因为底层的 TCP 协议是面向字节流的。它就像一根水管,你灌进去两杯水,水在管子里融为一体了,流出来的时候就是一股水流,它根本不知道哪滴水属于第一杯,哪滴属于第二杯!

解决办法: 你必须在 C# 代码里自己定规则。比如**"定长法"(规定每个消息必须是 100 字节,不够用空格补齐),或者"带包头法"**(每次发正文前,先雷打不动地发 4 个字节,告诉对方后面正文有多长)。

🕳️ 巨坑二:Read() 是个"老实人" ------ 阻塞(Blocking)

看这句代码:int bytesRead = stream.Read(buffer, 0, buffer.Length); 如果服务器此时开小差,没有给你发任何数据,你的 C# 程序运行到这一行时,会死死卡在这里,整个线程一动不动

  • 如果你在 UI 界面(比如 WinForm/WPF)的主线程里这么写,你的软件界面会直接卡死未响应

  • 解决办法: 在现代 C# 开发中,我们一律推荐使用它的异步版本await stream.ReadAsync(...)await stream.WriteAsync(...)。利用 C# 的 async/await 机制,让线程在等水流进来的时候去干别的事,网页和界面就不会卡顿

2.UDP

2.1特点

  • 没有三次握手(省心): TcpClient 发数据前,必须先死等 Connect() 成功。而 UdpClient 不需要连接 !你想给谁发,把数据打包好,填上对方的 IP 和端口,一脚油门直接 Send 发射出去。至于对方开没开机、能不能收到,它一概不管。

  • 没有连贯的水管(数据报形式): TCP 是管道里的"水流",会粘包。而 UDP 发送的是"大石块"(数据报)。你 Send 一个 "Hello",对方 Receive 到的绝对就是一个完完整整的 "Hello"天然绝不粘包

  • 缺点(提心吊胆): 它在物理马路上被路由器挤掉了就是真掉了,它不会自动重传。

2.2UDP简易通信

因为 UDP 不需要握手,所以在 C# 里,你甚至不需要像 TCP 那样分出严格的"服务端"和"客户端"结构,大家都是对等的 UdpClient

我们直接用一个程序(用多线程模拟接收和发送)

cs 复制代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        // 创建一个 UDP 客户端,绑定本机 9999 端口(专门用来接收别人发给 9999 的数据)
        UdpClient udpNode = new UdpClient(9999);
        Console.WriteLine("UDP 节点已启动,正在监听 9999 端口...");

        // 1. 开启一个线程,死循环【接收】数据
        Task.Run(() =>
        {
            while (true)
            {
                // IPEndPoint 用来记录"这块石头是谁扔给我的"
                IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
                
                // 核心:Receive 直接接住一整块"数据石头",并把对方的地址存入 remoteEP
                byte[] receivedData = udpNode.Receive(ref remoteEP); 
                
                string msg = Encoding.UTF8.GetString(receivedData);
                Console.WriteLine($"\n[收到来自 {remoteEP} 的导弹]: {msg}");
            }
        });

        // 2. 主线程用来【发送】数据
        Console.WriteLine("按下回车,给本机的 9999 端口发射一颗 UDP 导弹...");
        Console.ReadLine();

        string message = "乌拉!这是一颗 UDP 导弹!";
        byte[] dataToSend = Encoding.UTF8.GetBytes(message);

        // 核心:直接 Send!直接指定目的地 IP 和端口,不需要提前 Connect!
        udpNode.Send(dataToSend, dataToSend.Length, "127.0.0.1", 9999);
        Console.WriteLine("导弹已发射(不管对方接没接住)!");

        Console.ReadLine();
    }
}
相关推荐
开开心心_Every1 小时前
多连接方式的屏幕共享工具推荐
运维·服务器·pdf·电脑·excel·tornado·dash
AskHarries1 小时前
Workspace:文件系统、项目上下文和执行边界
java·服务器·前端
liulilittle1 小时前
我从 BBRv1 到 KCC 的思考
网络·c++·tcp/ip·计算机网络·tcp·bbr·通信
落羽的落羽1 小时前
【项目】JsonRpc框架——开发实现1(细节功能、字段定义、抽象层、具象层)
linux·服务器·网络·c++·人工智能·算法·机器学习
shixuzhimeng2 小时前
FTP服务器项目
linux·网络·ftp
剑神一笑2 小时前
Linux chown 命令详解:从 inode 到实战
linux·运维·服务器
STDD2 小时前
Linux cgroup v2 资源控制实战:限制进程 CPU/内存/IO,systemd slice 管理
linux·运维·服务器
Latticy2 小时前
内网渗透-横向移动-密码喷洒攻击和域内用(kerbrute使用)
运维·服务器·网络·内网渗透·内网