using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
/// <summary>
/// Http服务
/// </summary>
public class HttpServer : MonoBehaviour
{
public static HttpServer I;
HttpListener listener = new HttpListener();
public Action<string> receivedEvent;
int port = 8011;
private void Awake()
{
I = this;
listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
listener.Prefixes.Add(string.Format("http://*:{0}/", port));
listener.Start();
ReceiveAsync();
Debug.Log("启动监听!");
}
private async Task ReceiveAsync()
{
HttpListenerContext context;
while (true)
{
context = await listener.GetContextAsync();
Dispather(context);
}
}
//注意这里是异步:回调中不能调用Unity一些API,需要自行处理
private void Dispather(HttpListenerContext context)
{
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
response.AddHeader("Access-Control-Allow-Origin", "*"); //跨域
var rawUrl = request.RawUrl;
Debug.Log(rawUrl);
//下面这段是自己的逻辑,根据需要修改
if (rawUrl.StartsWith("/update"))
{
rawUrl = rawUrl.Remove(0, rawUrl.IndexOf('=') + 1);
receivedEvent?.Invoke(rawUrl);
//using (Stream body = request.InputStream)
//{
// Encoding encoding = request.ContentEncoding;
// using (StreamReader reader = new StreamReader(body, encoding))
// {
// string s = reader.ReadToEnd();
// receivedEvent?.Invoke(s);
// }
//}
}
//下面是对请求者的回复
Stream ss = response.OutputStream;
byte[] buffer = Encoding.UTF8.GetBytes("{\"code\":0}");
ss.Write(buffer, 0, buffer.Length);
ss.Close();
response.Close();
}
}
说一下可能碰到的问题:
如果在收到消息后触发事件,如果绑定在这个事件上的逻辑处理时间较长,会导致无法及时回复给对方,导致对方会出错,比如程序卡死
if (rawUrl.StartsWith("/update"))
{
receivedEvent?.Invoke(rawUrl);
}
例如:耗时操作在回应对方前面或后面执行都会导致一样的问题
if (rawUrl.StartsWith("/update"))
{
//回应对方
Response(response);
//模拟耗时操作
for (int i = 0; i < 999999999; i++)
{
float f = 545456 / 34234f;
f = f * f;
}
}
private void Response(HttpListenerResponse response)
{
response.AddHeader("Access-Control-Allow-Origin", "*");
Stream ss = response.OutputStream;
byte[] buffer = Encoding.UTF8.GetBytes("{\"code\":0}");
ss.Write(buffer, 0, buffer.Length);
ss.Close();
}
解决方案:1.用异步
if (rawUrl.StartsWith("/update"))
{
//回应对方
Response(response);
//耗时操作用异步,有些UnityApi只能在主线程执行
Task.Run(() =>
{
for (int i = 0; i < 999999999; i++)
{
float f = 545456 / 34234f;
}
});
}
2.放到主线程里做
if (rawUrl.StartsWith("/update"))
{
//回应对方
Response(response);
//耗时操作放到主线程
mainTheard=true;
}
bool mainTheard= false;
private void Update()
{
if (mainTheard)
{
mainTheard= false;
//如果太耗时,会卡主线程
for (int i = 0; i < 999999999; i++)
{
float f = 545456 / 34234f;
}
}
}
同样如果这个Http服务不是用于Unity程序也需注意这个问题
更新:添加视频请求,1.下载视频文件,2.在线播放
比如:请求的url规则是:http://xxxip:port/getfile?filename=xxx.mp4
在解析里添加如下代码:
//1.视频下载
if (rawUrl.StartsWith("/getfile"))
{
var fileName = rawUrl.Split('=')[1];
if (fileName.EndsWith(".mp4"))
{
response.ContentType = "video/mp4";
response.Headers.Add("Content-Disposition", $"attachment;filename={fileName}");
var bytes = File.ReadAllBytes(Application.streamingAssetsPath + "/" + fileName);
long videoLength = bytes.Length; // 视频文件总长度
response.ContentLength64 = videoLength; // 设置响应内容长度
response.OutputStream.Write(bytes, 0, bytes.Length); // 将视频块数据写入响应输出流并发送给客户端
response.Close(); // 关闭响应流
return;
}
}
//2.视频在线播放,边缓冲边播放
if (rawUrl.StartsWith("/getfile"))
{
var fileName = rawUrl.Split('=')[1];
if (fileName.EndsWith(".mp4"))
{
var bytes = File.ReadAllBytes(Application.streamingAssetsPath + "/" + fileName);
long videoLength = bytes.Length; // 视频文件总长度
// 设置响应头信息,指定内容范围和类型等
response.StatusCode = 206; // Partial Content 状态码,测试不加也可以
response.AddHeader("Content-Range", "bytes " + 0 + "-" + (videoLength - 1) + "/" + videoLength);
response.AddHeader("Accept-Ranges", "bytes");
response.ContentType = "video/mp4"; // 视频文件类型,根据实际情况设置
response.ContentLength64 = videoLength; // 设置响应内容长度
response.OutputStream.Write(bytes, 0, bytes.Length);// 将视频块数据写入响应输出流并发送给客户端
response.Close(); // 关闭响应流
return;
}
}
//3.视频在线播放,边缓冲边播放,同时处理分片与不分片请求,和第二条没发现有啥区别
if (rawUrl.StartsWith("/getfile"))
{
var fileName = rawUrl.Split('=')[1];
if (fileName.EndsWith(".mp4"))
{
string rangeHeader = request.Headers["Range"]; // 获取请求的 Range 头部信息
string videoPath = Application.streamingAssetsPath + "/" + fileName; // 视频文件路径
FileInfo videoFile = new FileInfo(videoPath);
long videoLength = videoFile.Length; // 视频文件总长度
// 解析 Range 头部信息,获取请求的视频块范围
long start = 0, end = videoLength - 1; //如果请求头没有Range,则视频不分片,全部发送
if (!string.IsNullOrEmpty(rangeHeader)) //分片处理
{
string[] ranges = rangeHeader.Replace("bytes=", "").Split('-');
start = long.Parse(ranges[0]);
if (ranges.Length > 1)
if (!long.TryParse(ranges[1], out end))
{
end = videoLength - 1;
}
}
// 计算视频块长度和内容范围
long contentLength = end - start + 1;
byte[] videoChunk = new byte[contentLength];
using (FileStream fileStream = videoFile.OpenRead())
{
fileStream.Seek(start, SeekOrigin.Begin);
fileStream.Read(videoChunk, 0, (int)contentLength);
}
// 设置响应头信息,指定内容范围和类型等
response.StatusCode = 206; // Partial Content 状态码
response.AddHeader("Content-Range", "bytes " + start + "-" + end + "/" + videoLength);
response.AddHeader("Accept-Ranges", "bytes");
response.ContentType = "video/mp4"; // 视频文件类型,根据实际情况设置
response.ContentLength64 = contentLength; // 设置响应内容长度
response.OutputStream.Write(videoChunk, 0, videoChunk.Length);// 将视频块数据写入响应输出流并发送给客户端
response.Close(); // 关闭响应流
}
}