http阻塞模式和非阻塞模式

http阻塞模式和非阻塞模式

##区别

  • 同步会阻塞线程,一直等到网络回调,而异步不会阻塞
  • 同步可以设置TimeOut,异步则对TimeOut无视
  • 异步需要自己对TimeOut进行计算,通过监听接收到网络回调
    ##在Unity的表现
  • 一般做连接launch sever的时候用http请求
    ###同步方法(sync http)
    注意看按钮,在同步时,主线程阻塞,按钮是无任何反应的,不可点击,无hover响应的
    http阻塞模式和非阻塞模式_第1张图片
    ###异步方法(async http)
    使用异步方法的情况下,HttpWebRequest通过事件的方式发送,整个线程不阻塞,response通过callback调用
    http阻塞模式和非阻塞模式_第2张图片

##封装的脚本
###同步方法

using System;
using UnityEngine;
using System.IO;
using System.Net;
using System.Text;
public class ClientSyncHttp  {
    private readonly int _timeout;
    public ClientSyncHttp(string url, string body, Action callback = null, int timeout = 30) {
        _timeout = timeout;
        RequestHttpServer(new Uri(url), body, callback);
    }
    private void RequestHttpServer(Uri uri, string body, Action callback) {
        HttpWebRequest request = (HttpWebRequest) WebRequest.Create(uri);
        request.ProtocolVersion = HttpVersion.Version11;
        request.AutomaticDecompression = DecompressionMethods.None;
        request.Method = "POST";
        request.ContentType = "application/json;charset=UTF-8";
        request.Timeout = _timeout*1000;
        request.ReadWriteTimeout = 10*1000;
        request.KeepAlive = false;
        request.Proxy = null;
        using (Stream requestStream = request.GetRequestStream()) {
            byte[] bytes = Encoding.UTF8.GetBytes(body);
            requestStream.Write(bytes, 0, bytes.Length);
            requestStream.Close();
        }
        try {
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            Stream responseStream = response.GetResponseStream();
            if (responseStream == null) {
                return;
            }
            StreamReader sr = new StreamReader(responseStream, Encoding.UTF8);
            string result = sr.ReadToEnd().Trim();
            sr.Close();
            if (callback != null) {
                callback(result);
            }
        }
        catch (Exception e) {
            Debug.Log(e);
        }
    }
}

###异步方法

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


namespace Joker.Network {
    public class ClientAsyncHttp : MonoBehaviour {
        public Action OnConnectFailed;
        public Action OnRequestTimeOut;
        private Action _onResponseSucceed;
        private HttpWebRequest _webRequest;

        [SerializeField]
        private string _requestBody;
        [SerializeField]
        private string _responseString;
        [SerializeField]
        private string _dstUrl;
        [SerializeField]
        private float _requestTimer;
        [SerializeField]
        private float _timeOut;
        [SerializeField]
        private bool _isResponseTriggered;
        [SerializeField]
        private bool _isTimeOutTriggered;
        [SerializeField]
        private bool _isWaitingResponse;

        private bool _isConnectFailedTriggered;


        public static ClientAsyncHttp Create(string url, string body, Action callback, float? timeOut = null) {
            float currentTimeOut = timeOut ?? 30f;
            string name = string.Format("request_{0}", url);
            ClientAsyncHttp instance = new GameObject(name, typeof(ClientAsyncHttp)).GetComponent();

            instance._dstUrl = url;
            instance._onResponseSucceed = callback;
            instance._requestBody = body;
            instance._timeOut = currentTimeOut;

            return instance;
        }

        public void Request(string requestString = null) {
            _isConnectFailedTriggered = false;
            _isResponseTriggered = false;
            _isTimeOutTriggered = false;
            _isWaitingResponse = true;
            _requestTimer = Time.realtimeSinceStartup;
            if (!string.IsNullOrEmpty(requestString)) {
                _requestBody = requestString;
            }

            StartAsyncRequest(new Uri(_dstUrl));
            Debug.LogFormat("[request]:{0}, [post]:{1} at:{2}s, time out:{3}s", _requestBody, _dstUrl, _requestTimer, _timeOut);
        }

        private void Update() {
            //when response received, trigger callback.
            if (_isResponseTriggered) {
                if (_onResponseSucceed != null) {
                    _onResponseSucceed(_responseString);
                }
                int elapsedMs = (int)((Time.realtimeSinceStartup - _requestTimer) * 1000);
                Debug.LogFormat("[response]:{0}, elapsed:{1}ms", _dstUrl, elapsedMs);
                _isResponseTriggered = false;
                _isWaitingResponse = false;
            }

            //check request time out.
            if (CheckTimeOut() && _isWaitingResponse && !_isTimeOutTriggered) {
                _isTimeOutTriggered = true;//标记超时状态触发
                if (OnRequestTimeOut != null) {
                    OnRequestTimeOut();
                }

                _webRequest.Abort();//当Requst没有在限定的TimeOut内Response,就把此次请求视为夭折,立即结束此次请求。
                Debug.LogFormat("[request]:{0} TIME OUT", _dstUrl);
            }

            if (_isConnectFailedTriggered) {
                _isConnectFailedTriggered = false;
                if (OnConnectFailed != null) {
                    OnConnectFailed();
                }

                _webRequest.Abort();
                Debug.LogFormat("[request]:{0} CONNECT FAILED", _dstUrl);
            }
        }

        private bool CheckTimeOut() {
            //Debuger.LogFormat("CheckoutTimeOut, {0}, {1}, {2}", Time.realtimeSinceStartup, _requestTimer, _timeOut);
            return Time.realtimeSinceStartup - _requestTimer > _timeOut;
        }

        private void StartAsyncRequest(Uri uri) {
            _webRequest = (HttpWebRequest)WebRequest.Create(uri);
            _webRequest.ProtocolVersion = HttpVersion.Version11;
            _webRequest.AutomaticDecompression = DecompressionMethods.None;
            _webRequest.Method = "POST";
            _webRequest.ContentType = "application/json;charset=UTF-8";
            //_webRequest.ReadWriteTimeout = (int)_timeOut * 1000;
            _webRequest.KeepAlive = false;
            _webRequest.Proxy = null;
            _webRequest.BeginGetRequestStream(GetRequestStreamCallback, _webRequest);
        }

        private void GetRequestStreamCallback(IAsyncResult ar) {
            HttpWebRequest request = (HttpWebRequest)ar.AsyncState;
            //End the operation
            Stream postStream;
            try {
                postStream = request.EndGetRequestStream(ar);
                // Convert the string into a byte array.
                byte[] byteArray = Encoding.UTF8.GetBytes(_requestBody);
                // Write to the request stream.
                //中文一个汉字占3个字节长度,所以write时以byte长度为准,不然输入中文时传输的长度不正确
                postStream.Write(byteArray, 0, byteArray.Length);
                postStream.Close();
                // Start the asynchronous operation to get the response
                request.BeginGetResponse(OnRequestServerResponse, request);
            }
            catch (Exception exception) {
                if (exception.GetType() == typeof(WebException)) {
                    _isConnectFailedTriggered = true;
                }
                Debug.LogFormat("GetRequestStreamCallback Failed! {0}", exception);
            }
        }

        private void OnRequestServerResponse(IAsyncResult ar) {
            try {
                HttpWebRequest request = (HttpWebRequest)ar.AsyncState;
                HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(ar);

                OnResponseReceived(response, request);
            }
            catch (Exception exception) {
                throw exception;
            }
        }

        private void OnResponseReceived(HttpWebResponse response, HttpWebRequest request) {
            if (response.StatusCode == HttpStatusCode.OK) {
                Stream streamResponse = response.GetResponseStream();
                StreamReader streamRead = new StreamReader(streamResponse, Encoding.UTF8);
                _responseString = streamRead.ReadToEnd();
                streamResponse.Close();
                streamRead.Close();
                //Release the HttpWebResponse
                response.Close();
                _isResponseTriggered = true;
            }
            else {
                Debug.LogFormat("接收请求失败,状态{0}", response.StatusCode);
            }
        }
    }
}

##测试案例
###首先用Python的Bottle库,模拟一个简单的web服务器
####服务端

  • 如果没有Bottle库,需要下载安装
  • 强行等待了3秒,才返回,为了测试
from bottle import post, run, Bottle, static_file, request
@post('/test_http_timeout')
def test_http_timeout():
        print "getpost %s" % request.body
        import time
        time.sleep(3)
        return request.body
#app = MyApp()
run(host="192.168.1.138", port = 8090)

####客户端

using UnityEngine;
public class TestHttp : MonoBehaviour {
    public GUISkin Skin;
    // Use this for initialization
    void Start() {
        Skin.textField.fixedWidth = Screen.width;
        Skin.textField.fixedHeight = 48;
        Skin.textField.fontSize = 28;
        Skin.button.fixedWidth = Screen.width;
        Skin.button.fixedHeight = 48;
        Skin.button.fontSize = 28;
        Skin.label.fixedWidth = Screen.width;
        Skin.label.fixedHeight = 48;
        Skin.label.fontSize = 28;
    }
    private void SyncCallback(string result) {
        _responseTextOfSync = result;
    }
    private void AsyncCallback(string result) {
        _responseTextOfAsync = result;
    }
    private string _requestBodyOfSync = "";
    private string _requestBodyOfAsync = "";
    private string _responseTextOfSync;
    private string _responseTextOfAsync;
    void OnGUI() {
        GUILayout.BeginArea(new Rect(0,0,Screen.width, Screen.height/2f));
        GUILayout.Label("同步方法", Skin.label);
        GUILayout.BeginHorizontal();
        GUILayout.Label("Http请求:", Skin.label, GUILayout.Width(Screen.width/3f));
        _requestBodyOfSync = GUILayout.TextField(_requestBodyOfSync, Skin.textField);
        GUILayout.EndHorizontal();
        if (GUILayout.Button("同步方法", Skin.button)) {
            _responseTextOfSync = string.Empty;
            new ClientSyncHttp("http://192.168.1.138:8090/update", _requestBodyOfSync, SyncCallback);
        }
        if (!string.IsNullOrEmpty(_responseTextOfSync)) {
            GUILayout.Label(string.Format("服务端回执:{0}", _responseTextOfSync), Skin.label);
        }
        GUILayout.EndArea();
        GUILayout.BeginArea(new Rect(0, Screen.width/2f, Screen.width, Screen.height/2f));
        GUILayout.Label("异步方法", Skin.label);
        GUILayout.Label("Http请求:", Skin.label, GUILayout.Width(Screen.width / 3f));
        _requestBodyOfAsync = GUILayout.TextField(_requestBodyOfAsync, Skin.textField);
        if (GUILayout.Button("异步请求", Skin.button)) {
            _responseTextOfAsync = string.Empty;
            new ClientAsyncHttp("http://192.168.1.138:8090/update", _requestBodyOfAsync, AsyncCallback);
        }
        if (!string.IsNullOrEmpty(_responseTextOfAsync)) {
            GUILayout.Label(string.Format("服务端回执:{0}", _responseTextOfAsync), Skin.label);
        }
        GUILayout.EndArea();
    }
}

你可能感兴趣的:(Unity3D)