zoukankan      html  css  js  c++  java
  • 使用HttpClient实现并发请求

    在.Net 4.0之前,一直是依靠HttpWebRequest实现Http操作的。它默认有一个非常保守的同一站点下最大2并发数限制,导致默认情况下HttpWebRequest往往得不到理想的速度,必须修改App.config或ServicePointManager.DefaultConnectionLimit的值。所以对于需要高并发请求的场景HttpWebRequest不是一个理想的选择。

    MS在.Net 4.5中引入了一个HttpClient类专门处理Http操作,HttpClient不受HttpWebRequest并发策略控制,也没有系统级的并发限制。

    关于HttpClient和HttpWebRequest的一些区别参考(https://stackoverflow.com/questions/22214930/httpclient-vs-httpwebrequest)

    下面开始进入重点,在本文之前对于HttpClient有过很多错误的使用,最开始使用的如下代码:

    写一个静态方法,每次调用该方法都会new一个新的HttpClient对象,使用完成后释放HttpClient对象

            public static HttpResponseMessage GetRequest(string requestUri, string accessToken)
            {
                using (HttpClient client = new HttpClient())
                {
                    if (!string.IsNullOrEmpty(accessToken))
                    {
                        client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
                    }
                    client.DefaultRequestHeaders.Add("Accept", "application/json");
                    try
                    {
                        var response = client.GetAsync(requestUri).Result;
                        if (response.IsSuccessStatusCode)
                        {
                            return response;
                        }
                        else
                        {
                            throw new Exception(response.Content.ReadAsStringAsync().Result);
                        }
                    }
                    catch (Exception ex)
                    {
    
                        throw ex;
                    }
                }
            }

    那这就完成没有发挥出HttpClient的一大优势(同一HttpClient可以发送多个请求),而以上代码一个请求就会创建一个HttpClient对象,同时我们调用HttpClient的Dispose()方法销毁它时,它就启动一个进程,关闭在它控制之下的套接字。也就是说,你下次请求连接时,必须重复整个连接新建过程。如果网络延迟很高,或者连接是受保护的(需要新一轮的SSL/TLS协商),就会非常痛苦。

    所以HttpClient关闭套接字的过程并不快。当“关闭”套接字时,你真正做的是将TCP连接状态置为TIME_WAIT。在一个预先配置好的时间窗口内,Windows将保持该套接字的状态不变,默认情况下是4分钟。这是为了防止有任何剩余的数据包仍在传输。

    所以以上代码是一个错误的使用方式,如果在一个高并发场景,服务器资源也会迅速耗尽。

    所以我们的正确做法应该是:HttpClient应该只初始化一次,并在应用程序的整个生存期内重用。在负载很高的情况下,为每个请求初始化一个HttpClient类会耗尽可用的套接字数量。

    以下展示正确的做法,就是对HttpClient做单例处理,使得应用生命周期内只有一个HttpClient对象,并可以重用,并且不调用Dispose()方法。

        internal class HttpClientUtils
        {
            private static HttpClient _httpClient;
            private readonly static object _lock = new object();
            private readonly static HttpClientUtils _httpClientUtils = new HttpClientUtils();
    
            static HttpClientUtils()
            {
                _httpClient = GetHttpClient();
            }
    
            public static HttpClientUtils GetHttpClientUtils()
            {
                return _httpClientUtils;
            }
    
            private HttpClientUtils()
            {
            }
    
            private static HttpClient GetHttpClient()
            {
                lock (_lock)
                {
                    if (_httpClient == null)
                    {
                        _httpClient = new HttpClient();
                        //_httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
                        _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                        _httpClient.Timeout = TimeSpan.FromSeconds(1800);
                    }
                }
                return _httpClient;
            }
    }

    以上代码解决了HttpClient重用问题,但是竟然所有Http请求都使用同一HttpClient对象,那么新的问题也随之而来,可能我们会遇到不同的HttpClient请求需要带不同的Http请求头,

    那么如下所示代码,就会造成很大问题,因为并不是所有的Http请求都需要此请求头。

    client.DefaultRequestHeaders.Add("Accept", "application/json");
    //....

    那么问题的关键就在于,我们不能将请求头直接赋值到HttpClient对象的DefaultRequestHeaders属性上。

    那么应该如何讲请求头和HttpClient对象分离。我们可以使用SendAsync(HttpRequestMessage request); ,在HttpRequestMessage中添加请求头。

    以Get请求为例,示例如下:

            public async Task<HttpResponseMessage> GetRequestAsync(string requestUri, string accessToken)
            {
                HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, requestUri);
                if (!string.IsNullOrEmpty(accessToken))
                {
                    message.Headers.Add("Authorization", "Bearer " + accessToken);
                }
                try
                {
                    var response = await _httpClient.SendAsync(message);
                    //var response = await this._client.GetAsync(requestUri);
                    if (response.IsSuccessStatusCode)
                    {
                        return response;
                    }
                    else
                    {
                        throw new Exception(response.Content.ReadAsStringAsync().Result);
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }

    可以看到我们将Http请求方法,Http请求Url以及请求头信息,封装到HttpRequestMessage对象中,使用 SendAsync(HttpRequestMessage request); 发送请求

    而这正好解决高并发下Http请求发送的问题,避免不同Http请求互相造成干扰。

  • 相关阅读:
    flash播放器插件与flash播放器的区别
    FLASH动作脚本详解
    flash代码
    LitJson使用中需要注意的一些问题(转)
    AS3中ASCII码和字符互转函数
    JQuery直接调用asp.net后台WebMethod方法(转)
    C#文件操作
    js延迟执行
    js操作表格、table、
    定时任务、js定时任务
  • 原文地址:https://www.cnblogs.com/rampb/p/10137195.html
Copyright © 2011-2022 走看看