73.总结
73.1 知识点

学习的主要内容

必须达到的水平

学会举一反三

学会了这些对于我们的意义

73.2 核心要点速览
网络基本概念
网络类型对比:
| 类型 | 范围 | 典型场景 |
|---|---|---|
| 局域网(LAN) | 小范围(家庭、学校、公司) | 内部文件共享、打印机共享 |
| 以太网 | 局域网的一种实现方式 | 有线局域网标准 |
| 城域网(MAN) | 城市范围 | 城市宽带网络 |
| 广域网(WAN) | 跨地区、跨国 | 企业跨地域互联 |
| 互联网(Internet) | 全球范围 | 全球信息互联 |
| 万维网(WWW) | 基于互联网的信息系统 | 网页浏览 |
- 网络本质:由若干设备和连接设备的链路构成,数据以二进制电脉冲形式传输
- 拓扑结构:星型、总线型、环型、树型、网状等,各有优缺点
IP地址、端口与Mac地址
IP地址:网络中设备的逻辑地址,用于定位网络中的计算机
- IPv4:32位,4字节,如
192.168.1.1 - IPv6:128位,16字节,地址空间更大
- 特殊地址:
127.0.0.1表示本机
端口号:标识计算机上的具体应用程序,范围 0~65535
- 0~1023:知名端口(系统保留)
- 1024~49151:注册端口
- 49152~65535:动态端口
Mac地址:网卡的物理地址,全球唯一,用于局域网内设备识别
三者关系:IP 定位网络中的计算机 → 端口定位计算机上的程序 → Mac 地址用于局域网内的物理寻址
客户端与服务端
| 角色 | 职责 |
|---|---|
| 客户端 | 发起连接请求,向服务端请求数据或服务 |
| 服务端 | 监听连接,处理请求,返回响应 |
游戏服务器职责:
- 玩家数据存储
- 消息转发
- 网络连接管理
- 游戏逻辑处理
数据通信模型
| 模型 | 特点 | 适用场景 |
|---|---|---|
| C/S(Client/Server) | 客户端请求、服务端响应,集中式管理 | 网络游戏、企业应用 |
| B/S(Browser/Server) | 浏览器作为客户端,无需安装客户端软件 | Web 应用 |
| P2P(Peer to Peer) | 对等网络,每个节点既是客户端又是服务端 | 局域网游戏、文件共享 |
游戏架构选择:网络游戏多用 C/S 模型,局域网游戏可用 P2P 模型
网络协议模型
网络协议:设备之间进行网络连接和通信的标准和规则
OSI 七层模型与 TCP/IP 四层模型对比
| OSI 七层模型 | TCP/IP 四层模型 | 主要职能 |
|---|---|---|
| 应用层 | 应用层 | 为应用程序提供服务,定义协议(HTTP、FTP、DNS 等) |
| 表示层 | 应用层 | 数据格式转换、加密解密 |
| 会话层 | 应用层 | 建立、管理、维护会话 |
| 传输层 | 传输层 | 端到端连接,加入端口信息(TCP、UDP) |
| 网络层 | 网络层 | IP 地址选址、路由选择 |
| 数据链路层 | 网络接口层 | 分帧、确定 MAC 地址 |
| 物理层 | 网络接口层 | 传输比特流和电信号 |
程序员关注点:主要工作在应用层和传输层,Socket 编程涉及传输层
TCP 与 UDP 协议对比
| 特性 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接 | 无连接 |
| 可靠性 | 可靠(无差错、不丢失、不重复、有序) | 不可靠 |
| 传输效率 | 较低 | 较高 |
| 连接对象 | 一对一 | 支持一对多、多对多 |
| 适用场景 | 文件传输、消息推送 | 实时游戏、视频直播 |
TCP 三次握手
- 客户端 → 服务端:发送连接请求
- 服务端 → 客户端:授予连接
- 客户端 → 服务端:确认连接
目的:建立可靠的通信连接
TCP 四次挥手
- 客户端 → 服务端:请求断开连接
- 服务端 → 客户端:确认,等待发送剩余数据
- 服务端 → 客户端:数据发送完毕,可以断开
- 客户端 → 服务端:确认断开
目的:优雅地终止连接,确保数据完整传输
网络通信概述
游戏联网类型
| 类型 | 特点 | 典型场景 |
|---|---|---|
| 弱联网 | 偶尔需要网络,离线也能玩 | 单机游戏、休闲游戏 |
| 强联网 | 必须联网,实时交互 | MMO、MOBA、FPS |
连接方式对比
| 类型 | 特点 | 协议 | 适用场景 |
|---|---|---|---|
| 长连接 | 保持连接,实时通信 | TCP/UDP | 实时对战、聊天 |
| 短连接 | 用完即断,按需连接 | HTTP | 数据查询、资源下载 |
通信方式
| 方式 | 特点 | 适用场景 |
|---|---|---|
| Socket | 底层通信,支持 TCP/UDP,实时性强 | 游戏实时通信 |
| HTTP | 请求-响应模式,无状态 | 资源下载、数据接口 |
| FTP | 文件传输协议 | 文件上传下载 |
IP地址和端口类
| 分类 | 详情 |
|---|---|
| IPAddress类 | 命名空间:System.Net 初始化方式: 1. byte数组:byte[] ipAddress = new byte[] { 118, 102, 111, 11 }; IPAddress iPAddress1 = new IPAddress(ipAddress); 2. long长整型:IPAddress iPAddress2 = new IPAddress(0x76666F0B);(不建议使用) 3. 字符串转换:IPAddress iPAddress3 = IPAddress.Parse(“118.102.111.11”);(推荐) 特殊IP地址:127.0.0.1代表本机地址 静态成员:IPAddress.IPv6Any(获取可用的IPv6地址) |
| IPEndPoint类 | 命名空间:System.Net 作用:将网络端点表示为IP地址和端口号,用IP地址和端口号表示计算机的一个程序 初始化方式: 1. long长整型IP和端口号:IPEndPoint iPEndPoint1 = new IPEndPoint(0x79666F0B, 8080); 2. IP地址对象和端口号:IPEndPoint iPEndPoint2 = new IPEndPoint(IPAddress.Parse(“118.102.111.11”), 8080);(推荐) |
| IPHostEntry | 作为域名解析返回值,用于获取IP、主机名等信息。成员变量有:AddressList(获取关联IP)、Aliases(获取主机别名列表)、HostName(获取DNS名称) |
| Dns | 静态类,提供根据域名获取IP的方法。常用方法有:GetHostName(获取本地主机名,如print(Dns.GetHostName());)、GetHostEntry(同步获取IP,如IPHostEntry entry = Dns.GetHostEntry(“www.baidu.com");)、GetHostEntryAsync(异步获取IP,如Task |
网络通信中序列化和反序列化2进制数据
字符编码概述
| 编码 | 特点 | 适用场景 |
|---|---|---|
| ASCII | 128 个字符,1 字节表示 | 英文环境 |
| Unicode | 统一字符集,包含全球符号 | 字符集标准 |
| UTF-8 | Unicode 的变长实现,1~4 字节 | 网络通信推荐 |
乱码原因:编码和解码使用了不同的字符编码规则
序列化/反序列化核心类
| 类名 | 详情 |
|---|---|
| BitConverter | 所在命名空间:System 1. GetBytes():将非字符串类型数据(如int、short、bool等)转换为字节数组,用于类对象二进制序列化时处理非字符串类型变量。 2. ToInt32()、ToInt16()、ToBoolean()等:将字节数组转换为对应的非字符串类型数据(如int、short、bool等),用于类对象二进制反序列化时将字节数组分组转换为对应类型变量。 |
| Encoding | 所在命名空间:System.Text 1. UTF8.GetBytes():将字符串转换为字节数组,在类对象二进制序列化时,先计算字符串字节数组长度并序列化,再序列化字符串实际内容。 2. UTF8.GetString():传入字节数组、起始位置和长度,将字节数组转换为字符串,在类对象二进制反序列化时,先读取字符串字节数组长度,再根据长度和字节数组转换为字符串。 |
序列化流程
- 计算容量:确定字节数组大小(字符串需先计算长度)
- 声明容器:创建对应大小的
byte[] - 依次写入:使用
CopyTo将各字段字节数组写入容器
字符串处理:先写长度(4 字节 int),再写字符串内容
BaseData 序列化基类
自定义序列化基类可封装常用读写方法:
- 写入:
WriteInt、WriteShort、WriteFloat、WriteString、WriteData - 读取:
ReadInt、ReadShort、ReadFloat、ReadString、ReadData<T> - 抽象方法:
GetBytesNum()、Writing()、Reading()
为什么不用 BinaryFormatter:跨语言兼容性差,服务端可能用其他语言
Socket类
| 类名 | 详情 |
|---|---|
| Socket | 命名空间:System.Net.Sockets 作用:支持TCP/IP网络通信的基本操作单位,是网络通信的数据通道,包含本机与对方主机的IP地址和端口、双方通信协议信息,可连接客户端和服务端以收发消息。常用于长连接游戏的通信。 类型: 1. 流套接字:用于TCP通信,提供面向连接、可靠、有序、无差错且无重复的数据传输服务。 2. 数据报套接字:用于UDP通信,提供无连接通信服务,数据包长度不超32KB,不保证正确性和顺序,可能重发、丢失。 3. 原始套接字:用于IP数据包通信,访问协议较低层,用于侦听和分析数据包(不常用)。 构造函数:public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType); 构造函数参数说明: 1. AddressFamily:网络寻址枚举类型,常用InterNetwork(IPv4寻址)、InterNetwork6(IPv6寻址)。 2. SocketType:套接字枚举类型,常用Dgram(用于UDP通信)、Stream(用于TCP通信)。 3. ProtocolType:协议类型枚举类型,常用TCP、UDP。 常用搭配: 1. SocketType.Dgram + ProtocolType.Udp:UDP协议通信。 2. SocketType.Stream + ProtocolType.Tcp:TCP协议通信。 实例化: 1. TCP流套接字:Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 2. UDP数据报套接字:Socket socketUdp = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 常用属性: 1. Connected:套接字的连接状态。 2. SocketType:套接字的类型。 3. ProtocolType:套接字的协议类型。 4. AddressFamily:套接字的寻址方案。 5. Available:套接字可读取字节数。 6. LocalEndPoint:本机EndPoint对象。 7. RemoteEndPoint:远程EndPoint对象。 常用方法: 服务端: 1. Bind:绑定IP和端口,传入IPEndPoint对象。 2. Listen:设置客户端连接的最大数,传入整型数字。 3. Accept:等待客户端连入,返回一个Socket对象。 客户端: 1. Connect:连接远程服务端,传入IPEndPoint对象。 客户端服务端通用: 1. Shutdown:释放连接并关闭Socket,选择Both可同时防止发送和接收。 2. Close:关闭连接,释放所有Socket关联资源。 总结:TCP和UDP两种长连接通信方案基于Socket套接字,需掌握TCP和UDP的Socket套接字声明。 |
TCP同步服务端和客户端基础实现
服务端基本实现
(1)创建套接字Socket对象(TCP):
//1.创建套接字Socket(TCP)
Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
(2)用Bind方法将套接字与本地地址绑定:
//2.用Bind方法将套接字与本地地址绑定
try
{
IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);//把本机作为服务端程序 IP地址传入本机
socketTcp.Bind(iPEndPoint);//绑定
}
catch (Exception e)
{
//如果IP地址不合法或者端口号被占用可能报错
Console.WriteLine("绑定报错" + e.Message);
return;
}
(3)用Listen方法监听:
//3.用Listen方法监听
socketTcp.Listen(1024);//最大接收1024个客户端
Console.WriteLine("服务端绑定监听结束,等待客户端连入");
(4)用Accept方法等待客户端连接,建立连接,Accept返回新套接字:
//5.建立连接,Accept返回新套接字
Socket socketClient = socketTcp.Accept();
//Accept是阻塞式的方法 会把主线程卡住 一定要等到客户端接入后才会继续执行后面的代码
//客户端接入后 返回新的Socket对象 这个新的Socket可以理解为客户端和服务端的通信通道
Console.WriteLine("有客户端连入了");
(5)用Send和Receive相关方法收发数据:
//6.用Send和Receive相关方法收发数据
//发送字符串转成的字节数组给客户端
socketClient.Send(Encoding.UTF8.GetBytes("欢迎连入服务端"));
//声明接收客户端信息的字节数组 声明1024容量代表能接收1kb的信息
byte[] result = new byte[1024];
//接收客户端信息 返回值为接收到的字节数
int receiveNum = socketClient.Receive(result);
//打印 远程发送信息的客户端的IP和端口 以及 发送过来的字符串
Console.WriteLine("接收到了{0}发来的消息:{1}",
socketClient.RemoteEndPoint.ToString(),
Encoding.UTF8.GetString(result, 0, receiveNum));
(6)用Shutdown方法释放连接:
//7.用Shutdown方法释放连接
//注意断开的是客户端和服务端的通信通道
socketClient.Shutdown(SocketShutdown.Both);
(7)关闭套接字:
//8.关闭套接字
//注意关闭的是客户端和服务端的通信通道
socketClient.Close();
客户端实现:
(1)创建套接字Socket Tcp
Socket socketTcp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
(2)用Connect方法与服务端相连:
//确定服务端的IP和端口 正常来说填的应该是远端服务器的ip地址以及端口号
//由于只有一台电脑用于测试 本机也当做服务器 所以传入当前电脑的ip地址
IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
try
{
//连接
socketTcp.Connect(iPEndPoint);
}
catch (SocketException e)
{
//如果连接没有开启或者服务器异常 会报错 不同的返回码代表不同报错
if (e.ErrorCode == 10061)
print("服务器拒绝连接");
else
print("连接服务器失败" + e.ErrorCode);
return;
}
(3)用Send和Receive相关方法收发数据:
//接收数据
//声明接收数据字节数组
byte[] receiveBytes = new byte[1024];
//Receive方法接收数据 返回接收多少字节
int receiveNum = socketTcp.Receive(receiveBytes);
print("收到服务端发来的消息:" + Encoding.UTF8.GetString(receiveBytes, 0, receiveNum));
//发送数据
socketTcp.Send(Encoding.UTF8.GetBytes("你好,我是韬老狮的客户端"));
(4)用Shutdown方法释放连接:
//4.用Shutdown方法释放连接
socketTcp.Shutdown(SocketShutdown.Both);
(5)关闭套接字:
//5.关闭套接字
socketTcp.Close();
多客户端处理与封装设计
核心问题:同步 Socket 的 Accept、Send、Receive 会阻塞主线程
解决思路:
- 使用独立线程处理连接接受和消息收发
- 使用消息队列解耦主线程和网络线程
- 使用线程池处理耗时消息解析
服务端封装要点:
| 类 | 职责 |
|---|---|
| ServerSocket | 管理监听套接字、客户端字典、广播方法 |
| ClientSocket | 封装单个客户端的收发逻辑、自动分配 ID |
客户端封装要点:
| 类 | 职责 |
|---|---|
| TcpNetManager | 单例模式、消息队列、Update 中处理接收消息 |
线程安全:操作客户端字典时需加锁,遍历时不能直接移除元素
区分消息类型
问题:多种消息类型(玩家信息、聊天信息、登录信息等)序列化后都是字节数组,接收方如何区分?
解决方案:在消息头部添加消息 ID(int/short/byte/long)
消息结构:
| 消息ID (4字节) | 消息体内容 |
实现要点:
- 创建
BaseMessage基类继承BaseData,添加GetID()方法 - 具体消息类继承
BaseMessage,实现序列化/反序列化 - 接收时先解析前 4 字节得到 ID,再根据 ID 选择对应类反序列化
int msgID = BitConverter.ToInt32(receiveBytes, 0);
switch (msgID)
{
case 1001:
PlayerMessage msg = new PlayerMessage();
msg.Reading(receiveBytes, 4);
break;
}
分包和粘包
概念:
- 分包:一个消息被拆成多个包发送
- 黏包:多个消息黏在一起发送
- 注意:分包和黏包可能同时发生
解决思路:在消息头部添加消息长度字段
消息结构(解决分包黏包后):
| 消息ID (4字节) | 消息长度 (4字节) | 消息体内容 |
处理逻辑:
- 维护缓存数组
cacheBytes和缓存长度cacheNum - 收到数据后拼接到缓存数组尾部
- 循环解析:先判断是否有完整头部(8字节),再根据消息长度判断是否有完整消息体
- 完整消息:解析并入队;不完整:保留到下次继续处理
关键代码结构:
// 拼接新数据
receiveBytes.CopyTo(cacheBytes, cacheNum);
cacheNum += receiveNum;
while (true)
{
if (cacheNum - nowIndex >= 8) // 可解析头部
{
msgID = BitConverter.ToInt32(cacheBytes, nowIndex);
msgLength = BitConverter.ToInt32(cacheBytes, nowIndex + 4);
}
if (cacheNum - nowIndex >= 8 + msgLength) // 可解析完整消息
{
// 解析消息体...
nowIndex += 8 + msgLength;
}
else // 分包:缓存剩余数据
{
Array.Copy(cacheBytes, nowIndex, cacheBytes, 0, cacheNum - nowIndex);
cacheNum -= nowIndex;
break;
}
}
TCP同步退出消息和心跳消息
客户端主动断开连接
问题:客户端直接调用 Shutdown 和 Close 后,服务端无法及时感知
解决方案:
- 客户端调用
Disconnect(false)后再Close() - 发送自定义退出消息
QuitMessage通知服务端
服务端处理要点:
- 操作客户端字典时加
lock保证线程安全 - 使用
delList暂存待移除客户端,遍历结束后统一处理 - 收发消息时检测
Connected状态
心跳消息
概念:长连接中定期发送的特殊数据包,用于确认对方在线
作用:
- 检测非正常断开的客户端(超时判断)
- 保持连接活跃,防止防火墙/路由器断开
实现要点:
- 客户端:定时发送
HeartMessage(如每 2 秒) - 服务端:记录上次收到消息时间,超时则断开
// 客户端定时发送
InvokeRepeating("SendHeartMsg", 0, SEND_HEART_MSG_TIME);
// 服务端超时检测
if (frontTime != -1 &&
DateTime.Now.Ticks / TimeSpan.TicksPerSecond - frontTime >= TIME_OUT_TIME)
{
// 超时,断开连接
}
TCP异步通信
同步 vs 异步
| 特性 | 同步方法 | 异步方法 |
|---|---|---|
| 执行方式 | 方法执行完毕后才继续 | 方法可能未执行完就继续 |
| 阻塞 | 阻塞当前线程 | 不阻塞,内部开多线程 |
| 适用场景 | 简单逻辑、控制台程序 | 游戏客户端、高并发服务端 |
两种异步 API 风格
Begin/End 系列:
BeginAccept+EndAccept:服务端接受连接BeginConnect+EndConnect:客户端连接BeginReceive+EndReceive:接收消息BeginSend+EndSend:发送消息- 回调函数参数类型:
IAsyncResult - 获取 Socket:
asyncResult.AsyncState as Socket
Async 系列:
AcceptAsync、ConnectAsync、SendAsync、ReceiveAsync- 参数类型:
SocketAsyncEventArgs - 回调事件:
Completed - 设置缓冲区:
SetBuffer(bytes, offset, count)
异步服务端封装要点
| 类 | 职责 |
|---|---|
| ServerSocket | 管理监听套接字、客户端字典、BeginAccept |
| ClientSocket | 封装单个客户端的异步收发、心跳检测 |
注意:异步方法中分包黏包处理与同步相同,但接收后数据直接在 cacheBytes 中
Socket类TCP异步常用成员
| 类名 | 方法 | 参数 | 详情 |
|---|---|---|---|
| Socket | BeginAccept(AsyncCallback callback, object state) | callback:处理异步接受客户端连接操作完成时的回调函数;state:服务端Socket对象 | 服务端开始接收客户端的连接。 |
| Socket | EndAccept(IAsyncResult asyncResult) | asyncResult:BeginAccept方法返回的IAsyncResult对象 | 服务端检测到客户端的连接结束,得到客户端Socket。 |
| Socket | BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state) | remoteEP:服务器的终结点(如IPEndPoint);callback:处理客户端异步连接操作完成时的回调函数;state:客户端的Socket对象 | 客户端异步连接到服务器。 |
| Socket | EndConnect(IAsyncResult asyncResult) | asyncResult:BeginConnect方法返回的IAsyncResult对象 | 客户端完成异步连接到服务器端的操作。 |
| Socket | BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state) | buffer:接收消息的字节数组;offset:从接收消息的字节数组第几位开始存储;size:字节数组长度;socketFlags:Socket标识;callback:接收消息回调函数;state:Socket对象 | 开始接收消息。 |
| Socket | EndReceive(IAsyncResult asyncResult) | asyncResult:BeginReceive方法返回的IAsyncResult对象 | 结束接收消息,返回接收的字节数组长度。 |
| Socket | BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state) | buffer:发送消息的字节数组;offset:从发送消息的字节数组第几位开始发送;size:字节数组长度;socketFlags:Socket标识;callback:发送消息回调函数;state:Socket对象 | 开始发送消息。 |
| Socket | EndSend(IAsyncResult asyncResult) | asyncResult:BeginSend方法返回的IAsyncResult对象 | 结束发送消息,返回成功发送的字节数。 |
| Socket | AcceptAsync(SocketAsyncEventArgs e) | e:SocketAsyncEventArgs对象,用于处理异步接受客户端连接操作完成后的操作 | 服务端异步接受客户端连接。 |
| Socket | ConnectAsync(SocketAsyncEventArgs e) | e:SocketAsyncEventArgs对象,用于处理异步连接到服务器操作完成后的操作 | 客户端异步连接到服务器。 |
| Socket | SendAsync(SocketAsyncEventArgs e) | e:SocketAsyncEventArgs对象,设置好发送数据的缓冲区等信息,用于处理异步发送消息操作完成后的操作 | 异步发送消息。 |
| Socket | ReceiveAsync(SocketAsyncEventArgs e) | e:SocketAsyncEventArgs对象,设置好接收数据的缓冲区等信息,用于处理异步接收消息操作完成后的操作 | 异步接收消息。 |
UDP通信
UDP vs TCP 核心区别
| 特性 | UDP | TCP |
|---|---|---|
| 连接方式 | 无连接,直接发 | 面向连接,需三次握手 |
| 可靠性 | 不保证送达、不保证顺序 | 可靠传输、有序 |
| 消息边界 | 保留消息边界,无黏包 | 流式传输,存在分包黏包 |
| 资源消耗 | 低 | 高 |
| 适用场景 | 实时游戏、音视频直播 | 文件传输、消息推送 |
UDP 编程流程
服务端/客户端通用流程:
- 创建套接字:
Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) - 绑定本机地址:
Bind(IPEndPoint) - 收发消息:
SendTo/ReceiveFrom - 关闭:
Shutdown+Close
UDP 关键特点
无黏包:UDP 保留消息边界,每条消息独立
分包限制:
- 互联网:单包 ≤ 548 字节
- 局域网:单包 ≤ 1472 字节
- 超过限制会自动分包,可能导致丢包
安全注意:商业游戏中通常先建立 TCP 连接验证身份,再用 UDP 通信
UDP 同步收发示例
//1.创建套接字 寻址类型还是Ipv4 Soket类型使用数据包 协议选择Udp
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//2.绑定本机地址 注意模拟测试时客户端本机和服务端远程机的端口号不能一样
IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
socket.Bind(ipPoint);
//3.发送到指定目标 注意模拟测试时客户端本机和服务端远程机的端口号不能一样
IPEndPoint remoteIpPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
//指定要发送的字节数 和 远程计算机的 IP和端口
socket.SendTo(Encoding.UTF8.GetBytes("韬老狮来了"), remoteIpPoint);
//4.接收消息
//装接收消息的字节数组
byte[] bytes = new byte[512];
//装接收消息的IP地址和端口的对象 在ReceiveFrom方法会使用ref赋值 主要是用来记录 谁发的信息给你 传入函数后 在内部 它会帮助我们进行赋值
EndPoint remoteIpPoint2 = new IPEndPoint(IPAddress.Any, 0);
//接收消息 得到消息长度
int length = socket.ReceiveFrom(bytes, ref remoteIpPoint2);
print("IP:" + (remoteIpPoint2 as IPEndPoint).Address.ToString() +
"port:" + (remoteIpPoint2 as IPEndPoint).Port +
"发来了" +
Encoding.UTF8.GetString(bytes, 0, length));
//5.释放关闭
socket.Shutdown(SocketShutdown.Both);
socket.Close();
实现UDP服务端通信收发字符串
//1.创建套接字 寻址类型还是Ipv4 Soket类型使用数据包 协议选择Udp
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
//2.绑定本机地址 注意模拟测试时客户端本机和服务端远程机的端口号不能一样
IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081);
socket.Bind(ipPoint);
Console.WriteLine("服务器开启");
//3.接收消息
//装接收消息的字节数组
byte[] bytes = new byte[512];
//装接收消息的IP地址和端口的对象 在ReceiveFrom方法会使用ref赋值 主要是用来记录 谁发的信息给你 传入函数后 在内部 它会帮助我们进行赋值
EndPoint remoteIpPoint2 = new IPEndPoint(IPAddress.Any, 0);
int length = socket.ReceiveFrom(bytes, ref remoteIpPoint2);
Console.WriteLine("IP:" + (remoteIpPoint2 as IPEndPoint).Address.ToString() +
"port:" + (remoteIpPoint2 as IPEndPoint).Port +
"发来了" +
Encoding.UTF8.GetString(bytes, 0, length));
//4.发送到指定目标
//由于服务端先收了消息 所以服务端已经知道谁发了消息给服务端 是使用remoteIpPoint2记录的 服务端直接发给它就行了
socket.SendTo(Encoding.UTF8.GetBytes("欢迎发送消息给服务器"), remoteIpPoint2);
//5.释放关闭
socket.Shutdown(SocketShutdown.Both);
socket.Close();
Socket类UDP异步常用方法
| 类名 | 方法 | 参数 | 详情 |
|---|---|---|---|
| Socket | BeginSendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, EndPoint remoteEP, AsyncCallback callback, object state) | buffer:要发送的数据字节数组;offset:从字节数组的哪个位置开始发送;size:发送数据的长度;socketFlags:SocketFlags标识;remoteEP:目标IP和端口号;callback:回调函数;state:回调函数的参数 | 开始向指定IP和端口异步发送数据,将结果异步传回回调函数进行处理 |
| Socket | EndSendTo(IAsyncResult asyncResult) | asyncResult:BeginSendTo方法返回的IAsyncResult对象 | 结束异步发送操作 |
| Socket | BeginReceiveFrom(byte[] buffer, int offset, int size, SocketFlags socketFlags, ref EndPoint remoteEP, AsyncCallback callback, object state) | buffer:缓冲区;offset:缓冲区的起始位置;size:最大接收数据长度;socketFlags:SocketFlags标识;remoteEP:接收数据的来源IPEndPoint;callback:回调函数;state:回调函数的参数 | 开始异步从UDP客户端接收数据,将接收到的数据放入缓冲区,接收数据的来源ip和端口号会被保存,有数据到达时调用回调函数处理 |
| Socket | EndReceiveFrom(IAsyncResult asyncResult, ref EndPoint remoteEP) | asyncResult:BeginReceiveFrom方法返回的IAsyncResult对象;remoteEP:接收数据的来源IPEndPoint | 结束异步接收操作,并返回接收到的字节数 |
| Socket | SendToAsync(SocketAsyncEventArgs e) | e:SocketAsyncEventArgs对象,设置好要发送的数据、目标IP地址等信息 | 开始向指定IP和端口发送数据,发送完成后在回调函数处理 |
| Socket | ReceiveFromAsync(SocketAsyncEventArgs e) | e:SocketAsyncEventArgs对象,设置好接收数据的缓冲区、接收来源的IPEndPoint等信息 | 开始异步从UDP客户端接收数据,接收完成后在回调函数进行处理 |
FTP
| 类名 | 用途/功能 | 重要方法 | 重要成员 |
|---|---|---|---|
| NetworkCredential(System.Net) | 在网络身份验证中存储用户名和密码信息,用于 FTP 文件传输场景下提供访问 FTP 服务器所需的认证凭据 | 无 | 无 |
| FtpWebRequest(System.Net) | 用于执行上传、下载、删除 FTP 服务器上的文件等操作 | 1. Create:创建新 Ftp 请求。 2. Abort:终止 Ftp 传输。 3. GetRequestStream:获取用于上传的流。 4. GetResponse:返回 FTP 服务器响应。 |
1. Credentials:设置通信凭证为NetworkCredential对象。 2. KeepAlive:设置完成请求时是否关闭到 FTP 服务器的控制连接。 3. Method:设置 FTP 请求操作命令,如删除、下载、列目录等。 4. UseBinary:指定是否采用二进制模式传输数据。 5. RenameTo:重命名文件。 |
| FtpWebResponse(System.Net) | 封装 FTP 服务器对请求的响应,提供操作状态及从服务器下载的数据,用于表示响应信息,含响应代码、消息等 | 1. Close:释放所有资源。 2. GetResponseStream:返回从 FTP 服务器下载数据的流。 |
1. ContentLength:接收到数据的长度。 2. ContentType:接收数据的类型。 3. StatusCode:FTP 服务器下发的最新状态码。 4. StatusDescription:状态代码的文本描述。 5. BannerMessage:登录前建立连接时服务器发送的消息。 6. ExitMessage:获取 FTP 会话结束时服务器发送的消息。 7. LastModified:获取 FTP 服务器上文件的最后修改日期和时间。 |
FTP上传文件
try
{
// 创建一个Ftp连接,pic.png代表想上传名叫pic的png图片。
// 这里的ftp://127.0.0.1是使用本机开启服务器进行测试,实际使用时应该是远端服务器IP。
FtpWebRequest ftpWebRequest = FtpWebRequest.Create(new Uri("ftp://127.0.0.1/pic.png")) as FtpWebRequest;
// 设置通信凭证(如果不支持匿名,就必须设置这一步)。
// 将代理相关信息置空,避免服务器同时有http相关服务造成冲突。
ftpWebRequest.Proxy = null;
// 创建并设置通信凭证。
NetworkCredential networkCredential = new NetworkCredential("MrTao", "MrTao");
ftpWebRequest.Credentials = networkCredential;
// 请求完毕后是否关闭控制连接,如果想要关闭,可以设置为false。
ftpWebRequest.KeepAlive = false;
// 设置操作命令。
ftpWebRequest.Method = WebRequestMethods.Ftp.UploadFile;//设置命令操作为上传文件。
// 指定传输类型,使用二进制。
ftpWebRequest.UseBinary = true;
// 得到用于上传的流对象。
Stream uploadRequestStream = ftpWebRequest.GetRequestStream();
// 开始上传,使用流读取StreamingAssets文件夹下的名叫test的图片。
using (FileStream fileStream = File.OpenRead(Application.streamingAssetsPath + "/test.png"))
{
// 我们可以一点一点的把这个文件中的字节数组读取出来,然后存入到上传流中。
byte[] bytes = new byte[1024];
// 返回值是真正从文件中读了多少个字节。
int contentLength = fileStream.Read(bytes, 0, bytes.Length);
// 不停的去读取文件中的字节,除非读取完毕了,不然一直读,并且写入到上传流中。
while (contentLength != 0)
{
// 写入上传流中。
uploadRequestStream.Write(bytes, 0, contentLength);
// 写完了继续读。
contentLength = fileStream.Read(bytes, 0, bytes.Length);
}
// 出了循环就证明写完了。
fileStream.Close();
uploadRequestStream.Close();
// 上传完毕。
print("上传结束");
}
}
catch (Exception e)
{
print("上传出错,失败" + e.Message);
}
FTP下载文件
try
{
// 创建一个Ftp连接。
// 这里和上传不同,上传的文件名是自己定义的,下载的文件名一定是资源服务器上有的,比如一张叫pic的图片。
FtpWebRequest ftpWebRequest = FtpWebRequest.Create(new Uri("ftp://127.0.0.1/pic.png")) as FtpWebRequest;
// 设置通信凭证(如果不支持匿名,就必须设置这一步)。
ftpWebRequest.Credentials = new NetworkCredential("MrTao", "MrTao");
// 请求完毕后是否关闭控制连接,如果要进行多次操作,可以设置为false。
ftpWebRequest.KeepAlive = false;
// 设置操作命令。
ftpWebRequest.Method = WebRequestMethods.Ftp.DownloadFile;
// 指定传输类型。
ftpWebRequest.UseBinary = true;
// 代理设置为空。
ftpWebRequest.Proxy = null;
// 得到用于下载的流对象。
// 相当于把请求发送给FTP服务器,返回值就会携带我们想要的信息。
FtpWebResponse ftpWebResponse = ftpWebRequest.GetResponse() as FtpWebResponse;
// 这就是下载的流。
Stream downloadResponseStream = ftpWebResponse.GetResponseStream();
// 开始下载。
print(Application.persistentDataPath);
using (FileStream fileStream = File.Create(Application.persistentDataPath + "/pic2.png"))
{
// 读取流的字节数组。
byte[] bytes = new byte[1024];
// 读取下载下来的流数据。
int contentLength = downloadResponseStream.Read(bytes, 0, bytes.Length);
// 一点一点的下载到本地流中。
while (contentLength != 0)
{
// 把读取出来的字节数组写入到本地文件流中。
fileStream.Write(bytes, 0, contentLength);
// 继续读。
contentLength = downloadResponseStream.Read(bytes, 0, bytes.Length);
}
// 下载结束,关闭流。
downloadResponseStream.Close();
fileStream.Close();
}
print("下载结束");
}
catch (Exception e)
{
print("下载出错" + e.Message);
}
HTTP通信
HTTP 工作原理
HTTP 本质:基于 TCP 的通信协议
通信流程:建立连接 → 请求 → 响应 → 断开连接
请求类型:
- GET:获取数据
- POST:上传数据,同时可获取响应
- HEAD:只获取消息头,不返回具体内容
常见状态码:
- 200:成功
- 404:找不到目标
- 500:服务器内部错误
HTTP 关键类
| 类名(命名空间) | 用途 | 重要方法 | 重要成员 |
|---|---|---|---|
| HttpWebRequest(System.Net) | 用于向服务器发送HTTP客户端请求,进行消息通信、上传、下载等操作 | 1. Create:创建新的WebRequest,用于HTTP相关操作 2. Abort:终止文件传输 3. GetRequestStream:获取用于上传的流 4. GetResponse:返回HTTP服务器响应 5. Begin/EndGetRequestStream:异步获取用于上传的流 6. Begin/EndGetResponse:异步获取返回的HTTP服务器响应 |
1. Credentials:通信凭证,设置为NetworkCredential对象 2. PreAuthenticate:是否随请求发送一个身份验证标头,一般需身份验证时设为true 3. Headers:构成标头的名称/值对的集合 4. ContentLength:发送信息的字节数,上传信息时需先设置 5. ContentType:POST请求时,需对发送内容进行内容类型设置 6. Method:操作命令设置,如Get、Post、Head等 |
| HttpWebResponse(System.Net) | 用于获取服务器反馈信息,通过HttpWebRequest对象的GetResponse()方法获取,使用完毕用Close释放 | 1. Close:释放所有资源 2. GetResponseStream:返回从HTTP服务器下载数据的流 |
1. ContentLength:接收到数据的长度 2. ContentType:接收数据的类型 3. StatusCode:HTTP服务器下发的最新状态码 4. StatusDescription:HTTP服务器下发的状态代码的文本 5. BannerMessage:登录前建立连接时HTTP服务器发送的消息 6. ExitMessage:HTTP会话结束时服务器发送的消息 7. LastModified:HTTP服务器上的文件的上次修改日期和时间 |
| NetworkCredential、Uri、Stream、FileStream(相关命名空间) | 在HTTP通讯时使用方式与FTP类似 | - | - |
HTTP 下载数据
Head 请求类型:检测资源可用性
HttpWebRequest httpWebRequest = HttpWebRequest.Create(new Uri("http://192.168.1.101:8000/HTTPRoot/pic.png")) as HttpWebRequest;
httpWebRequest.Method = WebRequestMethods.Http.Head;
httpWebRequest.Timeout = 2000;
HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
if (httpWebResponse.StatusCode == HttpStatusCode.OK)
{
print("文件存在且可用");
print(httpWebResponse.ContentLength);
print(httpWebResponse.ContentType);
}
Get 请求类型:下载资源到本地
HttpWebRequest httpWebRequest = HttpWebRequest.Create(new Uri("http://192.168.1.101:8000/HTTPRoot/pic.png")) as HttpWebRequest;
httpWebRequest.Method = WebRequestMethods.Http.Get;
httpWebRequest.Timeout = 3000;
HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
if (httpWebResponse.StatusCode == HttpStatusCode.OK)
{
using (FileStream fileStream = File.Create(Application.persistentDataPath + "/httpPic.png"))
{
Stream downloadResponseStream = httpWebResponse.GetResponseStream();
byte[] bytes = new byte[2048];
int contentLength = downloadResponseStream.Read(bytes, 0, bytes.Length);
while (contentLength != 0)
{
fileStream.Write(bytes, 0, contentLength);
contentLength = downloadResponseStream.Read(bytes, 0, bytes.Length);
}
}
}
Get 请求携带额外参数:
- 格式:
http://域名:端口/虚拟目录/文件名?参数名=参数值&参数名=参数值 - 示例:
http://www.aspxfans.com:8080/news/child/index.asp?boardID=5&ID=24618&page=1
HTTP 上传数据
上传规则:
- ContentType =
multipart/form-data; boundary=边界字符串 - 数据格式:
--边界字符串
Content-Disposition: form-data; name="字段名";filename="服务器文件名"
Content-Type: application/octet-stream
(文件数据体)
--边界字符串--
上传示例:
HttpWebRequest httpWebRequest = HttpWebRequest.Create("http://192.168.1.101:8000/HTTPRoot/") as HttpWebRequest;
httpWebRequest.Method = WebRequestMethods.Http.Post;
httpWebRequest.ContentType = "multipart/form-data;boundary=MrTao";
httpWebRequest.Timeout = 500000;
httpWebRequest.Credentials = new NetworkCredential("MrTao", "MrTao");
httpWebRequest.PreAuthenticate = true;
string head = "--MrTao\r\n" +
"Content-Disposition:form-data;name=\"file\";filename=\"http上传的文件.jpg\"\r\n" +
"Content-Type:application/octet-stream\r\n\r\n";
byte[] headBytes = Encoding.UTF8.GetBytes(head);
byte[] endBytes = Encoding.UTF8.GetBytes("\r\n--MrTao--\r\n");
using (FileStream localFileStream = File.OpenRead(Application.streamingAssetsPath + "/test.png"))
{
httpWebRequest.ContentLength = headBytes.Length + localFileStream.Length + endBytes.Length;
Stream uploadRequestStream = httpWebRequest.GetRequestStream();
uploadRequestStream.Write(headBytes, 0, headBytes.Length);
// 写入文件数据...
uploadRequestStream.Write(endBytes, 0, endBytes.Length);
}
Get 与 Post 对比
| 特性 | Get | Post |
|---|---|---|
| 主要用途 | 获取数据 | 上传数据 |
| 参数位置 | URL 中暴露 | 请求体中隐藏 |
| 数据大小 | 有限制(约 2048 字符) | 无限制 |
| 安全性 | 较低 | 较高 |
| 缓存 | 可缓存 | 不可缓存 |
ContentType 常用类型
| 类型 | 用途 |
|---|---|
application/octet-stream |
通用二进制数据 |
text/plain |
通用文本数据 |
application/x-www-form-urlencoded |
键值对参数 |
multipart/form-data |
复合类型(文件上传) |
WWW
| 类名(命名空间) | 作用 | 常用方法 | 常用变量 |
|---|---|---|---|
| WWW(UnityEngine) | 用于简单访问网页,可进行数据的下载和上传。使用 HTTP 协议时默认请求类型是 Get,进行 Post 上传需配合 WWWForm 类 | 1. WWW:构造函数,用于创建一个 WWW 请求 2. GetAudioClip:从下载数据返回一个音效切片 AudioClip 对象 3. LoadImageIntoTexture:用下载数据中的图像来替换现有的一个 Texture2D 对象 4. LoadFromCacheOrDownload:从缓存加载 AB 包对象,如果该包不在缓存则自动下载存储到缓存中,以便以后直接从本地缓存中加载 |
1. assetBundle:如果加载的数据是 AssetBundle,则可直接获取加载结果 2. audioClip:如果加载的数据是音效切片文件,可直接获取加载结果(新版本用 GetAudioClip 方法) 3. bytes:以字节数组形式获取加载内容 4. bytesDownloaded:已下载的字节数 5. error:下载出错时返回错误消息 6. isDone:判断下载是否完成 7. movie:下载视频时可获取 MovieTexture 类型结果(新版本用 GetMovieTexture 方法) 8. progress:下载进度 9. text:下载数据为字符串时,以字符串形式返回内容 10. texture:下载数据为图片时,以 Texture2D 形式返回加载结果 |
WWWForm
| 类名 | 作用 | 注意事项 | 常用方法 |
|---|---|---|---|
| WWWForm | 在使用 WWW 类下载数据的基础上,若需上传数据,可结合该类使用。主要用于集成数据,可设置上传的参数或者二进制数据,结合使用时主要用 Post 请求类型,通过 Http 协议上传处理 | 使用 WWW 结合 WWWForm 上传数据通常需要与后端程序制定上传规则 | 1. WWWForm:构造函数 2. AddBinaryData:添加二进制数据 3. AddField:添加字段 |
UnityWebRequest
常用上传和获取数据
获取
| 类名 | 方法 | 方法解析 | 参数 |
|---|---|---|---|
| UnityWebRequest | Get | 创建一个用于获取文本或二进制数据的 UnityWebRequest 对象。 | uri:请求资源的 URI 地址,如 “http://192.168.1.101:8000/HTTPRoot/test.txt“ |
| UnityWebRequestTexture | GetTexture | 创建一个用于获取纹理数据的 UnityWebRequest 对象。 | uri:请求纹理资源的 URI 地址,可以是 http、ftp 或 file 协议的地址,如 “http://192.168.1.101:8000/HTTPRoot/pic.png"、"ftp://127.0.0.1/pic.png“ 或 “file://“ + Application.streamingAssetsPath + “/test.png” |
| UnityWebRequestAssetBundle | GetAssetBundle | 创建一个用于获取 AB 包数据的 UnityWebRequest 对象。 | uri:请求 AB 包资源的 URI 地址,如 “http://192.168.1.101:8000/HTTPRoot/model“ |
| UnityWebRequest | SendWebRequest | 发送 Web 请求并等待服务器响应。 | 无 |
| DownloadHandlerTexture | GetContent | 从 UnityWebRequest 对象中获取下载的纹理数据。 | request:UnityWebRequest 对象,用于获取其中的纹理数据 |
| DownloadHandlerAssetBundle | GetContent | 从 UnityWebRequest 对象中获取下载的 AB 包数据。 | request:UnityWebRequest 对象,用于获取其中的 AB 包数据 |
上传
| 类名/接口 | 方法/描述 | 参数 |
|---|---|---|
| IMultipartFormSection | 数据相关类继承的父接口,可使用父类类型的集合来存储子类对象 | 无 |
| MultipartFormDataSection | 用于传递键值对数据的类 | 1. 构造函数(二进制字节数组):MultipartFormDataSection(byte[] data),参数data为二进制字节数组 2. 构造函数(字符串):MultipartFormDataSection(string data),参数data为字符串 3. 构造函数(参数名,参数值,编码类型,资源类型):MultipartFormDataSection(string name, string data, Encoding encoding, string contentType),参数name为参数名,data为参数值(字符串或字节数组形式),encoding为编码类型,contentType为资源类型 4. 构造函数(参数名,字节数组,资源类型):MultipartFormDataSection(string name, byte[] data, string contentType),参数name为参数名,data为字节数组,contentType为资源类型 |
| MultipartFormFileSection | 用于传递文件数据的类 | 1. 构造函数(字节数组):MultipartFormFileSection(byte[] data),参数data为字节数组 2. 构造函数(文件名,字节数组):MultipartFormFileSection(string fileName, byte[] data),参数fileName为文件名,data为字节数组 3. 构造函数(字符串数据,文件名):MultipartFormFileSection(string data, string fileName),参数data为字符串数据,fileName为文件名 4. 构造函数(字符串数据,编码格式,文件名):MultipartFormFileSection(string data, Encoding encoding, string fileName),参数data为字符串数据,encoding为编码格式,fileName为文件名 5. 构造函数(表单名,字节数组,文件名,文件类型):MultipartFormFileSection(string formName, byte[] data, string fileName, string contentType),参数formName为表单名,data为字节数组,fileName为文件名,contentType为文件类型 6. 构造函数(表单名,字符串数据,编码格式,文件名):MultipartFormFileSection(string formName, string data, Encoding encoding, string fileName),参数formName为表单名,data为字符串数据,encoding为编码格式,fileName为文件名 |
| UnityWebRequest | Post方法:创建一个用于使用 POST 请求上传数据的UnityWebRequest对象 Put方法:创建一个用于使用 PUT 请求上传数据的UnityWebRequest对象 SendWebRequest方法:发送 Web 请求并等待服务器响应 |
1. Post方法:UnityWebRequest.Post(string uri, List 2. Put方法:UnityWebRequest.Put(string uri, byte[] data),参数uri为请求的 URI 地址,data为要上传的文件内容(字节数组形式) 3. SendWebRequest方法:无参数 |
| NetWWWManager | UploadFile方法:启动异步上传文件的操作 UploadFileAsync方法:异步上传文件的具体实现 |
1. UploadFile方法:UploadFile(string fileName, string localPath, UnityAction<UnityWebRequest.Result> action),参数fileName为上传后的文件名,localPath为本地要上传文件的路径,action为上传完成后的回调函数 2. UploadFileAsync方法:UploadFileAsync(string fileName, string localPath, UnityAction<UnityWebRequest.Result> action),参数fileName为上传后的文件名,localPath为本地要上传文件的路径,action为上传完成后的回调函数 |
高级上传和获取数据
获取
| 类名 | 方法/属性 | 方法的参数 |
|---|---|---|
| UnityWebRequest | 构造函数 | 无 |
| UnityWebRequest | url | 字符串类型的服务器地址 |
| UnityWebRequest | method | 如UnityWebRequest.kHttpVerbPOST等请求类型常量 |
| UnityWebRequest | timeout | 整数类型的超时时间(单位:毫秒) |
| UnityWebRequest | redirectLimit | 整数类型,0表示不进行重定向 |
| UnityWebRequest | responseCode | 无 |
| UnityWebRequest | result | 无 |
| UnityWebRequest | error | 无 |
| UnityWebRequest | downloadHandler | 实现了DownloadHandler的对象,如DownloadHandlerBuffer等 |
| UnityWebRequest | uploadHandler | 实现了UploadHandler的对象 |
| UnityWebRequest | Get | 字符串类型的请求URL |
| UnityWebRequest | GetTexture | 字符串类型的纹理URL |
| UnityWebRequestAssetBundle | GetAssetBundle | 字符串类型的AssetBundle URL |
| UnityWebRequest | Put | 字符串类型的请求URL和字节数组形式的上传内容 |
| UnityWebRequest | Post | 字符串类型的请求URL和List类型的上传数据列表 |
| UnityWebRequest | isDone | 无 |
| UnityWebRequest | downloadProgress | 无 |
| UnityWebRequest | downloadedBytes | 无 |
| UnityWebRequest | uploadProgress | 无 |
| UnityWebRequest | uploadedBytes | 无 |
| UnityWebRequest | SendWebRequest | 无 |
| DownloadHandlerBuffer | 构造函数 | 无 |
| DownloadHandlerFile | 构造函数 | 字符串类型的本地文件保存路径 |
| DownloadHandlerTexture | 构造函数 | 无 |
| DownloadHandlerAssetBundle | 构造函数 | 字符串类型的请求URL和校验码(ulong类型,不知道则传入0) |
| UnityWebRequestMultimedia | GetAudioClip | 字符串类型的音频文件URL和AudioType类型的音频类型 |
| DownloadHandlerAudioClip | GetContent | UnityWebRequest对象 |
| CustomDownLoadFileHandler | 构造函数(无参) | 无 |
| CustomDownLoadFileHandler | 构造函数(字节数组) | 字节数组类型的参数 |
| CustomDownLoadFileHandler | 构造函数(路径) | 字符串类型的本地存储路径 |
| CustomDownLoadFileHandler | GetData | 无 |
| CustomDownLoadFileHandler | ReceiveData | byte[]类型的数据和int类型的数据长度 |
| CustomDownLoadFileHandler | ReceiveContentLengthHeader | ulong类型的内容长度 |
| CustomDownLoadFileHandler | CompleteContent | 无 |
上传
| 类名 | 方法/属性 | 方法的参数 | 描述 |
|---|---|---|---|
| UnityWebRequest | 构造函数 | string uri, string method:请求的 URL 和 HTTP 请求方法(如 UnityWebRequest.kHttpVerbPOST) | 创建一个 UnityWebRequest 对象,用于网络请求 |
| UnityWebRequest | uploadHandler | 实现了 UploadHandler 的对象,如 UploadHandlerRaw、UploadHandlerFile 等 | 设置上传处理对象,用于处理上传的数据 |
| UnityWebRequest | SendWebRequest | 无 | 发送网络请求,并等待请求返回 |
| UnityWebRequest | result | 无 | 获取网络请求的结果(成功或失败) |
| UploadHandlerRaw | 构造函数 | byte[] data:要上传的字节数组 | 创建一个用于上传字节数组的 UploadHandlerRaw 对象 |
| UploadHandlerRaw | contentType | string 类型的内容类型,如 “类型/细分类型” | 设置上传数据的内容类型,若不设置,默认是 application/octet - stream |
| UploadHandlerFile | 构造函数 | string path:要上传的文件路径 | 创建一个用于上传文件的 UploadHandlerFile 对象 |
Protobuf
Protobuf配置规则
(1)配置文件后缀:统一使用.proto,可通过多个该后缀文件配置。
(2)版本号:第一行需指定,如syntax = “proto3”;,不写默认用proto2。
(3)注释方式:支持//单行注释和/* */多行注释。
(4)命名空间:用package指定,如package 命名空间名; 。
(5)消息类:使用message定义,格式为message 类名 { // 字段声明 }。
(6)成员类型和编号:成员类型包含浮点数(float、double )、整数(int32、int64等)、其他(bool、string、bytes ),每个成员需指定从1开始的唯一编号。
(7)特殊标识:optional表示可选赋值字段;repeated表示数组(required在proto3中已移除 )。
(8)枚举:用enum定义,首个常量必须为0,如enum 枚举名 { 常量1 = 0; 常量2 = 1; }。
(9)默认值:string为空字符串、bytes为空字节、bool为false、数值为0、枚举为0、message在C#中为空。
(10)嵌套:支持消息类和枚举的嵌套定义。
(11)保留字段:使用reserved关键字保留字段,防止已删编号被重新使用。
(12)导入定义:若使用其他配置中的类型,用import “配置文件路径”;导入。
syntax = "proto3"; // 决定了 proto 文档的版本号
// 规则1:版本号
// 规则2:注释方式
// 注释方式一
/* 注释方式二 */
// 规则11:导入定义
// 两个配置在同一路径可以这样写,在不同路径要包含文件夹路径
import "test2.proto";
// 规则3:命名空间
package GamePlayerTest; // 这决定了命名空间
// 规则4:消息类
message TestMsg {
// 规则5:成员类型 和 唯一编号
// 浮点数
// = 1 不代表默认值,而是代表唯一编号,方便我们进行序列化和反序列化的处理
// 规则6:特殊标识
// required: 必须赋值的字段
required float testF = 1; // C# - float
// optional: 可以不赋值的字段
optional double testD = 2; // C# - double
// 变长编码
// 所谓变长,就是会根据数字的大小来使用对应的字节数来存储,1 2 4
// Protobuf 帮助我们优化的部分,可以尽量少的使用字节数来存储内容
int32 testInt32 = 3; // C# - int 它不太适用于来表示负数,请使用 sint32
// 1 2 4 8
int64 testInt64 = 4; // C# - long 它不太适用于来表示负数,请使用 sint64
// 更实用与表示负数类型的整数
sint32 testSInt32 = 5; // C# - int 适用于来表示负数的整数
sint64 testSInt64 = 6; // C# - long 适用于来表示负数的整数
// 无符号 变长编码
// 1 2 4
uint32 testUInt = 7; // C# - uint 变长的编码
uint64 testULong = 8; // C# - ulong 变长的编码
// 固定字节数的类型
fixed32 testFixed32 = 9; // C# - uint 它通常用来表示大于2的28次方的数,比 uint32 更有效,始终是4个字节
fixed64 testFixed64 = 10; // C# - ulong 它通常用来表示大于2的56次方的数,比 uint64 更有效,始终是8个字节
sfixed32 testSFixed32 = 11; // C# - int 始终4个字节
sfixed64 testSFixed64 = 12; // C# - long 始终8个字节
// 其它类型
bool testBool = 13; // C# - bool
string testStr = 14; // C# - string
bytes testBytes = 15; // C# - BytesString 字节字符串
// 数组 List
repeated int32 listInt = 16; // C# - 类似 List<int> 的使用
// 字典 Dictionary
map<int32, string> testMap = 17; // C# - 类似 Dictionary<int, string> 的使用
// 规则7:枚举
// 枚举成员变量的声明需要唯一编码
TestEnum testEnum = 18;
// 规则8:默认值
// 声明自定义类对象 需要唯一编码
// 默认值是 null
TestMsg2 testMsg2 = 19;
// 规则9:允许嵌套
// 嵌套一个类在另一个类当中,相当于是内部类
message TestMsg3 {
int32 testInt32 = 1;
}
TestMsg3 testMsg3 = 20;
// 规则9:允许嵌套
enum TestEnum2 {
NORMAL = 0; // 第一个常量必须映射到0
BOSS = 1;
}
TestEnum2 testEnum2 = 21;
// 规则10:保留字段
// int32 testInt3233333 = 22;
bool testBool2123123 = 23;
// 告诉编译器 22 被占用,不准用户使用
// 之所以有这个功能,是为了在版本不匹配时,反序列化时,不会出现结构不统一,解析错误的问题
reserved 22;
reserved testInt3233333;
// 规则11:导入定义
GameSystemTest.HeartMsg testHeart = 24;
}
// 规则7:枚举的声明
enum TestEnum {
NORMAL = 0; // 第一个常量必须映射到0
BOSS = 5;
}
// 规则8:默认值
message TestMsg2 {
int32 testInt32 = 1;
}
syntax = "proto3"; // 决定了 proto 文档的版本号
package GameSystemTest; // 这决定了命名空间
message HeartMsg {
int64 time = 1;
}
Protobuf协议使用
序列化存储为本地文件
主要使用方法:生成的类中的 WriteTo 方法和文件流 FileStream 对象。
示例代码:
TestMsg testMsg1 = new TestMsg();
testMsg1.ListInt.Add(1);
testMsg1.TestBool = false;
testMsg1.TestD = 5.5;
testMsg1.TestInt32 = 99;
testMsg1.TestMap.Add(1, "杨");
testMsg1.TestMsg2 = new TestMsg2();
testMsg1.TestMsg2.TestInt32 = 88;
testMsg1.TestMsg3 = new TestMsg.Types.TestMsg3();
testMsg1.TestMsg3.TestInt32 = 66;
testMsg1.TestHeart = new GameSystemTest.HeartMsg();
testMsg1.TestHeart.Time = 7777;
print(Application.persistentDataPath);
using (FileStream fileStream = File.Create(Application.persistentDataPath + "/TestMsg.tao"))
{
testMsg1.WriteTo(fileStream);
}
代码解释:先创建 TestMsg 实例并设置其属性值,然后使用 File.Create 方法创建一个文件流,将 TestMsg 实例序列化后写入该文件流,实现数据的本地存储。
反序列化本地文件
主要使用方法:生成的类中的 Parser.ParseFrom 方法和文件流 FileStream 对象。
示例代码:
using (FileStream fileStream = File.OpenRead(Application.persistentDataPath + "/TestMsg.tao"))
{
TestMsg testMsg2 = null;
testMsg2 = TestMsg.Parser.ParseFrom(fileStream);
print(testMsg2.TestMap[1]);
print(testMsg2.ListInt[0]);
print(testMsg2.TestD);
print(testMsg2.TestMsg2.TestInt32);
print(testMsg2.TestMsg3.TestInt32);
print(testMsg2.TestHeart.Time);
}
代码解释:使用 File.OpenRead 方法打开之前存储的文件流,然后调用 Parser.ParseFrom 方法从文件流中反序列化出 TestMsg 实例,最后打印该实例的属性值。
得到序列化后的字节数组
主要使用方法:生成的类中的 WriteTo 方法和内存流 MemoryStream 对象。
示例代码:
byte[] bytes = null;
using (MemoryStream memoryStream = new MemoryStream())
{
testMsg1.WriteTo(memoryStream);
bytes = memoryStream.ToArray();
print("字节数组长度" + bytes.Length);
}
代码解释:创建一个内存流,将 TestMsg 实例序列化后写入该内存流,再通过 ToArray 方法将内存流中的数据转换为字节数组,方便进行网络传输。
从字节数组反序列化
主要使用方法:生成的类中的 Parser.ParseFrom 方法和内存流 MemoryStream 对象。
示例代码:
using (MemoryStream memoryStream = new MemoryStream(bytes))
{
print("内存流当中反序列化的内容");
TestMsg testMsg2 = TestMsg.Parser.ParseFrom(memoryStream);
print(testMsg2.TestMap[1]);
print(testMsg2.ListInt[0]);
print(testMsg2.TestD);
print(testMsg2.TestMsg2.TestInt32);
print(testMsg2.TestMsg3.TestInt32);
print(testMsg2.TestHeart.Time);
}
代码解释:使用包含字节数组的内存流,调用 Parser.ParseFrom 方法从内存流中反序列化出 TestMsg 实例,最后打印该实例的属性值。
大小端模式
大小端模式的基本概念
| 模式 | 高字节存储位置 | 低字节存储位置 | 特点 |
|---|---|---|---|
| 大端模式 | 内存低地址 | 内存高地址 | 符合人类阅读习惯,网络传输标准 |
| 小端模式 | 内存高地址 | 内存低地址 | 计算机处理效率高,x86 架构默认 |
示例:十六进制数据 0x11223344
- 大端模式存储:
11 22 33 44(地址 0→3) - 小端模式存储:
44 33 22 11(地址 0→3)
大小端模式的影响及转换
影响:网络传输中,前后端语言和设备差异可能导致大小端不统一
- C# 和 Java/Erlang/AS3 通讯需转换
- C# 与 C++ 通信通常无需特殊处理
转换方法:
| 操作 | 方法 | 示例 |
|---|---|---|
| 判断模式 | BitConverter.IsLittleEndian |
print("是否是小端模式:" + BitConverter.IsLittleEndian); |
| 转为大端 | IPAddress.HostToNetworkOrder |
byte[] bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(i)); |
| 转为小端 | IPAddress.NetworkToHostOrder |
int receI = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(bytes, 0)); |
| 倒序数组 | Array.Reverse |
if (BitConverter.IsLittleEndian) Array.Reverse(bytes); |
消息加密解密
加密解密基本概念
| 名词 | 含义 |
|---|---|
| 明文 | 待加密的报文(内容) |
| 密文 | 加密后的报文(内容) |
| 密钥 | 加密或解密过程中输入的数据 |
| 加密算法 | 将明文和密钥结合生成密文的方法 |
| 解密算法 | 将密文和密钥结合还原明文的方法 |
加密算法分类
| 类型 | 特点 | 常用算法 | 适用场景 |
|---|---|---|---|
| 单向加密 | 不可逆,生成固定长度值 | MD5、SHA1、SHA256 | 密码存储 |
| 对称加密 | 同一密钥加密解密,速度快 | DES、3DES、AES | 网络通讯 |
| 非对称加密 | 公钥加密、私钥解密,安全性高 | RSA、DSA | 支付SDK、高安全场景 |
异或加密示例:
byte key = 55;
// 加密
for (int i = 0; i < bytes.Length; i++)
bytes[i] ^= key;
// 解密(异或两次还原)
for (int i = 0; i < bytes.Length; i++)
bytes[i] ^= key;
注意:加密只是提高破解门槛,没有 100% 保密的数据
UnityWebRequest高级操作
DownloadHandler 下载处理类
| 类名 | 用途 |
|---|---|
| DownloadHandlerBuffer | 下载字节数组 |
| DownloadHandlerFile | 下载文件保存到磁盘(内存占用少) |
| DownloadHandlerTexture | 下载图像 |
| DownloadHandlerAssetBundle | 下载 AssetBundle |
| DownloadHandlerAudioClip | 下载音频文件 |
自定义下载处理:继承 DownloadHandlerScript,重写:
ReceiveContentLengthHeader:收到 Content-Length 时调用ReceiveData:每帧收到数据时调用CompleteContent:消息接收完成时调用
UploadHandler 上传处理类
| 类名 | 用途 |
|---|---|
| UploadHandlerRaw | 上传字节数组 |
| UploadHandlerFile | 上传文件 |
重要属性:contentType 内容类型,默认 application/octet-stream
自定义协议生成工具
目的:
- 减少工作量,配置一次自动生成多语言代码
- 降低沟通成本,避免前后端协议不统一
制作流程:
- 确定配置方式(XML/JSON/Excel)
- 制定配置规则(枚举、数据类、消息类)
- 读取配置信息
- 按规则拼接字符串生成代码文件
XML配置示例:
<enum name="E_PLAYER_TYPE" namespace="GamePlayer">
<field name="MAIN">1</field>
<field name="OTHER"/>
</enum>
<data name="PlayerData" namespace="GamePlayer">
<field type="int" name="id"/>
<field type="float" name="atk"/>
</data>
<message id="1001" name="PlayerMessage" namespace="GamePlayer">
<field type="int" name="playerID"/>
</message>
Protobuf协议工具
Protobuf:谷歌开源的协议生成工具,支持 C++、Java、C#、Python 等多语言
使用流程:
- 准备 DLL 文件和 protoc 编译器
- 编写
.proto配置文件 - 使用 protoc 生成代码文件
- 导入工程使用
proto 配置规则:
syntax = "proto3"; // 版本号
package GamePlayer; // 命名空间
enum TestEnum { // 枚举
NORMAL = 0; // 第一个常量必须映射到0
BOSS = 1;
}
message TestMsg { // 消息类
int32 testInt32 = 1; // 成员变量 + 唯一编号
repeated int32 list = 2; // 数组
map<int32, string> dic = 3; // 字典
TestEnum e = 4; // 枚举成员
}
Protobuf 序列化/反序列化:
// 序列化到文件
using (FileStream fs = File.Create(path))
testMsg.WriteTo(fs);
// 从文件反序列化
TestMsg msg = TestMsg.Parser.ParseFrom(fs);
// 序列化到字节数组
using (MemoryStream ms = new MemoryStream())
{
testMsg.WriteTo(ms);
byte[] bytes = ms.ToArray();
}
// 从字节数组反序列化
TestMsg msg = TestMsg.Parser.ParseFrom(new MemoryStream(bytes));
优势:自动处理大小端问题,跨语言兼容性好
73.3 面试题精选
基础题
1. IP地址和端口的作用是什么?
题目
请简述 IP 地址和端口在网络通信中的作用,以及它们之间的关系。
深入解析
IP 地址和端口是网络通信中定位目标程序的两个关键要素:
IP 地址:
- 网络中设备的逻辑地址,用于在网络中定位一台计算机
- IPv4 为 32 位地址(如 192.168.1.1),IPv6 为 128 位地址
127.0.0.1是特殊的本机回环地址
端口:
- 用于标识计算机上具体的网络应用程序
- 范围 0~65535,其中 0~1023 为系统保留端口
- 一个 IP 地址可以有 65536 个端口
两者关系:
- IP 地址定位网络中的计算机(找到哪台机器)
- 端口定位计算机上的具体程序(找到哪个应用)
- 组合起来(IP:端口)可以唯一确定网络中的一个通信端点
答题示例
IP 地址用于定位网络中的计算机,端口用于标识计算机上的具体应用程序。
两者组合起来可以唯一确定网络中的一个通信端点,就像”小区地址+门牌号”能精确定位一户人家。
参考文章
- 3.网络理论-IP、端口、Mac地址
进阶题
1. C/S模型和P2P模型有什么区别?游戏开发中如何选择?
题目
请对比 C/S 模型和 P2P 模型的特点,并说明在网络游戏开发中如何选择合适的架构。
深入解析
C/S 模型(Client/Server):
- 架构特点:客户端发起请求,服务端响应处理,集中式管理
- 优点:数据安全性高、易于维护和扩展、防作弊能力强
- 缺点:服务器成本高、单点故障风险
- 适用场景:大型网络游戏、企业应用
P2P 模型(Peer to Peer):
- 架构特点:每个节点既是客户端又是服务端,对等通信
- 优点:无中心服务器、成本低、扩展性好
- 缺点:数据安全性差、难以防作弊、网络延迟不稳定
- 适用场景:局域网游戏、文件共享、即时通讯
游戏开发选择建议:
- 网络游戏:优先选择 C/S 模型,便于数据存储、防作弊、玩家匹配
- 局域网游戏:可考虑 P2P 模型,降低服务器成本
- 混合架构:部分功能用 P2P(如语音聊天),核心逻辑用 C/S
答题示例
C/S 模型是集中式架构,客户端请求、服务端响应,适合需要数据安全和防作弊的网络游戏;P2P 模型是对等架构,每个节点地位平等,适合局域网游戏。
网络游戏优先选择 C/S 模型,便于玩家数据管理和防作弊;局域网游戏可用 P2P 降低成本。
参考文章
- 5.网络理论-数据通信模型
深度题
1. 请详细说明TCP三次握手和四次挥手的过程及原因
题目
请详细描述 TCP 三次握手建立连接和四次挥手断开连接的完整过程,并解释为什么需要这样设计。
深入解析
三次握手过程:
- 第一次握手:客户端发送 SYN 报文(SYN=1,seq=x)给服务端,进入 SYN_SENT 状态
- 第二次握手:服务端收到 SYN,回复 SYN+ACK 报文(SYN=1,ACK=1,seq=y,ack=x+1),进入 SYN_RCVD 状态
- 第三次握手:客户端收到 SYN+ACK,回复 ACK 报文(ACK=1,seq=x+1,ack=y+1),进入 ESTABLISHED 状态
- 服务端收到 ACK 后,进入 ESTABLISHED 状态,连接建立完成
为什么需要三次握手:
- 防止已失效的连接请求报文突然到达服务端,导致服务端错误建立连接
- 确保双方都能确认对方的接收和发送能力正常
- 同步双方的初始序列号
四次挥手过程:
- 第一次挥手:客户端发送 FIN 报文(FIN=1,seq=u),进入 FIN_WAIT_1 状态
- 第二次挥手:服务端收到 FIN,回复 ACK(ACK=1,ack=u+1),进入 CLOSE_WAIT 状态;客户端收到后进入 FIN_WAIT_2 状态
- 第三次挥手:服务端发送 FIN 报文(FIN=1,seq=w),进入 LAST_ACK 状态
- 第四次挥手:客户端收到 FIN,回复 ACK(ACK=1,ack=w+1),进入 TIME_WAIT 状态,等待 2MSL 后关闭;服务端收到 ACK 后关闭
为什么需要四次挥手:
- TCP 是全双工通信,每个方向的连接需要单独关闭
- 服务端收到 FIN 后可能还有数据要发送,不能立即关闭
- 确保被动关闭方收到最后的 ACK 确认
答题示例
三次握手:客户端发 SYN → 服务端回 SYN+ACK → 客户端回 ACK。目的是建立可靠连接,同步序列号,防止失效连接请求。
四次挥手:客户端发 FIN → 服务端回 ACK → 服务端发 FIN → 客户端回 ACK。因为是全双工通信,每个方向需单独关闭,服务端可能还有数据要发。
参考文章
- 9.网络协议-TCP和UDP
2. TCP和UDP有什么区别?游戏开发中如何选择?
题目
请对比 TCP 和 UDP 协议的特点,并说明在网络游戏开发中如何选择使用。
深入解析
TCP 特点:
- 面向连接,需要三次握手建立连接
- 可靠传输:无差错、不丢失、不重复、有序
- 有流量控制和拥塞控制机制
- 只支持一对一通信
- 传输效率相对较低
UDP 特点:
- 无连接,直接发送数据
- 不可靠传输:不保证送达、不保证顺序
- 无流量控制和拥塞控制
- 支持一对多、多对多通信
- 传输效率高
游戏开发选择:
| 场景 | 推荐协议 | 原因 |
|---|---|---|
| 玩家登录、账号验证 | TCP | 需要可靠传输 |
| 聊天消息、邮件 | TCP | 不能丢失 |
| 实时战斗同步 | UDP | 低延迟优先 |
| 语音通话 | UDP | 实时性要求高 |
| 文件下载 | TCP | 完整性重要 |
实际应用:很多游戏采用 TCP + UDP 混合方案,TCP 用于关键数据,UDP 用于实时数据
答题示例
TCP 面向连接、可靠但效率低;UDP 无连接、不可靠但效率高。
游戏开发中,登录验证、聊天等关键数据用 TCP;实时战斗、语音等对延迟敏感的数据用 UDP。实际项目常采用混合方案。
参考文章
- 9.网络协议-TCP和UDP
3. 什么是分包和黏包?如何解决?
题目
请解释 TCP 通信中分包和黏包现象产生的原因,并说明解决方案。
深入解析
概念:
- 分包:一个消息被拆成多个包发送
- 黏包:多个消息黏在一起发送
- 注意:分包和黏包可能同时发生
产生原因:
- TCP 是流式协议,没有消息边界
- 发送方缓冲区积累多条消息一起发送
- 接收方缓冲区读取多条消息一起处理
解决方案:在消息头部添加消息长度字段
消息结构:
| 消息ID (4字节) | 消息长度 (4字节) | 消息体内容 |
处理逻辑:
- 维护缓存数组
cacheBytes和缓存长度cacheNum - 收到数据后拼接到缓存数组尾部
- 循环解析:先判断是否有完整头部(8字节),再根据消息长度判断是否有完整消息体
- 完整消息:解析并入队;不完整:保留到下次继续处理
答题示例
分包是一个消息被拆成多个包,黏包是多个消息黏在一起。原因是 TCP 是流式协议没有消息边界。
解决方案是在消息头部添加消息长度字段,接收方维护缓存数组,根据长度判断是否收到完整消息。
参考文章
- 24.网络通信-套接字Socket-TCP通信-同步-分包黏包-基本实现
4. 心跳消息的作用是什么?如何实现?
题目
请说明心跳消息在网络通信中的作用,并简述实现方案。
深入解析
心跳消息概念:长连接中定期发送的特殊数据包,用于确认对方在线
主要作用:
- 检测非正常断开的客户端(超时判断)
- 保持连接活跃,防止防火墙/路由器断开空闲连接
- 及时清理无效连接,释放服务器资源
实现要点:
客户端:
- 定时发送心跳消息(如每 2 秒)
- 使用
InvokeRepeating或协程实现
服务端:
- 记录每个客户端上次收到消息的时间
- 定时检测超时的客户端并断开连接
// 服务端超时检测
if (frontTime != -1 &&
DateTime.Now.Ticks / TimeSpan.TicksPerSecond - frontTime >= TIME_OUT_TIME)
{
// 超时,断开连接
}
注意事项:
- 心跳间隔不宜过短,避免增加网络负担
- 超时时间应大于心跳间隔的 2-3 倍
答题示例
心跳消息用于检测连接是否存活,防止防火墙断开空闲连接。客户端定时发送心跳包,服务端记录最后收到消息的时间,超时则断开连接。
参考文章
- 27.网络通信-套接字Socket-TCP通信-同步-心跳消息-实现
5. HTTP中Get和Post有什么区别?
题目
请对比 HTTP 中 Get 和 Post 请求的区别。
深入解析
| 特性 | Get | Post |
|---|---|---|
| 主要用途 | 获取数据 | 上传数据 |
| 参数位置 | URL 中暴露 | 请求体中隐藏 |
| 数据大小 | 有限制(约 2048 字符) | 无限制 |
| 安全性 | 较低(参数可见) | 较高(参数隐藏) |
| 缓存 | 可被浏览器缓存 | 不可缓存 |
| 传输次数 | 一次传输 | 可能分两次传输 |
使用建议:
- Get 用于获取资源,如查询数据、下载文件
- Post 用于提交数据,如上传文件、表单提交
- 敏感信息必须使用 Post
答题示例
Get 用于获取数据,参数在 URL 中暴露,有大小限制;Post 用于上传数据,参数在请求体中隐藏,无大小限制。敏感信息应使用 Post。
参考文章
- 50.网络通信-超文本传输HTTP-Post学前准备
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com