有bug,不推荐使用

有bug,不推荐使用

有bug,不推荐使用

有bug,不推荐使用

有bug,不推荐使用

有bug,不推荐使用

有bug,不推荐使用

有bug,不推荐使用

有bug,不推荐使用

有bug,不推荐使用

有bug,不推荐使用

有bug,不推荐使用

有bug,不推荐使用

一、简介

WebSocket 是一种在单个TCP连接上进行全双工通信的协议。WebSocket 通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

Websocket 用法和 TCP 协议差不多,在这里我用 C# 写服务器端, html 和 winform 作为客户端。

二、服务端

1.新建项目

新建一个控制台项目

在 NuGet 引入  Fleck 插件

2.WebSocketHelper

新建 WebSocketHelper.cs

代码

using Fleck;

namespace WebSocket
{
    internal class WebSocketHelper
    {
        //客户端url以及其对应的Socket对象字典
        IDictionary<string, IWebSocketConnection> dic_Sockets = new Dictionary<string, IWebSocketConnection>();
        //创建一个 websocket ,0.0.0.0 为监听所有的的地址
        WebSocketServer server = new WebSocketServer("ws://0.0.0.0:30000");

        //打开连接委托
        public delegate void _OnOpen(string ip);
        public event _OnOpen OnOpen;
        //关闭连接委托
        public delegate void _OnClose(string ip);
        public event _OnClose OnClose;
        //当收到消息
        public delegate void _OnMessage(string ip, string msg);
        public event _OnMessage OnMessage;

        /// <summary>
        /// 初始化
        /// </summary>
        private void Init()
        {
            //出错后进行重启
            server.RestartAfterListenError = true;

            //开始监听
            server.Start(socket =>
            {
                //连接建立事件
                socket.OnOpen = () =>
                {
                    //获取客户端网页的url
                    string clientUrl = socket.ConnectionInfo.ClientIpAddress + ":" + socket.ConnectionInfo.ClientPort;
                    dic_Sockets.Add(clientUrl, socket);
                    if (OnOpen != null) OnOpen(clientUrl);
                    Console.WriteLine(DateTime.Now.ToString() + " | 服务器:和客户端:" + clientUrl + " 建立WebSock连接!");
                };

                //连接关闭事件
                socket.OnClose = () =>
                {
                    string clientUrl = socket.ConnectionInfo.ClientIpAddress + ":" + socket.ConnectionInfo.ClientPort;
                    //如果存在这个客户端,那么对这个socket进行移除
                    if (dic_Sockets.ContainsKey(clientUrl))
                    {
                        dic_Sockets.Remove(clientUrl);
                        if (OnClose != null) OnClose(clientUrl);
                    }
                    Console.WriteLine(DateTime.Now.ToString() + " | 服务器:和客户端:" + clientUrl + " 断开WebSock连接!");
                };

                //接受客户端网页消息事件
                socket.OnMessage = message =>
                {
                    string clientUrl = socket.ConnectionInfo.ClientIpAddress + ":" + socket.ConnectionInfo.ClientPort;
                    Receive(clientUrl, message);
                    if (OnMessage != null) 
                        OnMessage(clientUrl, message);
                };
            });
        }

        /// <summary>
        /// 向客户端发送消息
        /// </summary>
        /// <param name="webSocketConnection">客户端实例</param>
        /// <param name="message">消息内容</param>
        public void Send(string clientUrl, string message)
        {
            IWebSocketConnection webSocketConnection = GetUserSocketInstance(clientUrl);
            if (webSocketConnection != null)
            {
                if (webSocketConnection.IsAvailable)
                {
                    webSocketConnection.Send(message);
                }
            }
        }

        /// <summary>
        /// 接收消息
        /// </summary>
        /// <param name="clientUrl"></param>
        /// <param name="message"></param>
        private void Receive(string clientUrl, string message)
        {
            Console.WriteLine(DateTime.Now.ToString() + " | 服务器:【收到】来客户端:" + clientUrl + "的信息:\n" + message);
        }

        /// <summary>
        /// 获取用户实例
        /// </summary>
        /// <param name="clientUrl">用户的地址</param>
        public IWebSocketConnection GetUserSocketInstance(string clientUrl)
        {
            if (dic_Sockets.ContainsKey(clientUrl))
                return dic_Sockets[clientUrl];
            else
                return null;
        }

        /// <summary>
        /// 关闭某一个用户的连接
        /// </summary>
        /// <param name="clientUrl"></param>
        public void CloseUserConnect(string clientUrl)
        {
            IWebSocketConnection webSocketConnection = GetUserSocketInstance(clientUrl);
            if (webSocketConnection != null)
                webSocketConnection.Close();
        }

        /// <summary>
        /// 关闭与客户端的所有的连接
        /// </summary>
        public void CloseAllConnect()
        {
            foreach (var item in dic_Sockets.Values)
            {
                if (item != null)
                {
                    item.Close();
                }
            }
        }

        public WebSocketHelper()
        {
            Init();
        }
    }
}

3.Program

目前并没有写入太多功能,只是加了控制台输入文字,然后转发给所有连接的用户

代码

using WebSocket;

namespace WebSocketNet6
{
    internal class Program
    {
        private static List<string> IPList = new List<string>();
        private static WebSocketHelper WebSocketHelpers = new WebSocketHelper();

        static void Main(string[] args)
        {
            WebSocketHelpers.OnOpen += WebSocketHelper_OnOpen;
            WebSocketHelpers.OnClose += WebSocketHelper_OnClose;
            WebSocketHelpers.OnMessage += WebSocketHelper_OnMessage;

            while (true)
            {
                //监听控制台的输入
                string? contetn = Console.ReadLine();
                if (contetn != null)
                {
                    Relay(contetn);

                    if (contetn.Equals("q"))
                    {
                        WebSocketHelpers.CloseAllConnect();
                        Console.WriteLine("关闭所有客户端的连接");
                        break;
                    }
                }
                Thread.Sleep(10);
            }
        }

        #region WebSocket回调

        //当收到消息
        private static void WebSocketHelper_OnMessage(string ip, string msg)
        {
            for (int i = 0; i < IPList.Count; i++)
            {
                if (IPList[i] != ip)
                {
                    WebSocketHelpers.Send(IPList[i], msg);
                }
            }
        }

        //当客户端断开连接
        private static void WebSocketHelper_OnClose(string ip)
        {
            if (IPList.Contains(ip))
            {
                IPList.Remove(ip);
            }
        }

        //当客户端连接上服务器
        private static void WebSocketHelper_OnOpen(string ip)
        {
            if (!IPList.Contains(ip))
            {
                IPList.Add(ip);
            }
        }

        #endregion

        //转发所有客户端
        private static void Relay(string content)
        {
            if (IPList.Count == 0)  return;

            for (int i = 0; i < IPList.Count; i++)
            {
                WebSocketHelpers.Send(IPList[i], content);
            }
        }
    }
}

三、客户端

1.html


<!DOCTYPE  HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
    <title>websocket client</title>
    <script type="text/javascript">
        var start = function () {
            var inc = document.getElementById('incomming');
            var wsImpl = window.WebSocket || window.MozWebSocket;
            var form = document.getElementById('sendForm');
            var input = document.getElementById('sendText');
 
            inc.innerHTML += "连接服务器..<br/>";
 
            // 创建一个新的websocket并连接
            window.ws = new wsImpl('ws://localhost:30000/');
 
            // 当数据来自服务器时,将调用此方法
            ws.onmessage = function (evt) {
                inc.innerHTML += ("[来自服务器的消息] " + evt.data + '<br/>');
                console.log("[来自服务器的消息] " + evt.data);
            };
 
            // 当建立连接时,将调用此方法
            ws.onopen = function () {
                inc.innerHTML += '已建立连接.. <br/>';
            };
 
            // 当连接关闭时,将调用此方法
            ws.onclose = function () {
                inc.innerHTML += '连接已关闭.. <br/>';
            }
 
            form.addEventListener('submit', function (e) {
                e.preventDefault();
                var val = input.value;
                ws.send(val);
                input.value = "";
            });
        }
        window.onload = start;
    </script>
</head>
<body>
    <form id="sendForm">
        <span>输入内容按回车发送消息</span> <br/>
        <input id="sendText" placeholder="Text to send" />
    </form>
    <pre id="incomming"></pre>
</body>
</html>

在本地新建一个 文本文档,将名字改为 index.html ,然后将上面的代码复制进去。

先打开上面的 Webscoket 服务器,用浏览器打开 index.html,

然后服务器端就会收到对于的消息了

服务器发送 你好

同样的,网页端也收到了来自服务器的消息

那么这样,就完成了网页端的通信了

2.Winform

新建一个 winform 项目

先安装插件 SuperSocket.ClientEngine

再安装插件 WebSocket4Net

新建脚本 WSocketClient.cs

using SuperSocket.ClientEngine;
using System;
using System.Threading;
using System.Threading.Tasks;
using WebSocket4Net;

namespace WebSocketClient
{
    public class WSocketClient : IDisposable
    {
        //收到消息后的回调
        //public event Action<string> MessageReceived;
        public Action<string> MessageReceived;

        private WebSocket4Net.WebSocket _webSocket;

        /// <summary>
        /// 检查重连线程
        /// </summary>
        Thread _thread;
        bool _isRunning = false;

        /// <summary>
        /// 是否在运行中
        /// </summary>
        public bool IsRunning => _isRunning;

        /// <summary>
        /// WebSocket连接地址
        /// </summary>
        public string ServerPath { get; set; }

        public WSocketClient(string url)
        {
            ServerPath = url;
            this._webSocket = new WebSocket4Net.WebSocket(url);
            this._webSocket.Opened += WebSocket_Opened;
            this._webSocket.Error += WebSocket_Error;
            this._webSocket.Closed += WebSocket_Closed;
            this._webSocket.MessageReceived += WebSocket_MessageReceived;
        }

        /// <summary>
        /// 连接方法
        /// <returns></returns>
        public bool Start()
        {
            bool result = true;
            try
            {
                this._webSocket.Open();
                this._isRunning = true;
                this._thread = new Thread(new ThreadStart(CheckConnection));
                this._thread.Start();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                result = false;
                this._isRunning = false;
            }
            return result;
        }

        /// <summary>
        /// 消息收到事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void WebSocket_MessageReceived(object sender, MessageReceivedEventArgs e)
        {
            Console.WriteLine("Received:" + e.Message);

            if (MessageReceived != null)
                MessageReceived(e.Message);
        }

        /// <summary>
        /// Socket关闭事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void WebSocket_Closed(object sender, EventArgs e)
        {
            Console.WriteLine("websocket_Closed");
        }

        /// <summary>
        /// Socket报错事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void WebSocket_Error(object sender, ErrorEventArgs e)
        {
            Console.WriteLine("websocket_Error:" + e.Exception.ToString());
        }

        /// <summary>
        /// Socket打开事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void WebSocket_Opened(object sender, EventArgs e)
        {
            Console.WriteLine("websocket_Opened");
        }

        /// <summary>
        /// 检查重连线程
        /// </summary>
        private void CheckConnection()
        {
            do
            {
                try
                {
                    if (this._webSocket.State != WebSocket4Net.WebSocketState.Open && this._webSocket.State != WebSocket4Net.WebSocketState.Connecting)
                    {
                        Console.WriteLine("Reconnect websocket WebSocketState:" + this._webSocket.State);

                        this._webSocket.Close();
                        this._webSocket.Open();

                        Console.WriteLine("正在重连");
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
                System.Threading.Thread.Sleep(5000);
            } while (this._isRunning);
        }

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="Message"></param>
        public void SendMessage(string Message)
        {
            Task.Factory.StartNew(() =>
            {
                if (_webSocket != null && _webSocket.State == WebSocket4Net.WebSocketState.Open)
                {
                    this._webSocket.Send(Message);
                }
            });
        }

        public void Dispose()
        {
            try
            {
                _thread?.Abort();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            this._webSocket.Close();
            this._webSocket.Dispose();
            this._webSocket = null;
            this._isRunning = false;
        }
    }
}

winfrom 界面如下

Form1.cs

using System;
using System.Windows.Forms;
using WebSocketClient;

namespace WinFormWebsocket
{
    public partial class Form1 : Form
    {
        private static string url = "ws://127.0.0.1:30000";
        private WSocketClient client = new WSocketClient(url);

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Control.CheckForIllegalCrossThreadCalls = false;
            txtServerIP.Text = url;
            client.MessageReceived = MessageReceived;
            this.Text = "客户端";
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if(client.IsRunning)
                client.Dispose();
        }

        /// <summary>
        /// 连接服务器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                if(client.IsRunning)
                {
                    AddOrdinaryLog("已经连接服务器,不能重复执行");
                    return;
                }

                bool result = client.Start();
                AddOrdinaryLog("连接是否成功:" + result);
            }
            catch (Exception ex)
            {
                string err = string.Format("连接失败:{0}", ex.Message);
                Console.WriteLine(err);
                throw;
            }
        }

        /// <summary>
        /// 关闭服务器
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnClose_Click(object sender, EventArgs e)
        {
            if (!client.IsRunning)
            {
                AddOrdinaryLog("服务器未连接");
                return;
            }

            // 记得释放资源否则会造成堆栈
            client.Dispose();
            AddOrdinaryLog("连接已关闭");
        }

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSendMsg_Click(object sender, EventArgs e)
        {
            string inputMsg = txtInputMsg.Text;
            if (string.IsNullOrEmpty(inputMsg))
            {
                MessageBox.Show("输入框不能为空");
                return;
            }

            client.SendMessage(inputMsg);
            AddOrdinaryLog(inputMsg);
            txtInputMsg.Text = string.Empty;
        }

        /// <summary>
        /// 服务端返回的消息
        /// </summary>
        private void MessageReceived(string msg)
        {
            AddOrdinaryLog(msg);
        }

        /// <summary>
        /// 添加日志
        /// </summary>
        /// <param name="content"></param>
        private void AddOrdinaryLog(string content)
        {
            //读取当前ListBox列表长度
            int len = ListBox_OrdinaryLogList.Items.Count;
            //插入新的一行
            ListBox_OrdinaryLogList.Items.Insert(len, content);
            //列表长度大于30,那么就删除第1行的数据
            if (len > 30)
                ListBox_OrdinaryLogList.Items.RemoveAt(0);
            //插入新的数据后,将滚动条移动到最下面
            int visibleItems = ListBox_OrdinaryLogList.ClientSize.Height / ListBox_OrdinaryLogList.ItemHeight;
            ListBox_OrdinaryLogList.TopIndex = Math.Max(ListBox_OrdinaryLogList.Items.Count - visibleItems + 1, 0);
        }
    }
}

到这里,所有的准备工作都做完了,下面就来测试一下效果吧。

首先,运行 WebSocket 服务器端,再运行客户端,点击连接按钮

服务器端也显示有客户端连接上了。

那么现在用客户端发送消息,输入恭喜发财,然后点击 发送消息 按钮,这时历史消息列表就有对于的记录。

这时,服务器端也同样的收到了来自客户端的消息。

接下来,我们用服务器向客户端发送消息,在控制台输入文字后,按回车,会自动发送

客户端收到了来自服务器的消息

 

 这样,就完成了通讯部分的基本功能了。

 源码:点击下载

结束

如果这个帖子对你有用,欢迎 关注 + 点赞 + 留言,谢谢

end

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐