Unity中使用HttpListener创建本地Http web服务器教程与完整代码

前言

下方有完整代码和使用方法,急用的请直接拉到最下方

本文可以实现不开新进程在Unity中创建http服务器。
监听自定义ip获取指定目录下的网页或其他资源。如果网页内有其他资源链接也可以正常访问。
可以配合Unity网页浏览器组件使用解决资源打包问题

在Unity中搭建简易http服务主要分为三步

  1. 监听访问请求
  2. 解析请求
  3. 响应请求

1 监听访问请求

监听服务使用的是System.Net库中的HttpListener组件,并使用其Start()方法对相关端口的访问进行监听

using System;
using System.Net;
public class HttpServer : MonoBehaviour
{
    // 服务器对象
    private  HttpListener listener;
    // 要监听的目标地址
    private string localIp = "http://localhost:8081/";
    // 本地web资源地址
    private string localFilePath = "C:\\Users\\Desktop\\html";
}

一般执行监听有三种方式:
第一种就是用whilet(true)循环监听, 这种方式会阻塞当前线程。
第二种是调用threadingpool线程池开一个线程监听请求,是比较常用的方式
第三种是使用异步监听。本文使用的是第三种

listener.BeginGetContext(Response, null);

listener.BeginGetContext会开启异步监听,监听到访问会回调我们自定义的Response方法并传入请求信息,我们对访问请求的数据解析和响应都要在这个方法中完成,并且要在其中再次调用listener.BeginGetContext方法来保持持续监听

    void Start()
    {
        listener = new HttpListener();
        // 定义url
        listener.Prefixes.Add(localIp);
        listener.Start();
        // 使用异步监听Web请求,当客户端的网络请求到来时会自动执行委托
        listener.BeginGetContext(Response, null);
        // 提示信息
        Debug.Log($"服务已启动 {DateTime.Now.ToString()},访问地址:{localIp}");
    }
    private void Response(IAsyncResult ar)
	{
    	// 再次开启异步监听
    	listener.BeginGetContext(Response, null);
    }

2 解析请求

解析请求的目的是根据访问地址来定位文件资源的目录和区分文件不同类型的处理办法。
比如访问的地址是www.baidu.com/folder/index.html,我们首先要得到/folder/index.html这个文件相对地址来定位这个文件在本地磁盘上的路径,然后得到html这个文件的扩展名,来对html文件的处理方式做专门的说明

var context = listener.EndGetContext(ar)用来获取传入的请求数据
var request = context.Request; 用来获取请求信息

private void Response(IAsyncResult ar)
{
    // 再次开启异步监听
    listener.BeginGetContext(Response, null);
    Debug.Log($"{DateTime.Now.ToString()}接到新的请求");

    // 获取context对象
    var context = listener.EndGetContext(ar);
    // 获取请求体
    var request = context.Request;
    
    // 配置响应结构
    try
    {
        // 获取网址中指定的文件名,比如www.baidu.com/show.html, 这里会得到show.如果为空就默认为index.html
        string filename = context.Request.Url.AbsolutePath.Trim('/');
        if (filename == "")
        {
            filename = "index.html";
        }

        // 获取文件扩展名,比如file.jpg,这里拿到的就是jpg
        string[] ext_list = filename.Split('.');
        string ext = ext_list.Length > 1? ext_list[ext_list.Length - 1]:"";
    }
}

3 响应请求

响应请求要做的是,组织好要提供给访问者的资源,然后指明该资源在浏览器中应该怎样呈现
在这一部分中,

我们首先根据解析出来的文件相对路径与设置好的本地资源目录来获得目标资源的绝对路径:string absPath = Path.Combine(localFilePath, filename);

然后为html扩展名的文件设置响应头信息,让他以网页的形式在浏览器中正常展示:switch (ext){ case “html”: context.Response.ContentType = “text/html”;

接着将文件数据转为数据 msg = File.ReadAllBytes(absPath);context.Response.ContentLength64 = msg.Length;

最后将数据发给客户端 using (Stream s = context.Response.OutputStream) { s.Write(msg, 0, msg.Length); }

 private void Response(IAsyncResult ar)
 {
         // 获取网址中指定的文件名,比如www.baidu.com/show.html, 这里会得到show.如果为空就默认为index.html
         string filename = context.Request.Url.AbsolutePath.Trim('/');
         if (filename == "")
         {
             filename = "index.html";
         }

         // 获取文件扩展名,比如file.jpg,这里拿到的就是jpg
         string[] ext_list = filename.Split('.');
         string ext = ext_list.Length > 1? ext_list[ext_list.Length - 1]:"";
         // 根据结合本地资源目录和网址中的文件地址,得到要访问的文件的绝对路径
         string absPath = Path.Combine(localFilePath, filename);
         
         // 设置响应状态,就是网页响应码。ok == 200
         context.Response.StatusCode = (int)HttpStatusCode.OK;
         string expires = DateTime.Now.AddYears(10).ToString("r");

         // 根据文件扩展名配置不同的网页响应头
         switch (ext)
         {
             case "html":
             case "htm":
                 context.Response.ContentType = "text/html";
                 break;
             case "js":
                 context.Response.ContentType = "application/x-javascript";
                 context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                 context.Response.AddHeader("expires", expires);
                 break;
             case "css":
                 context.Response.ContentType = "text/css";
                 context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                 context.Response.AddHeader("expires", expires);
                 break;
             case "jpg":
             case "jpeg":
             case "jpe":
                 context.Response.ContentType = "image/jpeg";
                 context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                 context.Response.AddHeader("expires", expires);
                 break;
             case "png":
                 context.Response.ContentType = "image/png";
                 context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                 context.Response.AddHeader("expires", expires);
                 break;
             case "gif":
                 context.Response.ContentType = "image/gif";
                 context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                 context.Response.AddHeader("expires", expires);
                 break;
             case "ico":
                 context.Response.ContentType = "application/x-ico";
                 context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                 context.Response.AddHeader("expires", expires);
                 break;
             case "txt":
                 context.Response.ContentType = "text/plain";
                 break;
             case "do":
                 context.Response.AddHeader("Access-Control-Allow-Origin", "*");
                 context.Response.ContentType = "text/plain;charset=utf-8";
                 break;
             default:
                 context.Response.ContentType = "";
                 context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                 context.Response.AddHeader("expires", expires);
                 break;
         }

         // 组织数据流
         byte[] msg = new byte[0];
         if (msg.Length == 0)
         {
             // 如果目标文件不存在就显示错误页面
             if (!File.Exists(absPath))
             {
                 context.Response.ContentType = "text/html";
                 context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                 if (File.Exists(localFilePath + "error.html"))
                 {
                     msg = File.ReadAllBytes(localFilePath + "error.html");
                 }
                 else
                 {
                     msg = Encoding.Default.GetBytes("404");
                 }
             }
             // 如果存在就将文件转为byte流
             else
             {
                 msg = File.ReadAllBytes(absPath);
             }
         }
         // 返回字节流
         context.Response.ContentLength64 = msg.Length;
         using (Stream s = context.Response.OutputStream)
         {
             s.Write(msg, 0, msg.Length);
         }

         msg = new byte[0];
         GC.Collect();
     }
     catch (Exception ex)
     {
     }
 }

最后清空数据即可: msg = new byte[0]; GC.Collect();

完整代码及使用方式

使用方式:

  1. 在Unity中创建cs脚本其名为HttpServer.cs,将下方代码copy进去保存
  2. 修改成想要监控的地址:private string localIp = “http://localhost:8081/”;
  3. 修改成允许被访问的本地资源目录:private string localFilePath = “C:\Users\Desktop\html”;
  4. 在Unity场景中创建GameObject,然后为该对象添加HttpServer组件
  5. 运行Unity
  6. 打开浏览器访问http://localhost:8081/

完整代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Net;
using System.Text;
using System.IO;

public class HttpServer : MonoBehaviour
{
    // 服务器对象
    private  HttpListener listener;
    private string localIp = "http://localhost:8081/";
    // 本地web资源地址
    private string localFilePath = "C:\\Users\\Desktop\\html";

    void Start()
    {
        listener = new HttpListener();
        // 定义url
        listener.Prefixes.Add(localIp);
        listener.Start();
        // 使用异步监听Web请求,当客户端的网络请求到来时会自动执行委托
        listener.BeginGetContext(Response, null);
        // 提示信息
        Debug.Log($"服务已启动 {DateTime.Now.ToString()},访问地址:{localIp}");
    }

    /// 
    ///  处理收到的访问请求
    /// 
    /// 包含请求体的参数
    private void Response(IAsyncResult ar)
    {
        // 再次开启异步监听
        listener.BeginGetContext(Response, null);
        Debug.Log($"{DateTime.Now.ToString()}接到新的请求");

        // 获取context对象
        var context = listener.EndGetContext(ar);
        // 获取请求体
        var request = context.Request;
        // 获取响应结构
        var response = context.Response;

        try
        {
            // 获取网址中指定的文件名,比如www.baidu.com/show.html, 这里会得到show.如果为空就默认为index.html
            string filename = context.Request.Url.AbsolutePath.Trim('/');
            if (filename == "")
            {
                filename = "index.html";
            }

            // 获取文件扩展名,比如file.jpg,这里拿到的就是jpg
            string[] ext_list = filename.Split('.');
            string ext = ext_list.Length > 1? ext_list[ext_list.Length - 1]:"";
            // 根据结合本地资源目录和网址中的文件地址,得到要访问的文件的绝对路径
            string absPath = Path.Combine(localFilePath, filename);
            
            // 设置响应状态,就是网页响应码。ok == 200
            context.Response.StatusCode = (int)HttpStatusCode.OK;
            string expires = DateTime.Now.AddYears(10).ToString("r");

            // 根据文件扩展名配置不同的网页响应头
            switch (ext)
            {
                case "html":
                case "htm":
                    context.Response.ContentType = "text/html";
                    break;
                case "js":
                    context.Response.ContentType = "application/x-javascript";
                    context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                    context.Response.AddHeader("expires", expires);
                    break;
                case "css":
                    context.Response.ContentType = "text/css";
                    context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                    context.Response.AddHeader("expires", expires);
                    break;
                case "jpg":
                case "jpeg":
                case "jpe":
                    context.Response.ContentType = "image/jpeg";
                    context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                    context.Response.AddHeader("expires", expires);
                    break;
                case "png":
                    context.Response.ContentType = "image/png";
                    context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                    context.Response.AddHeader("expires", expires);
                    break;
                case "gif":
                    context.Response.ContentType = "image/gif";
                    context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                    context.Response.AddHeader("expires", expires);
                    break;
                case "ico":
                    context.Response.ContentType = "application/x-ico";
                    context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                    context.Response.AddHeader("expires", expires);
                    break;
                case "txt":
                    context.Response.ContentType = "text/plain";
                    break;
                case "do":
                    context.Response.AddHeader("Access-Control-Allow-Origin", "*");
                    context.Response.ContentType = "text/plain;charset=utf-8";
                    break;
                default:
                    context.Response.ContentType = "";
                    context.Response.AddHeader("cache-control", "max-age=315360000, immutable");
                    context.Response.AddHeader("expires", expires);
                    break;
            }

            // 组织数据流
            byte[] msg = new byte[0];
            if (msg.Length == 0)
            {
                // 如果目标文件不存在就显示错误页面
                if (!File.Exists(absPath))
                {
                    context.Response.ContentType = "text/html";
                    context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                    if (File.Exists(localFilePath + "error.html"))
                    {
                        msg = File.ReadAllBytes(localFilePath + "error.html");
                    }
                    else
                    {
                        msg = Encoding.Default.GetBytes("404");
                    }
                }
                // 如果存在就将文件转为byte流
                else
                {
                    msg = File.ReadAllBytes(absPath);
                }
            }
            // 返回字节流
            context.Response.ContentLength64 = msg.Length;
            using (Stream s = context.Response.OutputStream)
            {
                s.Write(msg, 0, msg.Length);
            }

            msg = new byte[0];
            GC.Collect();
        }
        catch (Exception ex)
        {
        }

        // 跨域等设置
        // context.Response.AppendHeader("Access-Control-Allow-Headers", "ID,PW");
        // context.Response.AppendHeader("Access-Control-Allow-Method", "post");
        // context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); // 允许跨域请求
        // context.Response.ContentType = "text/plain;charset=UTF-8"; // 响应类型为UTF-8纯文本格式
        // context.Response.AddHeader("Content-type", "text/plain"); // 添加响应头
        // context.Response.ContentEncoding = Encoding.UTF8;
    }

    private void OnDestroy()
    {
        // httpobj.EndGetContext(null);
    }
}

你可能感兴趣的:(#,unity3D,使用,技巧笔记,#,服务端开发,三维引擎,unity,http,前端,unity,web)