C#WebSocket Client底层实现

github地址:https://github.com/jf-acer/WebSocketClient.git

实现方式和官方客户端库一样:

static void Main(string[] args)
        {
            ClientWebSocket clientWebSocket = new ClientWebSocket();
            clientWebSocket.ConnectAsync(new Uri("ws://**.**.**.**:****"),CancellationToken.None).Wait();//
            //发送消息
            while (true)
            {
                ArraySegment<byte> bytesToSend = new ArraySegment<byte>(Encoding.UTF8.GetBytes("ok"));
                clientWebSocket.SendAsync(bytesToSend, WebSocketMessageType.Text, true, CancellationToken.None).Wait();
                Thread.Sleep(1000);
            }
        }

源码目录:

前言:双工通信WebSocket客户端一般都是JS的,如果想用后台模拟客户端一般都用第三方库,或者微软的官方库ClientWebSocket,但是ClientWebSocket不支持win7平台。

win7中会报以下错误:

 

查了下文档发现该类实现在win8以上,win7可以用但是只提供抽象类,没有具体实现,如果需要使用可以自行继承抽象类后实现。

需求:封装一个dll,每次调用dll时启动客户端连接WebSocket服务端,系统支持win7和win10,如果用第三方库时每次调用dll时也要加上第三方的库这就显得有点臃肿了,所以只能把底层实现封装在自己的dlll里面:

找了半天在Stackoverflow上面找到有一篇是Java改过来的:

https://stackoverflow.com/questions/2064641/is-there-a-websocket-client-implemented-for-net

调试时:

发现连握手都不成功还发送啥信息;

尝试自己实现:

WebSocket原理:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSockets_API/Writing_WebSocket_servers

一、握手:

public static void HandShake()
        {

            HttpClientHandler httpClientHandler = new HttpClientHandler()
            {
                Proxy = null
            };
            HttpClient httpClient = new HttpClient(httpClientHandler);
            httpClient.DefaultRequestHeaders.Add("Upgrade", "websocket");
            httpClient.DefaultRequestHeaders.Add("Connection", "Upgrade");
            httpClient.DefaultRequestHeaders.Add("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==");
            httpClient.DefaultRequestHeaders.Add("Host", "ip:port");
            //httpClient.DefaultRequestHeaders.Add("Origin", @"Http://ip");
            httpClient.DefaultRequestHeaders.Add("Sec-WebSocket-Version", "13");

            var reponse = httpClient.GetAsync(@"http://ip:port").GetAwaiter().GetResult();
            Console.WriteLine(reponse.Headers.ToString());

            //已经握手成功,如果不发送信息 一分四十秒后自动关闭连接
        }

注意握手时http请求版本要1.1以上,我使用HttpClient默认就是1.1,如果有需要可以修改版本:

//设置http版本
            var request = new HttpRequestMessage(HttpMethod.Get, @"http://ip:port") { Version = new Version(1, 1) };
            var reponse = httpClient.SendAsync(request).Result;

握手成功:

二:发送信息:

private static Stream _stream;
        private static TcpClient _tcpClient;
        private static ClientSslConfiguration _sslConfig;

        private static RemoteCertificateValidationCallback _certValidationCallback;
        private static readonly CancellationToken _cancellationToken;
        private static CancellationTokenRegistration _registration;
        internal static RemoteCertificateValidationCallback CertificateValidationCallback
        {
            get
            {
                return (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) => true;
            }
            set
            {
                _certValidationCallback = value;
            }
        }
        public static LocalCertificateSelectionCallback CertificateSelection { get; private set; }

        public Program(ClientSslConfiguration sslAuthConfiguration)
        {
            _sslConfig = sslAuthConfiguration;
        }


        static void Main(string[] args)
        {
            //握手
            HandShake();

            _registration = _cancellationToken.Register(async delegate
            {
                Console.WriteLine("注册");
            });
            _tcpClient = new TcpClient("ip", port);
            _stream = _tcpClient.GetStream();
            SslStream sslStream = new SslStream(_stream, leaveInnerStreamOpen: false, _sslConfig?.CertificateValidationCallback, _sslConfig?.CertificateSelection);
            if (_sslConfig != null)
            {
                sslStream.AuthenticateAsClientAsync("ip", _sslConfig.ClientCertificates, _sslConfig.EnabledSslProtocols, _sslConfig.CheckCertificateRevocation).ConfigureAwait(continueOnCapturedContext: false);
            }
            else
            {
                sslStream.AuthenticateAsClientAsync("ip").ConfigureAwait(continueOnCapturedContext: false);
            }
            _stream = sslStream;
            //_stream = new SslStream(_stream, false, CertificateValidationCallback, CertificateSelection);

            //开始发送信息

            try
            {

                while (true)
                {
                    Send("ok");
                    Thread.Sleep(1000);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadKey();
        }

        public static void Send(string str)
        {
            byte[] sendBuffer = Encoding.UTF8.GetBytes(str);
            _stream.WriteAsync(sendBuffer, 0, sendBuffer.Length, _cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
            _stream.FlushAsync(_cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
        }

这里ssl认证时失败,报:

sslStream.CipherStrength引发了类型System.InvalidOperationException的异常

只允许使用已成功验证身份的上下文执行此操作。

折腾了好一会最终放弃了去扣人家封装好库的源码:

第三方有:

SignalR

WebSocket4Net

websocket-sharp.clone

SuperSocket.WebSocket

System.Net.WebSockets.Client.Managed等等

看了大半天发现他们都差不多,最终选了一个代码最少的下手:

System.Net.WebSockets.Client.Managed

扣下来单纯连接和发送信息就有三千多行代码:这里附上底层最主要的两段:

连接:

public async Task ConnectAsyncCore(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
        {
            CancellationTokenRegistration registration = cancellationToken.Register(delegate (object s)
            {
                ((WebSocketHandle)s).Abort();
            }, this);
            try
            {
                Uri httpUri = new UriBuilder(uri)
                {
                    Scheme = ((uri.Scheme == "ws") ? "http" : "https")
                }.Uri;
                Uri connectUri = httpUri;
                bool useProxy = false;
                if (options.Proxy != null && !options.Proxy.IsBypassed(httpUri))
                {
                    useProxy = true;
                    connectUri = options.Proxy.GetProxy(httpUri);
                }
                Stream stream = new NetworkStream(await ConnectSocketAsync(connectUri.Host, connectUri.Port, cancellationToken).ConfigureAwait(continueOnCapturedContext: false), ownsSocket: true);
                if (useProxy)
                {
                    stream = await EstablishTunnelTrhoughWebProxy(stream, httpUri, connectUri, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
                }
                if (httpUri.Scheme == "https")
                {
                    SslStream sslStream = new SslStream(stream);
                    await sslStream.AuthenticateAsClientAsync(httpUri.Host, options.ClientCertificates, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12, checkCertificateRevocation: false).ConfigureAwait(continueOnCapturedContext: false);
                    stream = sslStream;
                }
                KeyValuePair<string, string> secKeyAndSecWebSocketAccept = CreateSecKeyAndSecWebSocketAccept();
                byte[] array = BuildRequestHeader(uri, options, secKeyAndSecWebSocketAccept.Key);
                await stream.WriteAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
                _webSocket = WebSocketUtil.CreateClientWebSocket(stream, await ParseAndValidateConnectResponseAsync(stream, options, secKeyAndSecWebSocketAccept.Value, cancellationToken).ConfigureAwait(continueOnCapturedContext: false), options.ReceiveBufferSize, options.SendBufferSize, options.KeepAliveInterval, useZeroMaskingKey: false, options.Buffer.GetValueOrDefault());
                if (_state == WebSocketState.Aborted)
                {
                    _webSocket.Abort();
                }
                else if (_state == WebSocketState.Closed)
                {
                    _webSocket.Dispose();
                }
            }
            catch (Exception ex)
            {
                if (_state < WebSocketState.Closed)
                {
                    _state = WebSocketState.Closed;
                }
                Abort();
                if (ex is WebSocketException)
                {
                    throw;
                }
                throw new WebSocketException(/*SR.net_webstatus_ConnectFailure, ex*/);
            }
            finally
            {
                registration.Dispose();
            }
        }
View Code

发送信息:

private async Task SendFrameFallbackAsync(MessageOpcode opcode, bool endOfMessage, ArraySegment<byte> payloadBuffer, CancellationToken cancellationToken)
        {
            await _sendFrameAsyncLock.WaitAsync().ConfigureAwait(continueOnCapturedContext: false);
            try
            {
                int count = WriteFrameToSendBuffer(opcode, endOfMessage, payloadBuffer);
                using (cancellationToken.Register(delegate (object s)
                {
                    ((ManagedWebSocket)s).Abort();
                }, this))
                {
                    await _stream.WriteAsync(_sendBuffer, 0, count, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
                }
            }
            catch (Exception innerException)
            {
                throw (_state == WebSocketState.Aborted) ? CreateOperationCanceledException(innerException, cancellationToken) : new WebSocketException(WebSocketError.ConnectionClosedPrematurely, innerException);
            }
            finally
            {
                _sendFrameAsyncLock.Release();
                ReleaseSendBuffer();
            }
        }
View Code

 

posted @ 2021-11-19 18:26  点终将连成线  阅读(3479)  评论(0编辑  收藏  举报