关于c#的HttpClient的特性

       在项目的开发过程中,需要用到POST机制将信息发送给服务器。翻阅微软资料,可以采用HttpClient类实现。实际使用过程中,发现这个类挺有脾气的,用不好特别容易出现的问题,最容易碰到的问题是下面两个:

1)会建立大量的Tcp链接,耗费系统资源;

2)容易出现TaskCanceledException异常,表示这个链接的发送任务被HttpClient强行取消了;

    翻阅了一些资料,查了下百度,特别感谢的是dudu在网上的分享,网址链接如下:

C#中HttpClient使用注意:预热与长连接

        基于这篇文章,特别对这个类的性能做了一个测试。先直接贴上自己的测试代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
using System.Threading;
namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            HttpClientTest cl = new HttpClientTest();
            int cnt = 0;
            while (true)
            {
                for (int i = 0; i < 20; i++)
                {
                    try
                    {
                        Task tsk = cl.PostAsync(cnt);
                        cnt++;
                        //tsk.Wait();  // 取消这个注释,则将等待上一个POST任务结束,然后再进行下一次POST
                        Thread.Sleep(300);

                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
                Console.ReadLine();
            }
        }
    }


    public class HttpClientTest
    {
        private  HttpClient _httpClient;
   
        public HttpClientTest()
        {
            _httpClient = new HttpClient() { BaseAddress = new Uri("http://192.168.101.233:2317") };//这里需要改为您自己的服务器
            _httpClient.Timeout = new TimeSpan(0,0,0,3);//默认超时100s,太长,改为3秒
        }

        /// <summary>
        /// 控制台打印Exception信息
        /// </summary>
        /// <param name="ex"></param>
        /// <param name="cc"></param>
        private void PrintException(Exception ex, ConsoleColor cc, string mystr)
        {
            ConsoleColor temp = Console.ForegroundColor;
            Console.ForegroundColor = cc;
            StringBuilder str = new StringBuilder();
            Exception extemp = ex;
            do
            {
                str.AppendLine(extemp.GetType() + "__" + extemp.Message);
                extemp = extemp.InnerException;
            } while (extemp != null);
            Console.WriteLine(DateTime.Now.ToString() + mystr + str);
            Console.ForegroundColor = temp;
        }

        public async Task<string> PostAsync(int cnt)/**/
        {
            string rst = "";
            HttpResponseMessage response = new HttpResponseMessage();
            try
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(DateTime.Now + $"  ({cnt})  Post: ...");
                response = await _httpClient.PostAsync("/", new StringContent("Hello"));
            }
            catch(Exception ex)
            {
                PrintException(ex, ConsoleColor.Green, $"  ({cnt})  HttpClient.PostAsync()异常)");
                return rst;
            }
            
            try
            {
                Console.ForegroundColor = ConsoleColor.White;
                rst = await response.Content.ReadAsStringAsync();
                Console.WriteLine(DateTime.Now + $"  ({cnt})  收到应答:" + rst);
            }
            catch(Exception ex)
            {
                PrintException(ex, ConsoleColor.Yellow, $"  ({cnt})  ReadAsStringAsync()异常)");
            }
            return rst;
        }
    }
}

        上面的代码主要功能就是连续不断的向服务器POST一个数据"Hello", 如果用TCP工具抓取服务器端实际上收到的完整的HTTP数据如下:

POST /HTTP/1.1
Content-Type: text/plain;charset=utf-8
Host:  192.168.101.233:2317
Content-Length: 5
Expect: 100-continue
Connection: Keep-Alive

Hello

实验0:

        这个实验这里不详细展开了,大家可以自行试试如果将代码改成在每次POST发送时采用using方式生成HttpClient来POST,这不是一个好习惯,这会导致大量的TCP连接。大家可以使用SocketTool这个工具来查看到这些tcp连接。这些tcp连接大量消耗系统的端口资源(我们知道端口最多有65535个,看起来很大,但如果POST的频率很快,还是会比较容易被耗光)。

实验1:

        服务器关闭,代码中task.Wait()部分注释掉,此时运行代码,我们发现程序不停的POST,但是由于服务器未打开服务端口,所有的POST都会被HttpClient自行取消,实际上是因为超时了,超时的代码

_httpClient.Timeout = new TimeSpan(0,0,0,3);

表示3秒。默认Timeout属性值是100秒。

    下图就是这个试验下,程序产生的现象,20个POST任务均被HttpClient中断:


    如果Timeout时间足够短,可能是产生TaskCanceledException异常,如果Timeout时间足够长,也可能产生HttpRequestException异常,表示无法建立连接。

实验2: 

        服务器打开,但是服务器不做任何应答,代码中task.Wait()部分注释掉,也就是连续不断的立即进行20个POST。


        这个实验的结论是:如果服务器不对POST信息做任何回答,那么,HttpClient最多维护2个tcp连接,如果产生超过2个POST那么,后续的POST被阻塞(个人猜测是进入某种队列等待中),超时时间到来之后(此处的超时是由于发了POST后,超时没由收到来自服务器对该POST的任何应答),这些未收到任何应答的POST任务会被取消并产生TaskCanceledException异常,然后,之前被阻塞的POST的任务会立即重新建立一个新连接(端口不一样)。

实验3):

        服务器随便给个不符合Http协议的应答。此时,任务会报HttpRequestException异常,再细查内部的异常是WebException异常,提示"服务器提交了协议冲突",因此,服务器必须应答的是HTTP协议的报文。

产生这个异常后,HttpClient会自行关闭掉这个连接。如果后续还有POST,那么它会立即建立一个新连接。


实验4):

        服务器随便给个符合Http协议的应答(需要用户自行写一个tcp应答程序,或者找一个可以自动应答的工具):

HTTP/1.1 200 OK
Content-Length: 5

abcde

        此时,如果是注释掉task.Wait(),那么结果是每次都能得到这个应答,并且最多维护2个连接。如下图:


        如果保留task.Wait(),最符合我们想象的情形出现了,就是我们会发现网络稳定的情形下HttpClient保持仅仅1个连接!!!! 此时是最省资源的情形了,也就是所有的POST信息都是在一个连接上进行的,系统也是最节约资源的!!! 如下图:



        实验4)的测试过程中还发现一个有趣的现象,也就是POST到服务器的数据:

POST /HTTP/1.1
Content-Type: text/plain;charset=utf-8
Host:  192.168.101.233:2317
Content-Length: 5
Expect: 100-continue
Connection: Keep-Alive

Hello
    红色部分和黑色部分在实际的tcp发送过程中被tcp传输层分片了,如下图(wireshark抓包)所示,1是红色部分信息,2是Hello信息:


       服务器这边,在对POST进行应答时,必须特别注意,应当是在收到完整的POST报文后再做应答,否则,如果服务器不按照HTTP协议的要求操作的话(比如,在收到红色部分就立即给应答),那么HttpClient可能会自行断开tcp连接,这意味着HttpClinet内部对协议做了严格分析,如果协议时序或者报文不符合http协议,HttpClient随时会断开连接(抓包发现断开连接是HttpClinet向服务器发送RST复位报文所致)。

 总结,对HttpClient的新的认识是:

1)HttpClient并不一定要static静态的,实际上连接数与HttpClient的实例个数有关系,并且一个HttpClient最多就同时维护2个tcp连接。因此,如果想增加连接,可以考虑适当增加HttpClient实例个数。

2) 本实验没有做延时性测试,但个人认为预热的问题不存在,HttpClient内部实现可能比较严格,服务器稍微有些伺候不好(比如服务器应答的不及时),HttpClient可能就会阻塞后续的POST任务,因此使用HttpClient的时候可能需要更多考虑服务器端的应答性能;

3) HttpClient POST数据之后,服务器如果做了非法应答(不符合Http协议),那么HttpClient会断开本次连接,并报告非法协议的异常。

4) 建议缩短Timeout的设置,默认的100秒太长,因为一个HttpClient最多同时维护两个tcp连接,因此如果由于超时时间太长,那么后续的大量POST会被阻塞,导致严重的POST不及时。因此,应当根据业务性质综合考虑,建议Timeout设置小一些,尽量让POST业务不被阻塞。也因此需要对网络情况,服务器应答的及时性进行优化。


  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: I'm sorry, but I need a little more context to understand what you're looking for. Can you please provide more information or ask a specific question? ### 回答2: C是一种高级编程语言,也是一种通用的编程语言。C语言由贝尔实验室的丹尼斯·里奇(Dennis Ritchie)在20世纪70年代初开发。C语言的设计目标是提供高效的编程语言,以实现系统级编程任务,如操作系统和编译器的开发。 C语言具有简洁、高效和可移植的特点。它的语法简单,易于理解和学习。C语言中有很少的关键字和语法规则,使得程序员能够更方便地编写代码。同时,C语言支持底层的操作,使得程序员能够直接控制计算机的硬件资源。 C语言也是一种比较高效的编程语言。由于其底层的特性,C语言的运行速度相对较快。这使得C语言在开发需要高性能的应用程序时非常受欢迎。 另一个C语言的重要特点是可移植性。C语言的设计使得代码在不同的机器和操作系统上都能够正常运行。这使得开发人员能够更方便地将程序从一种平台移植到另一种平台,提高了代码的复用性和可维护性。 C语言广泛应用于各种领域,包括嵌入式系统、操作系统、编译器、图形用户界面等。它也是很多其他编程语言的基础,如C++、Java和Python等。 总之,C语言是一种简洁、高效和可移植的编程语言,具有广泛的应用领域。无论是初学者还是有经验的开发人员都应该掌握C语言,因为它在计算机科学领域中扮演着非常重要的角色。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值