代码地址:https://gitee.com/qq28069933146_admin/csharp_networkprotocol_research

  视频地址: C#-WebSocket通讯示例演示_WinForm版 C#-WebSocket通讯示例演示_Web应用版

一、WinForm版服务器

C#-WebSocket协议通讯_Net5_Windows

1、WebSocket知识点

(1)侦听

  ① 使用httpListener进行侦听

  ② 若侦听到的内容是WebSocket协议,即httpListener.GetContextAsync().Request.IsWebSocketRequest,则对内容进行处理

private static HttpListener httpListener = new HttpListener();    // HttpListener

        /// <summary>
        /// 开启Socket服务,对参数地址进行监听
        /// </summary>
        /// <param name="ipAdress">监听地址,记得以 / 结尾</param>
        private async void Start(string ipAdress)
        {
            try
            {
                httpListener.Prefixes.Add(ipAdress); // 添加监听的URL范围
                // 通过连接名称可以区分多个websocket服务。如可以通过 http://localhost:8080/learn http://localhost:8080/work 使用两个服务,不过需要多线程和两个http监听对象等
                httpListener.Start();
                lblListen.Text = "监听中...";
                while (true)
                {
                    // http端口监听获取内容
                    HttpListenerContext httpListenerContext = await httpListener.GetContextAsync();
                    if (httpListenerContext.Request.IsWebSocketRequest)  // 如果是websocket请求
                    {
                        // 处理SocketRequest
                        ProcessRequest(httpListenerContext);
                    }
                    else
                    {
                        httpListenerContext.Response.StatusCode = 400;
                        httpListenerContext.Response.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                txtInfo.AppendText(ex.ToString() + DateTime.Now.ToString() + "\n");
            }
        }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
(2)侦听-处理内容并维护WebSocket客户端连接
/// <summary>
        /// 处理端口监听到的请求
        /// </summary>
        /// <param name="httpListenerContext"></param>
        private async void ProcessRequest(HttpListenerContext httpListenerContext)
        {
            WebSocketContext webSocketContext = null;  // WebSocketContext 类用于访问websocket握手中的信息
            try
            {
                webSocketContext = await httpListenerContext.AcceptWebSocketAsync(subProtocol: null);  // 配置协议为空
                // 获取客户端IP
                string ipAddress = httpListenerContext.Request.RemoteEndPoint.Address.ToString();
                txtInfo.AppendText("客户端IP地址:" + ipAddress + "\n");
            }
            catch (Exception e)  // 如果出错
            {
                httpListenerContext.Response.StatusCode = 500;
                httpListenerContext.Response.Close();
                txtInfo.AppendText("Exception:" + e.ToString() + DateTime.Now.ToString() + "\n");
                return;
            }
            // 获取websocket连接
            WebSocket webSocket = webSocketContext.WebSocket;
            _sockets.Add(webSocket);         // 此处将web socket对象加入一个静态列表中
            SendToNewConnection(webSocket);  // 将当前服务器上最新的数据(a,b的值)发送过去

            try
            {
                // 我们定义一个常数,它将表示接收到的数据的大小。 它是由我们建立的,我们可以设定任何值。 我们知道在这种情况下,发送的数据的大小非常小。
                const int maxMessageSize = 2048;
                // received bits的缓冲区

                while (webSocket != null && webSocket.State == WebSocketState.Open)  // 如果连接是打开的
                {
                    // 此句放在while里面,每次使用都重新初始化。如果放在外面,由于没有进行清空操作,下一次接收的数据若比上一次短,则会多出一部分内容。
                    var receiveBuffer = new ArraySegment<Byte>(new Byte[maxMessageSize]);

                    WebSocketReceiveResult receiveResult = null;
                    byte[] payloadData = null;
                    do
                    {
                        // 读取数据。此类的实例表示在 WebSocket 上执行单个 ReceiveAsync 操作所得到的结果
                        receiveResult = await webSocket.ReceiveAsync(receiveBuffer, CancellationToken.None);
                        // 字节数组
                        payloadData = receiveBuffer.Array.Where(b => b != 0).ToArray();
                    }
                    while (!receiveResult.EndOfMessage);  // 如果指示已完整接收消息则停止

                    // 如果输入帧为取消帧,发送close命令。
                    // MessageType指示当前消息是utf-8消息还是二进制信息。Text(0,明文形式),Close(2,收到关闭消息,接受已完成),Binary(1,消息采用二进制格式)
                    if (receiveResult.MessageType == WebSocketMessageType.Close)
                    {
                        await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, String.Empty, CancellationToken.None);
                        _sockets.Remove(webSocket);  // 从列表移除当前连接

                    }
                    else
                    {
                        // 因为我们知道这是一个字符串,我们转换它                        
                        string receiveString = System.Text.Encoding.UTF8.GetString(payloadData, 0, payloadData.Length);

                        try  // 将反序列化内容放入try中,避免无法匹配、内容为空等可能报错的地方
                        {
                            // 将转换后的字符串内容进行json反序列化。参考:
                            TestValue tv = JsonConvert.DeserializeObject<TestValue>(receiveString);
                            // 将收到的a,b的值显示到文本框
                            if (tv != null)
                            {
                                string valueA = string.Empty, valueB = string.Empty;
                                if (tv.a != null && tv.a.Length > 0) { valueA = tv.a; }
                                if (tv.a != null && tv.b.Length > 0) { valueB = tv.b; }
                                txtAvalue.Text = valueA;
                                txtBvalue.Text = valueB;
                            }

                            RefreshConnectionList();  // 先清理无效的连接,否则会导致服务端websocket被dispose

                            //  当接收到文本消息时,对当前服务器上所有web socket连接进行广播
                            foreach (var innerSocket in _sockets)
                            {
                                await innerSocket.SendAsync(new ArraySegment<byte>(payloadData), WebSocketMessageType.Text, true, CancellationToken.None);
                            }
                        }
                        catch (Exception ex)
                        {
                            // 如果json反序列化出了问题
                            txtInfo.AppendText(ex.ToString() + DateTime.Now.ToString() + "\n");  // 将错误类型显示出来
                            txtInfo.AppendText(receiveString + DateTime.Now.ToString() + "\n");  // 将收到的原始字符串显示出来
                        }
                    }
                }
            }
            catch (Exception e)
            {
                if (e.GetType().ToString() == "System.Net.WebSockets.WebSocketException")
                {
                    // 客户端关闭时会抛出此错误
                    txtInfo.AppendText("连接已关闭" + DateTime.Now.ToString() + "\n");
                }
                else
                {
                    txtInfo.AppendText(e.ToString() + DateTime.Now.ToString() + "\n");
                }
            }
        }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
(3)发送
WebSocket webSocket=webSocketContext.WebSocket;

// 发送信息
string jsonmessage="信息内容";  // 信息内容
Byte[] bytes = System.Text.Encoding.UTF8.GetBytes(jsonmessage);
webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);  // 发送
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

2、完整示例

(1)WinForm服务器端:

  ① WinSocketServer1.Designer.cs

查看代码

namespace WinSocketServer
{
    partial class WinSocketServer1
    {
        /// <summary>
        ///  Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        ///  Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        ///  Required method for Designer support - do not modify
        ///  the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            btnModify = new System.Windows.Forms.Button();
            label1 = new System.Windows.Forms.Label();
            txtIPAddress = new System.Windows.Forms.TextBox();
            label2 = new System.Windows.Forms.Label();
            label3 = new System.Windows.Forms.Label();
            txtAvalue = new System.Windows.Forms.TextBox();
            txtBvalue = new System.Windows.Forms.TextBox();
            btnBroadcast = new System.Windows.Forms.Button();
            panel1 = new System.Windows.Forms.Panel();
            btnClose = new System.Windows.Forms.Button();
            btnOpenServer = new System.Windows.Forms.Button();
            txtInfo = new System.Windows.Forms.TextBox();
            btnShowCon = new System.Windows.Forms.Button();
            lblListen = new System.Windows.Forms.Label();
            panel1.SuspendLayout();
            SuspendLayout();
            // 
            // btnModify
            // 
            btnModify.Location = new System.Drawing.Point(271, 10);
            btnModify.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            btnModify.Name = "btnModify";
            btnModify.Size = new System.Drawing.Size(73, 25);
            btnModify.TabIndex = 0;
            btnModify.Text = "修改";
            btnModify.UseVisualStyleBackColor = true;
            btnModify.Click += btnModify_Click;
            // 
            // label1
            // 
            label1.AutoSize = true;
            label1.Location = new System.Drawing.Point(9, 14);
            label1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
            label1.Name = "label1";
            label1.Size = new System.Drawing.Size(56, 17);
            label1.TabIndex = 1;
            label1.Text = "监听地址";
            // 
            // txtIPAddress
            // 
            txtIPAddress.Enabled = false;
            txtIPAddress.Location = new System.Drawing.Point(68, 11);
            txtIPAddress.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            txtIPAddress.Name = "txtIPAddress";
            txtIPAddress.Size = new System.Drawing.Size(200, 23);
            txtIPAddress.TabIndex = 2;
            txtIPAddress.Text = "http://127.0.0.1:8080/";
            // 
            // label2
            // 
            label2.AutoSize = true;
            label2.Location = new System.Drawing.Point(9, 48);
            label2.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
            label2.Name = "label2";
            label2.Size = new System.Drawing.Size(27, 17);
            label2.TabIndex = 3;
            label2.Text = "a:";
            // 
            // label3
            // 
            label3.AutoSize = true;
            label3.Location = new System.Drawing.Point(9, 78);
            label3.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
            label3.Name = "label3";
            label3.Size = new System.Drawing.Size(28, 17);
            label3.TabIndex = 4;
            label3.Text = "b:";
            // 
            // txtAvalue
            // 
            txtAvalue.Location = new System.Drawing.Point(68, 45);
            txtAvalue.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            txtAvalue.Name = "txtAvalue";
            txtAvalue.Size = new System.Drawing.Size(161, 23);
            txtAvalue.TabIndex = 5;
            // 
            // txtBvalue
            // 
            txtBvalue.Location = new System.Drawing.Point(68, 76);
            txtBvalue.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            txtBvalue.Name = "txtBvalue";
            txtBvalue.Size = new System.Drawing.Size(161, 23);
            txtBvalue.TabIndex = 6;
            // 
            // btnBroadcast
            // 
            btnBroadcast.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            btnBroadcast.Location = new System.Drawing.Point(244, 48);
            btnBroadcast.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            btnBroadcast.Name = "btnBroadcast";
            btnBroadcast.Size = new System.Drawing.Size(100, 48);
            btnBroadcast.TabIndex = 7;
            btnBroadcast.Text = "广播";
            btnBroadcast.UseVisualStyleBackColor = true;
            btnBroadcast.Click += btnBroadcast_Click;
            // 
            // panel1
            // 
            panel1.Controls.Add(btnClose);
            panel1.Controls.Add(btnOpenServer);
            panel1.Location = new System.Drawing.Point(368, 3);
            panel1.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            panel1.Name = "panel1";
            panel1.Size = new System.Drawing.Size(110, 96);
            panel1.TabIndex = 10;
            // 
            // btnClose
            // 
            btnClose.Location = new System.Drawing.Point(19, 60);
            btnClose.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            btnClose.Name = "btnClose";
            btnClose.Size = new System.Drawing.Size(73, 25);
            btnClose.TabIndex = 11;
            btnClose.Text = "关闭监听";
            btnClose.UseVisualStyleBackColor = true;
            btnClose.Click += btnClose_Click;
            // 
            // btnOpenServer
            // 
            btnOpenServer.Location = new System.Drawing.Point(19, 11);
            btnOpenServer.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            btnOpenServer.Name = "btnOpenServer";
            btnOpenServer.Size = new System.Drawing.Size(73, 25);
            btnOpenServer.TabIndex = 10;
            btnOpenServer.Text = "开启监听";
            btnOpenServer.UseVisualStyleBackColor = true;
            btnOpenServer.Click += btnOpenServer_Click;
            // 
            // txtInfo
            // 
            txtInfo.Location = new System.Drawing.Point(9, 121);
            txtInfo.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            txtInfo.Multiline = true;
            txtInfo.Name = "txtInfo";
            txtInfo.Size = new System.Drawing.Size(604, 252);
            txtInfo.TabIndex = 11;
            // 
            // btnShowCon
            // 
            btnShowCon.Location = new System.Drawing.Point(510, 56);
            btnShowCon.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            btnShowCon.Name = "btnShowCon";
            btnShowCon.Size = new System.Drawing.Size(92, 39);
            btnShowCon.TabIndex = 12;
            btnShowCon.Text = "显示当前连接";
            btnShowCon.UseVisualStyleBackColor = true;
            btnShowCon.Click += btnShowCon_Click;
            // 
            // lblListen
            // 
            lblListen.AutoSize = true;
            lblListen.Font = new System.Drawing.Font("Microsoft YaHei UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
            lblListen.Location = new System.Drawing.Point(510, 18);
            lblListen.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
            lblListen.Name = "lblListen";
            lblListen.Size = new System.Drawing.Size(92, 17);
            lblListen.TabIndex = 13;
            lblListen.Text = "服务为关闭状态";
            lblListen.TextAlign = System.Drawing.ContentAlignment.TopRight;
            // 
            // WinSocketServer1
            // 
            AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
            AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            ClientSize = new System.Drawing.Size(622, 382);
            Controls.Add(lblListen);
            Controls.Add(btnShowCon);
            Controls.Add(txtInfo);
            Controls.Add(panel1);
            Controls.Add(btnBroadcast);
            Controls.Add(txtBvalue);
            Controls.Add(txtAvalue);
            Controls.Add(label3);
            Controls.Add(label2);
            Controls.Add(txtIPAddress);
            Controls.Add(label1);
            Controls.Add(btnModify);
            Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
            Name = "WinSocketServer1";
            Text = "Win_Socker服务器";
            Load += WinSockerServer1_Load;
            panel1.ResumeLayout(false);
            ResumeLayout(false);
            PerformLayout();
        }

        #endregion

        private System.Windows.Forms.Button btnModify;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.TextBox txtIPAddress;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.TextBox txtAvalue;
        private System.Windows.Forms.TextBox txtBvalue;
        private System.Windows.Forms.Button btnBroadcast;
        private System.Windows.Forms.Panel panel1;
        private System.Windows.Forms.Button btnClose;
        private System.Windows.Forms.Button btnOpenServer;
        private System.Windows.Forms.TextBox txtInfo;
        private System.Windows.Forms.Button btnShowCon;
        private System.Windows.Forms.Label lblListen;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.

  ② WinSocketServer1.cs

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WinSocketServer
{
    public partial class WinSocketServer1 : Form
    {
        public WinSocketServer1()
        {
            InitializeComponent();
        }

        private void WinSockerServer1_Load(object sender, EventArgs e)
        {

        }
        /// <summary>
        /// 打开服务
        /// </summary>
        private void btnOpenServer_Click(object sender, EventArgs e)
        {
            if (txtIPAddress.Enabled == true)
            {
                MessageBox.Show("请先确认地址");
                return;
            }
            string IpAdress = txtIPAddress.Text;
            txtInfo.AppendText("打开监听" + DateTime.Now.ToString() + "\n");
            Start(IpAdress);
        }



        /// <summary>
        /// 关闭服务
        /// </summary>
        private void btnClose_Click(object sender, EventArgs e)
        {
            if (httpListener.IsListening)
            {
                try
                {
                    httpListener.Stop();
                    txtInfo.AppendText("成功关闭" + DateTime.Now.ToString() + "\n");
                    lblListen.Text = "服务为关闭状态";
                }
                catch (Exception ex)
                {
                    txtInfo.AppendText(ex.ToString() + DateTime.Now.ToString() + "\n");
                }
            }
            else
            {
                txtInfo.AppendText("此时服务并未处于监听状态,无法关闭" + DateTime.Now.ToString() + "\n");
                lblListen.Text = "服务为关闭状态";
                return;
            }
        }

        /// <summary>
        /// 修改
        /// </summary>
        private void btnModify_Click(object sender, EventArgs e)
        {
            if (btnModify.Text == "修改")
            {
                txtIPAddress.Enabled = true;
                btnModify.Text = "确定";
            }
            else if (btnModify.Text == "确定")
            {
                txtIPAddress.Enabled = false;
                btnModify.Text = "修改";
            }
        }

        /// <summary>
        /// 广播
        /// </summary>
        private void btnBroadcast_Click(object sender, EventArgs e)
        {
            string jsonmessage = RequestMsg.SerializeJson(txtAvalue.Text, txtBvalue.Text);
            txtInfo.AppendText("广播" + DateTime.Now.ToString() + ":\n");
            txtInfo.AppendText("内容:" + jsonmessage + "\n");
            Broadcast(jsonmessage);
        }
        /// <summary>
        /// 显示链接列表
        /// </summary>
        private void btnShowCon_Click(object sender, EventArgs e)
        {
            int ConnectionCount = _sockets.Count;
            txtInfo.AppendText("服务端当前存储了" + ConnectionCount + "个客户端连接:\n" + DateTime.Now.ToString() + "\n");
            foreach (WebSocket innersocket in _sockets)
            {
                txtInfo.AppendText(innersocket.GetHashCode().ToString() + innersocket.State.ToString() + "\n");
            }
        }

        #region 监听方法
        private static List<WebSocket> _sockets = new List<WebSocket>();  // 存储当前所有连接的静态列表
        private static HttpListener httpListener = new HttpListener();    // HttpListener

        /// <summary>
        /// 开启Socket服务,对参数地址进行监听
        /// </summary>
        /// <param name="ipAdress">监听地址,记得以 / 结尾</param>
        private async void Start(string ipAdress)
        {
            try
            {
                httpListener.Prefixes.Add(ipAdress); // 添加监听的URL范围
                // 通过连接名称可以区分多个websocket服务。如可以通过 http://localhost:8080/learn http://localhost:8080/work 使用两个服务,不过需要多线程和两个http监听对象等
                httpListener.Start();
                lblListen.Text = "监听中...";
                while (true)
                {
                    // http端口监听获取内容
                    HttpListenerContext httpListenerContext = await httpListener.GetContextAsync();
                    if (httpListenerContext.Request.IsWebSocketRequest)  // 如果是websocket请求
                    {
                        // 处理SocketRequest
                        ProcessRequest(httpListenerContext);
                    }
                    else
                    {
                        httpListenerContext.Response.StatusCode = 400;
                        httpListenerContext.Response.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                txtInfo.AppendText(ex.ToString() + DateTime.Now.ToString() + "\n");
            }
        }

        /// <summary>
        /// 处理端口监听到的请求
        /// </summary>
        /// <param name="httpListenerContext"></param>
        private async void ProcessRequest(HttpListenerContext httpListenerContext)
        {
            WebSocketContext webSocketContext = null;  // WebSocketContext 类用于访问websocket握手中的信息
            try
            {
                webSocketContext = await httpListenerContext.AcceptWebSocketAsync(subProtocol: null);  // 配置协议为空
                // 获取客户端IP
                string ipAddress = httpListenerContext.Request.RemoteEndPoint.Address.ToString();
                txtInfo.AppendText("客户端IP地址:" + ipAddress + "\n");
            }
            catch (Exception e)  // 如果出错
            {
                httpListenerContext.Response.StatusCode = 500;
                httpListenerContext.Response.Close();
                txtInfo.AppendText("Exception:" + e.ToString() + DateTime.Now.ToString() + "\n");
                return;
            }
            // 获取websocket连接
            WebSocket webSocket = webSocketContext.WebSocket;
            _sockets.Add(webSocket);         // 此处将web socket对象加入一个静态列表中
            SendToNewConnection(webSocket);  // 将当前服务器上最新的数据(a,b的值)发送过去

            try
            {
                // 我们定义一个常数,它将表示接收到的数据的大小。 它是由我们建立的,我们可以设定任何值。 我们知道在这种情况下,发送的数据的大小非常小。
                const int maxMessageSize = 2048;
                // received bits的缓冲区

                while (webSocket != null && webSocket.State == WebSocketState.Open)  // 如果连接是打开的
                {
                    // 此句放在while里面,每次使用都重新初始化。如果放在外面,由于没有进行清空操作,下一次接收的数据若比上一次短,则会多出一部分内容。
                    var receiveBuffer = new ArraySegment<Byte>(new Byte[maxMessageSize]);

                    WebSocketReceiveResult receiveResult = null;
                    byte[] payloadData = null;
                    do
                    {
                        // 读取数据。此类的实例表示在 WebSocket 上执行单个 ReceiveAsync 操作所得到的结果
                        receiveResult = await webSocket.ReceiveAsync(receiveBuffer, CancellationToken.None);
                        // 字节数组
                        payloadData = receiveBuffer.Array.Where(b => b != 0).ToArray();
                    }
                    while (!receiveResult.EndOfMessage);  // 如果指示已完整接收消息则停止

                    // 如果输入帧为取消帧,发送close命令。
                    // MessageType指示当前消息是utf-8消息还是二进制信息。Text(0,明文形式),Close(2,收到关闭消息,接受已完成),Binary(1,消息采用二进制格式)
                    if (receiveResult.MessageType == WebSocketMessageType.Close)
                    {
                        await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, String.Empty, CancellationToken.None);
                        _sockets.Remove(webSocket);  // 从列表移除当前连接

                    }
                    else
                    {
                        // 因为我们知道这是一个字符串,我们转换它                        
                        string receiveString = System.Text.Encoding.UTF8.GetString(payloadData, 0, payloadData.Length);

                        try  // 将反序列化内容放入try中,避免无法匹配、内容为空等可能报错的地方
                        {
                            // 将转换后的字符串内容进行json反序列化。参考:
                            TestValue tv = JsonConvert.DeserializeObject<TestValue>(receiveString);
                            // 将收到的a,b的值显示到文本框
                            if (tv != null)
                            {
                                string valueA = string.Empty, valueB = string.Empty;
                                if (tv.a != null && tv.a.Length > 0) { valueA = tv.a; }
                                if (tv.a != null && tv.b.Length > 0) { valueB = tv.b; }
                                txtAvalue.Text = valueA;
                                txtBvalue.Text = valueB;
                            }

                            RefreshConnectionList();  // 先清理无效的连接,否则会导致服务端websocket被dispose

                            //  当接收到文本消息时,对当前服务器上所有web socket连接进行广播
                            foreach (var innerSocket in _sockets)
                            {
                                await innerSocket.SendAsync(new ArraySegment<byte>(payloadData), WebSocketMessageType.Text, true, CancellationToken.None);
                            }
                        }
                        catch (Exception ex)
                        {
                            // 如果json反序列化出了问题
                            txtInfo.AppendText(ex.ToString() + DateTime.Now.ToString() + "\n");  // 将错误类型显示出来
                            txtInfo.AppendText(receiveString + DateTime.Now.ToString() + "\n");  // 将收到的原始字符串显示出来
                        }
                    }
                }
            }
            catch (Exception e)
            {
                if (e.GetType().ToString() == "System.Net.WebSockets.WebSocketException")
                {
                    // 客户端关闭时会抛出此错误
                    txtInfo.AppendText("连接已关闭" + DateTime.Now.ToString() + "\n");
                }
                else
                {
                    txtInfo.AppendText(e.ToString() + DateTime.Now.ToString() + "\n");
                }
            }
        }
        #endregion 监听方法

        #region 广播
        /// <summary>
        /// 服务端主动向所有客户端广播
        /// </summary>
        /// <param name="jsonmessage">传过来的应该是序列化后的json字符串,接收方会通过TestValue类进行反序列化获取a,b的内容</param>
        public async void Broadcast(string jsonmessage)
        {
            try
            {
                Byte[] bytes = System.Text.Encoding.UTF8.GetBytes(jsonmessage);
                RefreshConnectionList();//先清理无效的连接,否则会导致服务端websocket被dispose
                //当接收到文本消息时,对当前服务器上所有web socket连接进行广播
                foreach (var innerSocket in _sockets)
                {
                    await innerSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
                }
            }
            catch (Exception ex)
            {
                txtInfo.AppendText(ex.ToString() + DateTime.Now.ToString() + "\n");
                MessageBox.Show("某些连接出了问题,如果广播多次出问题,请重启服务端");
            }
        }
        /// <summary>
        /// 监听到一个新的websocket连接后,将服务器当前最新数据同步过去
        /// </summary>
        /// <param name="currentWebsocket">当前新接入的websocket连接</param>
        public async void SendToNewConnection(WebSocket currentWebsocket)
        {
            try
            {
                string jsonmessage = RequestMsg.SerializeJson(txtAvalue.Text, txtBvalue.Text);
                Byte[] bytes = System.Text.Encoding.UTF8.GetBytes(jsonmessage);

                if (currentWebsocket.State == WebSocketState.Open)
                {
                    await currentWebsocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);//try访问已释放对象问题
                }
                else
                {
                    //此处并不对该链接进行移除,会导致调用本方法后的代码出问题,只需在进行发送时确认状态即可
                    txtInfo.AppendText("新接入连接:" + currentWebsocket.GetHashCode().ToString() + "的连接状态不为open,因此停止向其同步数据" + DateTime.Now.ToString() + "\n");
                }
            }
            catch (Exception ex)
            {
                txtInfo.AppendText(ex.ToString() + DateTime.Now.ToString() + "\n");
            }
        }

        /// <summary>
        /// 刷新当前websocket连接列表,如果状态为Closed,则移除。连接异常断开后会被dispose,如果访问会报错,但可以获取状态为closed
        /// </summary>
        public static void RefreshConnectionList()
        {
            if (_sockets != null)//lock不能锁定空值
            {
                lock (_sockets)//锁定数据源
                {
                    //System.InvalidOperationException: 集合已修改;可能无法执行枚举操作。
                    //使用foreach不能执行删除、修改,这是规定。你可以使用for循环遍历修改。删除数据正确做法,for循环 i 要从大到小
                    for (int i = _sockets.Count - 1; i >= 0; i--)
                    {
                        if (_sockets[i].State != WebSocketState.Open)
                        {
                            _sockets.Remove(_sockets[i]);
                        }
                    }
                }
            }

        }
        #endregion
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
(2)WebForm客户端:

WinSocketClent.cshtml (注:Clent改为Client,写错了

@{
    ViewData["Title"] = "WinSocket客户端";
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>WinSocket客户端</title>
</head>
<body>
    <div>
        <form id="Form">
            <label id="address">服务端地址:</label>
            <input name="" id="IpAddress" value="ws://127.0.0.1:8080" />
            <button type="submit" onclick="Connect();">连接</button><br />

            <label id="a">a</label>
            <input name="" id="ValueA" value="" />
            <button type="submit" onclick="SendData();">提交</button><br />

            <label id="b">b</label>
            <input name="" id="ValueB" value="" />
        </form>
        <div id="txtInfo" style="border: dashed 1px black;height: 500px;width: 500px;margin-top: 10px;"></div>
    </div>
</body>
</html>
@{
<script type="text/javascript">
    var test = function () {

        var print = document.getElementById('txtInfo');
        var form = document.getElementById('Form');
        var inputStrA = document.getElementById('ValueA');
        var ipaddress = document.getElementById('IpAddress').value;

        print.innerHTML += "connecting to server ..<br/>";

        //参数即想要连接服务器的ip。 服务器可以是node.js, 也可以是其他项目,如c#
        //window.ws = new WebSocket('ws://10.206.14.152:8080/'); //连接服务
        //window.ws = new WebSocket('ws://192.168.43.78:8080/'); //连接服务
        window.ws = new WebSocket(ipaddress); //连接服务


        //监听消息状态
        ws.onmessage = function (msg) {
            var jsobj = JSON.parse(msg.data);//json字符串转为对象
            document.getElementById('ValueA').value = jsobj.a;//写入输入框中
            document.getElementById('ValueB').value = jsobj.b;

            print.innerHTML += msg.data + '<br/>'
        }
        //监听链接状态
        ws.onopen = function () {
            print.innerHTML += '监听 打开 <br/>'
        }
        //监听关闭状态
        ws.onclose = function () {
            print.innerHTML += '监听 关闭<br/>';
        }

        //向服务器端发送消息
        form.addEventListener('submit', function (e) {
            e.preventDefault();
            //序列化后的字符串发给服务端,服务端以字节数组格式接收,若转成字符串并添加部分信息再转回字节数组时,string.format函数会报错
            //var jsonObj = { "name": "zhangsan", "age": 18 };
            //传入一个json字符串进行测试,服务器会进行判断。
            //ws.send(JSON.stringify(jsonObj));
            //ws.send(jsonObj);
            //传入一个非json字符串进行测试,服务器会进行判断。
            //ws.send("test");
        })
    }
    //window.onload = test();

    function Connect() {
        test();
    }

    function SendData() {
        var valueA = document.getElementById('ValueA').value;
        var valueB = document.getElementById('ValueB').value;

        if (valueA.length == 0) { valueA = "-"; }
        if (valueB.length == 0) { valueB = "-"; }

        var jsonobj = {
            "a": valueA,
            "b": valueB
        };
        var jsonstr = JSON.stringify(jsonobj);

        //初始化WebSocket
        //InitWebSocket();
        //如果WebSocket打开,发送数据
        if (ws.OPEN && ws.readyState == 1) {
            ws.send(jsonstr);
        }
        console.log(ws.readyState);
        //如果WebSocket关闭,显示消息
        if (ws.readyState == 2 || ws.readyState == 3) {
            console.log("进来关闭函数了")
            alert("WebSocket关闭了,无法发送数据");
        }
    }
</script>
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.

二、WebAPI版服务器

  • 可以使用“基于Http/2 的WebSockets 支持”  
  • 标头压缩。
  • 多路复用,可减少向服务器发出多个请求时所需的时间和资源。
  • HTTP/2 WebSockets 使用 CONNECT 请求而不是 GET,因此可能需要更新你自己的路由和控制器。见: 为现有控制器添加 HTTP/2 WebSockets 支持
  • Chrome 和 Edge 默认启用 HTTP/2 WebSocket,对于 FireFox,则可在 about:config 页面中使用 network.http.spdy.websockets 标志来启用它。
  • HTTP/2 WebSockets 使用 CONNECT 请求而不是 GET,因此可能需要更新你自己的路由和控制器。
  • 与WebSocket 相比,SignalR具有以下优点:
  • 可为 WebSocket 不可用的环境提供传输回退。
  • 可提供基本的远程过程调用应用模型。
  • 在大多数情况下,与使用原始 WebSocket 相比,没有显著的性能缺点。

1、WebSocket服务器_Web应用版

(1)在 Program.cs 中启用 WebSockets 中间件:
app.UseWebSockets();  // 1、启用 WebSocket 中间件

            // 1、启用 WebSocket 中间件并自定义配置
            //Microsoft.AspNetCore.Builder.WebSocketOptions webSocketOptions = new Microsoft.AspNetCore.Builder.WebSocketOptions
            //{
            //    KeepAliveInterval = TimeSpan.FromMinutes(2),
            //};
             WebSocket 源限制只允许 https://client.com、https://www.client.com的客户端反馈;
            //webSocketOptions.AllowedOrigins.Add("https://client.com");
            //webSocketOptions.AllowedOrigins.Add("https://www.client.com");
            //app.UseWebSockets(webSocketOptions);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
(2)创建一个WebSockets帮助类
  • 示例方法1:循环发送并反馈消息-未加密
  • 示例方法2:循环发送和转发消息(聊天室)-未加密
using System.Net.WebSockets;
using System.Threading.Tasks;
using System.Threading;
using System;
using System.Collections.Generic;

namespace WebAPIWebSockets
{
    /// <summary>
    /// WebSockets帮助类
    /// </summary>
    public static class WebSocketsHelper
    {
        /// <summary>
        /// 循环发送并反馈消息-未加密
        /// 逻辑:如果在开始循环之前接受 WebSocket 连接,中间件管道会结束。 关闭套接字后,管道展开。 即接受 WebSocket 时,请求停止在管道中推进。 循环结束且套接字关闭时,请求继续回到管道。
        /// </summary>
        /// <param name="webSocket">WebSocket对象</param>
        /// <returns></returns>
        public static async Task Echo(WebSocket webSocket)
        {
            var buffer = new byte[1024 * 4];
            var receiveResult = await webSocket.ReceiveAsync(  // 接收消息
                new ArraySegment<byte>(buffer), CancellationToken.None);

            while (!receiveResult.CloseStatus.HasValue)  // webSocket接收开启时,循环接收、转发
            {
                await webSocket.SendAsync(  // 转发消息
                    new ArraySegment<byte>(buffer, 0, receiveResult.Count),
                    receiveResult.MessageType,
                    receiveResult.EndOfMessage,
                    CancellationToken.None);

                receiveResult = await webSocket.ReceiveAsync(  // 接收消息
                    new ArraySegment<byte>(buffer), CancellationToken.None);
            }

            await webSocket.CloseAsync(  // 关闭WebSocket连接
                receiveResult.CloseStatus.Value,
                receiveResult.CloseStatusDescription,
                CancellationToken.None);
        }

        private static List<WebSocket> _webSockets = new List<WebSocket>();
        /// <summary>
        /// 循环发送和转发消息(聊天室)-未加密
        /// 逻辑:如果在开始循环之前接受 WebSocket 连接,中间件管道会结束。 关闭套接字后,管道展开。 即接受 WebSocket 时,请求停止在管道中推进。 循环结束且套接字关闭时,请求继续回到管道。
        /// </summary>
        /// <param name="webSocket">WebSocket对象</param>
        /// <returns></returns>
        public static async Task ChatRoom(WebSocket webSocket)
        {
            // 将webSocket加入到_webSockets
            if (!_webSockets.Contains(webSocket))
            {
                _webSockets.Add(webSocket);
            }

            var buffer = new byte[1024 * 4];
            var receiveResult = await webSocket.ReceiveAsync(  // 接收消息
                new ArraySegment<byte>(buffer), CancellationToken.None);

            while (!receiveResult.CloseStatus.HasValue)  // webSocket接收开启时,循环接收、转发
            {
                List<WebSocket> webSockets = new List<WebSocket>();

                foreach (WebSocket webSocket1 in _webSockets)
                {
                    if (webSocket1.State == WebSocketState.Open)
                    {
                        await webSocket1.SendAsync(  // 转发消息
                           new ArraySegment<byte>(buffer, 0, receiveResult.Count),
                           receiveResult.MessageType,
                           receiveResult.EndOfMessage,
                           CancellationToken.None);

                        webSockets.Add(webSocket1);
                    }
                }
                _webSockets= webSockets;

                receiveResult = await webSocket.ReceiveAsync(  // 接收消息
                    new ArraySegment<byte>(buffer), CancellationToken.None);
            }
            if (_webSockets.Count <= 0)
            {
                await webSocket.CloseAsync(  // 关闭WebSocket连接
                    receiveResult.CloseStatus.Value,
                    receiveResult.CloseStatusDescription,
                    CancellationToken.None);
            }
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
(3)控制器中接收ws协议
  • 示例方法1:循环发送并反馈消息-未加密
  • 示例方法2:循环发送和转发消息(聊天室)-未加密
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Threading.Tasks;

namespace WebAPIWebSockets.Controllers
{
    public class WebSocketController : ControllerBase
    {
        [Route("/wsTest")]
        public async Task Get()
        {
            if (HttpContext.WebSockets.IsWebSocketRequest)
            {
                using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
                await WebSocketsHelper.Echo(webSocket);
            }
            else
            {
                HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
            }
        }

        private static List<WebSocket> _sockets = new List<WebSocket>();  // 存储当前所有连接的静态列表
        [Route("/ChatRoom")]
        public async Task ChatRoom()
        {
            try
            {
                if (HttpContext.WebSockets.IsWebSocketRequest)
                {
                    using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
                    await WebSocketsHelper.ChatRoom(webSocket);
                }
                else
                {
                    HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
                }
            }
            catch(Exception ex) 
            {
                string message = ex.Message;
            }
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.

2、WebSocket客户端_JavaScript版

  • 创建 WebSocket客户端 连接对象:socket = new WebSocket(connectionUrl.value);
  • 发送:socket.send(data);
  • 关闭:socket.close(1000, "从客户端关闭");
  • 打开事件:socket.onopen = function (event) {  ...};  // 发送事件
  • 接收事件:socket.onmessage = function (event) {  ...};  // 接收事件;消息内容为event.data
  • 关闭事件:socket.onclose = function (event) {  ...};  // 关闭事件
  • 出错事件:socket.onerror = updateState;  // 报错; function updateState() {}

  完整代码如下:

@{
    ViewData["Title"] = "WebSocket示例页";
}

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <style>
        table {
            border: 0
        }

        .commslog-data {
            font-family: Consolas, Courier New, Courier, monospace;
        }

        .commslog-server {
            background-color: red;
            color: white
        }

        .commslog-client {
            background-color: green;
            color: white
        }
    </style>
</head>
<body>
    <h1>WebSocket示例</h1>
    <p id="stateLabel">准备连接...</p>
    <div>
        <label for="connectionUrl">WebSocket服务器的URL:</label>
        <input id="connectionUrl" />
        <button id="connectButton" type="submit">连接</button>
    </div>
    <p></p>
    <div>
        <label for="sendMessage">要发送的消息:</label>
        <input id="sendMessage" disabled />
        <button id="sendButton" type="submit" disabled>发送</button>
        <button id="closeButton" disabled>关闭Socket</button>
    </div>

    <h2>通信日志</h2>
    <table style="width: 800px">
        <thead>
            <tr>
                <td style="width: 100px"></td>
                <td style="width: 100px">目标</td>
                <td>数据</td>
            </tr>
        </thead>
        <tbody id="commsLog">
        </tbody>
    </table>

    <script>
        var connectionUrl = document.getElementById("connectionUrl");
        var connectButton = document.getElementById("connectButton");
        var stateLabel = document.getElementById("stateLabel");
        var sendMessage = document.getElementById("sendMessage");
        var sendButton = document.getElementById("sendButton");
        var commsLog = document.getElementById("commsLog");
        var closeButton = document.getElementById("closeButton");
        var socket;

        var scheme = document.location.protocol === "https:" ? "wss" : "ws";
        var port = document.location.port ? (":" + document.location.port) : "";

        connectionUrl.value = scheme + "://" + document.location.hostname + port + "/ChatRoom";

        function updateState() {
            function disable() {
                sendMessage.disabled = true;
                sendButton.disabled = true;
                closeButton.disabled = true;
            }
            function enable() {
                sendMessage.disabled = false;
                sendButton.disabled = false;
                closeButton.disabled = false;
            }

            connectionUrl.disabled = true;
            connectButton.disabled = true;

            if (!socket) {
                disable();
            } else {
                switch (socket.readyState) {
                    case WebSocket.CLOSED:
                        stateLabel.innerHTML = "关闭";
                        disable();
                        connectionUrl.disabled = false;
                        connectButton.disabled = false;
                        break;
                    case WebSocket.CLOSING:
                        stateLabel.innerHTML = "正在关闭...";
                        disable();
                        break;
                    case WebSocket.CONNECTING:
                        stateLabel.innerHTML = "正在连接...";
                        disable();
                        break;
                    case WebSocket.OPEN:
                        stateLabel.innerHTML = "打开";
                        enable();
                        break;
                    default:
                        stateLabel.innerHTML = "未知的WebSocket状态: " + htmlEscape(socket.readyState);
                        disable();
                        break;
                }
            }
        }

        closeButton.onclick = function () {
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                alert("socket未连接");
            }
            socket.close(1000, "从客户端关闭");
        };

        sendButton.onclick = function () {  // 发送
            if (!socket || socket.readyState !== WebSocket.OPEN) {  // 检测socket是否已连接
                alert("socket未连接");
            }
            var data = sendMessage.value;
            socket.send(data);  // 发送
            commsLog.innerHTML += '<tr>' +
                '<td class="commslog-client">客户端</td>' +
                '<td class="commslog-server">服务器</td>' +
                '<td class="commslog-data">' + htmlEscape(data) + '</td></tr>';
        };

        connectButton.onclick = function() {  // 连接
            stateLabel.innerHTML = "连接中...";
            socket = new WebSocket(connectionUrl.value);  // 创建WebSocket对象
            socket.onopen = function (event) {  // WebSocket打开
                socket.send("有新的伙伴加入了聊天室");  // 发送

                updateState();
                commsLog.innerHTML += '<tr>' +
                    '<td colspan="3" class="commslog-data">连接已打开!</td>' +
                '</tr>';
            };
            socket.onclose = function (event) {  // WebSocket关闭
                updateState();
                commsLog.innerHTML += '<tr>' +
                    '<td colspan="3" class="commslog-data">连接已关闭!Code: ' + htmlEscape(event.code) + '. Message: ' + htmlEscape(event.reason) + '</td>' +
                '</tr>';
            };
            socket.onerror = updateState;  // 报错
            socket.onmessage = function (event) {  // 接收消息,消息内容为event.data
                commsLog.innerHTML += '<tr>' +
                    '<td class="commslog-server">服务器</td>' +
                    '<td class="commslog-client">客户端</td>' +
                    '<td class="commslog-data">' + htmlEscape(event.data) + '</td></tr>';
            };
        };

        function htmlEscape(str) {
            return str.toString()
                .replace(/&/g, '&')
                .replace(/"/g, '"')
                .replace(/'/g, ''')
                .replace(/</g, '<')
                .replace(/>/g, '>');
        }
    </script>
</body>
</html>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.

作者:꧁执笔小白꧂