TCP、UDP、WebSocket 都属于持续通信,而游戏里还有大量功能更适合 HTTP:登录鉴权、活动接口、公告、支付、后台服务、文件接口……
如果每个业务模块都自己写 UnityWebRequest、自己处理 JSON、超时、重试和线程切换,很快就会出现一堆重复代码。
MyFramework 里的 NetConnectHttp 做的事情,就是把一次 HTTP 请求从协议对象创建,到发送、重试、回到主线程执行回调全部接管。
项目地址:
https://github.com/ZHOURUIH/MyFramework
一、HTTP 也被做成了 NetPacket
HTTP 消息的基类是:
public class NetPacketHttp : NetPacket
{
protected string mURL;
protected HTTP_METHOD mMethod;
public NetPacketHttp()
{
mMethod = HTTP_METHOD.POST;
}
public virtual string write() { return null; }
public virtual void read(string message) { }
public virtual int timeout()
{
return 10000;
}
}
所以业务并不是直接操作:
URL
JSON
UnityWebRequest
而是继续操作:
NetPacketHttp
一条 HTTP 协议自己定义:
请求地址
GET / POST
请求数据
返回数据
超时时间
这样 TCP、UDP、WebSocket、HTTP 在上层仍然保持同一种“协议对象”思想。
二、JSON 序列化也直接封装进协议
框架提供:
public class NetPacketHttpT<CSBody, SCBody> :
NetPacketHttp
where CSBody : IResetProperty, new()
{
public SCBody mBody;
public CSBody mSendBody = new();
public override string write()
{
return JsonConvert.SerializeObject(mSendBody);
}
public override void read(string message)
{
mBody =
JsonConvert.DeserializeObject<SCBody>(message);
}
}
业务只需要定义:
CSBody:发什么
SCBody:收什么
例如:
public class LoginSendBody : IResetProperty
{
public string account;
public string password;
public void resetProperty()
{
account = null;
password = null;
}
}
public class LoginResponseBody
{
public string token;
public long playerID;
}
然后协议类继承:
NetPacketHttpT<LoginSendBody, LoginResponseBody>
JSON 的序列化和反序列化就不需要每个接口重复写。
三、业务调用只负责“发协议,等结果”
发送入口:
mNetManager.sendPacket(
packet,
(NetPacketHttpXXX response) =>
{
// 处理结果
});
NetConnectHttp 收到请求后并不会马上联网。
首先创建:
HttpSendInfo
里面保存:
请求内容
请求类型
URL
GET / POST
回调
超时时间
剩余重试次数
然后放进:
DoubleBuffer<HttpSendInfo> mOutputBuffer;
整个流程变成:
主线程
NetPacketHttp
↓
JSON序列化
↓
HttpSendInfo
↓
DoubleBuffer
================
HTTP发送线程
↓
真正发请求
主线程不用自己等待网络。
四、同一种请求还没回来,直接禁止重复发送
NetConnectHttp 维护:
Dictionary<Type, HttpSendInfo>
mNotResponsePacket;
发送之前会先检查:
if (mNotResponsePacket.ContainsKey(
packet.GetType()))
{
logWarning(
"消息还未返回,不能重复发送:" +
packet.GetType());
return;
}
也就是说:
请求A发出
↓
还没返回
↓
再次发送请求A
↓
直接拦截
这对很多游戏接口非常实用。
比如玩家连续狂点:
领取奖励
领取奖励
领取奖励
领取奖励
至少 HTTP 网络层不会同时把四个完全相同类型的请求全部送出去。
不过这里有一个很重要的语义:
限制的 Key 是协议 Type,而不是请求参数。
所以如果同一个 HTTP 消息类型本来就需要同时请求多个不同 ID:
QueryPlayer(1001)
QueryPlayer(1002)
QueryPlayer(1003)
当前机制也会认为它们是“同一种未完成请求”。
这种接口就不能直接套用这个防重复策略。
五、失败以后自动重试,不让业务层到处写 Retry
每条请求默认:
protected static int RETRY_COUNT = 3;
发送时会把完整的 HttpSendInfo 再备份一份:
mNotResponsePacket
.addClass(packet.GetType())
.cloneFrom(info);
如果收到的数据为空:
if (sendInfo.mRemainRetryCount > 0)
{
–sendInfo.mRemainRetryCount;
CLASS_THREAD(
out HttpSendInfo info)
.cloneFrom(sendInfo);
mOutputBuffer.add(info);
}
于是:
请求失败
↓
还有Retry次数?
↓ Yes
重新放入OutputBuffer
↓
发送线程再次请求
直到没有剩余次数:
pair.mCallback(null);
业务层最终只需要判断:
if (response == null)
{
// 最终失败
}
而不用每个接口都重新实现一遍重试状态机。
六、网络回调绝对不直接执行游戏逻辑
异步 HTTP 请求完成以后,并不会直接执行业务 Callback。
它只做:
mReceiveBuffer.add(new()
{
mCallback = callback,
mPacketType = type,
mData = data
});
也就是进入:
DoubleBuffer<ReceivedDataInfo>
mReceiveBuffer;
真正执行发生在 update():
HTTP线程
↓
请求完成
↓
ReceivedDataInfo
↓
DoubleBuffer
================
Unity主线程
↓
创建NetPacketHttp
↓
JSON反序列化
↓
执行Callback
↓
回收Packet
这样 HTTP 回调中直接:
修改角色
打开UI
刷新背包
触发事件
仍然处于 Unity 主线程。
这一点和前面的 TCP、UDP 网络模型其实完全一致。
七、Header 和 Token 也统一管理
连接中长期维护:
Dictionary<string, string> mHttpHeader;
可以:
addHeader(name, value);
removeHeader(name);
Token 更进一步封装成:
mHttpServer.setToken(token);
内部实际上就是:
Token存在
↓
加入Header
Token为空
↓
删除Header
并且 Header 有:
ThreadLock mHeaderLock;
保护。
所以登录后不需要每发送一个 HTTP 请求,都重新手写:
Authorization
Token
Content-Type
…
公共 Header 由连接统一维护。
八、WebGL 下连发送模型都变了
普通平台初始化时会创建:
MyThread mSendThread =
new("HttpSend");
也就是说:
主线程生产请求
↓
DoubleBuffer
↓
HttpSend线程消费
但是 WebGL 没有这条线程。
源码直接变成:
#if UNITY_WEBGL
sendSocket();
#endif
也就是每帧 update() 主动处理发送队列。
POST 请求则切换到:
httpPostAsyncWebGL(…)
底层使用 UnityWebRequest + Coroutine。
所以又一次出现了 MyFramework 一贯的处理方式:
Native
线程 + HttpWebRequest
WebGL
主线程 + UnityWebRequest
↓
上层仍然是
NetPacketHttp
平台差异继续被压在底层。
九、当前实现还有两个值得注意的细节
第一,自动重试当前主要依据的是:
ReceivedDataInfo.mData == null
ReceivedDataInfo 虽然也保存了 HTTP Status 和错误状态,但当前 update() 的重试逻辑并不是按照所有 HTTP Code 分类判断。
所以:
500
404
业务错误码
返回了非空Body
和:
真正没有返回数据
并不是同一套处理逻辑。
第二,当前 WebGL 对 POST 有明确的:
httpPostAsyncWebGL()
分支,而 NetConnectHttp 的 GET 路径仍然直接调用 httpGetAsync()。
如果项目要大量在 WebGL 上使用 GET,这一段最好根据实际目标平台再检查并补齐对应的 WebGL 路径。
这种地方反而很适合在读源码时提前发现,而不是上线以后再踩坑。
十、整个 HTTP 请求最终变成了一条流水线
完整流程:
NetPacketHttp
↓
填写SendBody
↓
JSON序列化
↓
防止同Type重复请求
↓
HttpSendInfo
↓
Output DoubleBuffer
↓
HTTP线程 / WebGL Update
↓
POST / GET
↓
收到Response
↓
Receive DoubleBuffer
↓
Unity主线程
↓
JSON反序列化
↓
Callback
↓
失败则自动Retry
↓
对象池回收
所以 NetConnectHttp 真正解决的并不是“怎么发一个 HTTP”。
UnityWebRequest 本身就能做到这一点。
框架真正做的是把:
协议定义、防重复、序列化、线程隔离、公共 Header、Token、超时、自动重试、主线程回调和对象生命周期
全部放进同一条可复用的 HTTP 请求流水线。
当项目只有三五个接口时,这些东西看起来并不重要。
但当后台接口逐渐变成几十个、上百个以后,真正能救命的往往不是“会不会发 HTTP”,而是所有 HTTP 请求是不是都在遵守同一套规则。




