目录
0 任务概述
1 WebSocket & mqtt是什么
2 代码实现
1. WebSocket客户端:
2. WebSocket服务端
3 其他参考
如前所述,WebSocket通讯实现C++/C#_Suxiang997的博客-CSDN博客
目标:假定有一条来自mqtt的信息,将这条信息转发至WebSocket服务器端。
最终效果:A主机是WebSocket客户端,兼具mqtt发布者/订阅者的功能,作为发布者自定义消息发送至mqtt云端服务器B,再由云端回响给订阅者A,最后转发至另一台电脑C的WebSocket服务端。
WebSocket服务端 WebSocket&mqtt客户端1> MQTT
MQTT协议,终于有人讲清楚了_小麦大叔的博客-CSDN博客
基于发布/订阅
(publish/subscribe
)模式的“轻量级”通讯协议,该协议构建于TCP/IP协议上
MQTT最大优点在于,用极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。
低开销、低带宽占用的即时通讯协议
2> WebSocket
WebSocket_ohana!的博客-CSDN博客
WebSocket详解:技术原理、代码演示和应用案例 - 知乎 (zhihu.com)
ubuntu C# 和windows C# socket TCP通信(附用emqx开启mqtt服务器)_ubuntu与windows通信_Suxiang997的博客-CSDN博客
参考:C# Socket网络编程入门(服务器与客户端通信,客户端与客户端通信)_c#客户端和客户端通信_黄瓜炒鸡蛋儿的博客-CSDN博客
Program.cs:主程序,入口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WS_C_2_WindowsFormsApp1
{
static class Program
{
///
/// 应用程序的主入口点。
///
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new ClientForm());
}
}
}
ClientForm.Designer.cs:Form文件设计
namespace WS_C_2_WindowsFormsApp1
{
partial class ClientForm
{
///
/// 必需的设计器变量。
///
private System.ComponentModel.IContainer components = null;
///
/// 清理所有正在使用的资源。
///
/// 如果应释放托管资源,为 true;否则为 false。
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows 窗体设计器生成的代码
///
/// 设计器支持所需的方法 - 不要修改
/// 使用代码编辑器修改此方法的内容。
///
private void InitializeComponent()
{
this.btn_Link = new System.Windows.Forms.Button();
this.lab_Port = new System.Windows.Forms.TextBox();
this.lab_Ip = new System.Windows.Forms.TextBox();
this.Port = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.txt_Cont = new System.Windows.Forms.TextBox();
this.txt_Msg = new System.Windows.Forms.TextBox();
this.btn_Send = new System.Windows.Forms.Button();
this.label2 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// btn_Link
//
this.btn_Link.Location = new System.Drawing.Point(552, 59);
this.btn_Link.Name = "btn_Link";
this.btn_Link.Size = new System.Drawing.Size(176, 23);
this.btn_Link.TabIndex = 0;
this.btn_Link.Text = "Link";
this.btn_Link.UseVisualStyleBackColor = true;
this.btn_Link.Click += new System.EventHandler(this.btn_Link_Click);
//
// lab_Port
//
this.lab_Port.Location = new System.Drawing.Point(359, 59);
this.lab_Port.Name = "lab_Port";
this.lab_Port.Size = new System.Drawing.Size(100, 21);
this.lab_Port.TabIndex = 1;
this.lab_Port.Text = "2000";
//
// lab_Ip
//
this.lab_Ip.Location = new System.Drawing.Point(104, 59);
this.lab_Ip.Name = "lab_Ip";
this.lab_Ip.Size = new System.Drawing.Size(100, 21);
this.lab_Ip.TabIndex = 2;
this.lab_Ip.Text = "192.168.0.111";
this.lab_Ip.TextChanged += new System.EventHandler(this.textBox2_TextChanged);
//
// Port
//
this.Port.AutoSize = true;
this.Port.Location = new System.Drawing.Point(312, 64);
this.Port.Name = "Port";
this.Port.Size = new System.Drawing.Size(41, 12);
this.Port.TabIndex = 3;
this.Port.Text = "Port:";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(69, 62);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(29, 12);
this.label1.TabIndex = 4;
this.label1.Text = "IP:";
//
// txt_Cont
//
this.txt_Cont.Location = new System.Drawing.Point(71, 107);
this.txt_Cont.Multiline = true;
this.txt_Cont.Name = "txt_Cont";
this.txt_Cont.Size = new System.Drawing.Size(657, 243);
this.txt_Cont.TabIndex = 5;
this.txt_Cont.TextChanged += new System.EventHandler(this.textBox1_TextChanged);
//
// txt_Msg
//
this.txt_Msg.Location = new System.Drawing.Point(71, 358);
this.txt_Msg.Name = "txt_Msg";
this.txt_Msg.Size = new System.Drawing.Size(552, 21);
this.txt_Msg.TabIndex = 6;
//
// btn_Send
//
this.btn_Send.Location = new System.Drawing.Point(629, 356);
this.btn_Send.Name = "btn_Send";
this.btn_Send.Size = new System.Drawing.Size(99, 23);
this.btn_Send.TabIndex = 7;
this.btn_Send.Text = "Send";
this.btn_Send.UseVisualStyleBackColor = true;
this.btn_Send.Click += new System.EventHandler(this.btn_Send_Click);
//
// label2
//
this.label2.AutoSize = true;
this.label2.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.label2.Location = new System.Drawing.Point(68, 88);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(48, 16);
this.label2.TabIndex = 8;
this.label2.Text = "Panel";
this.label2.Click += new System.EventHandler(this.label2_Click);
//
// ClientForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.label2);
this.Controls.Add(this.btn_Send);
this.Controls.Add(this.txt_Msg);
this.Controls.Add(this.txt_Cont);
this.Controls.Add(this.label1);
this.Controls.Add(this.Port);
this.Controls.Add(this.lab_Ip);
this.Controls.Add(this.lab_Port);
this.Controls.Add(this.btn_Link);
this.Name = "ClientForm";
this.Text = "Client";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button btn_Link;
private System.Windows.Forms.TextBox lab_Port;
private System.Windows.Forms.TextBox lab_Ip;
private System.Windows.Forms.Label Port;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox txt_Cont;
private System.Windows.Forms.TextBox txt_Msg;
private System.Windows.Forms.Button btn_Send;
private System.Windows.Forms.Label label2;
}
}
ClientForm.cs:客户端实现(集成mqtt Publish&Subscriber的功能)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
namespace WS_C_2_WindowsFormsApp1
{
public partial class ClientForm : Form
{
public ClientForm()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//csharpMQTT.CS_mqtt mqttClient = new csharpMQTT.CS_mqtt();
}
Socket socketSend;
//点击连接
private void btn_Link_Click(object sender, EventArgs e)
{
try
{
_ = CS_mqtt_Main();
//创建负责通信的Socket
socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse(lab_Ip.Text);
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(lab_Port.Text));
//IPAddress ip = IPAddress.Parse("192.168.0.105");
//IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32("2000"));
socketSend.Connect(point);
ShowMsg("连接成功,目标服务器IP地址为:" + ip.ToString());
//开启一个线程不停接受服务器发送来的信息
Thread th = new Thread(Recive)
{
IsBackground = true
};
th.Start();
}
catch (Exception ex)
{
//处理异常
ShowMsg("连接失败:" + ex.Message);
}
}
//不停接受服务器发来的消息
void Recive()
{
while (true)
{
byte[] buffer = new byte[1024 * 1024 * 3];
//实际接受到的有效字节
int r = socketSend.Receive(buffer);
if (r <= 0)
{
break;
}
string s = Encoding.UTF8.GetString(buffer, 0, r);
ShowMsg(socketSend.RemoteEndPoint + ":" + s);
}
}
void ShowMsg(string str)
{
//txt_Cont.AppendText(str + "\r\n");
if (txt_Cont.InvokeRequired)
{
txt_Cont.Invoke(new Action(ShowMsg), str);
}
else
{
txt_Cont.AppendText(str + "\r\n");
}
}
//点击发送按钮,客户端给服务器发送消息
private void btn_Send_Click(object sender, EventArgs e)
{
string str = txt_Msg.Text.Trim();
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);
socketSend.Send(buffer);
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
}
private void label2_Click(object sender, EventArgs e)
{
}
private void Port_Click(object sender, EventArgs e)
{
}
//mqtt部分
MqttClient ConnectMQTT(string broker, int port, string clientId, string username, string password)
{
MqttClient client = new MqttClient(broker, port, false, MqttSslProtocols.None, null, null);
client.Connect(clientId, username, password);
if (client.IsConnected)
{
ShowMsg("Connected to MQTT Broker");
}
else
{
ShowMsg("Failed to connect");
}
return client;
}
void Publish(MqttClient client, string topic)
{
int msg_count = 0;
while (true)
{
System.Threading.Thread.Sleep(1 * 1000);
string msg = "messages: " + msg_count.ToString();
client.Publish(topic, System.Text.Encoding.UTF8.GetBytes(msg));
ShowMsg("Send '" + msg + "' to topic '" + topic + "'");
//Console.WriteLine("Send `{0}` to topic `{1}`", msg, topic);
msg_count++;
}
}
void Subscribe(MqttClient client, string topic)
{
client.MqttMsgPublishReceived += client_MqttMsgPublishReceived;
client.Subscribe(new string[] { topic }, new byte[] { MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE });
}
void client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
{
string payload = Encoding.Default.GetString(e.Message);
ShowMsg("Received '" + payload + "' from '" + e.Topic.ToString() + "' topic");
//Console.WriteLine("Received `{0}` from `{1}` topic", payload, e.Topic.ToString());
//将从mqtt服务端获取到的数据再转发给ws服务端
Transmit(Encoding.Default.GetString(e.Message));
}
///
/// 将从mqtt服务端获取到的数据再转发给ws服务端
///
void Transmit(string msg)
{
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(msg);
socketSend.Send(buffer);
}
///
/// Mqtt服务器连接的入口,完成client对象创建等功能
///
//static void Main(string[] args)
async Task CS_mqtt_Main()
{
string broker = "broker.emqx.io";//服务器地址,默认为云端服务器
//string broker = "115.156.168.35"; //本地服务器ip
int port = 1883;
string topic = "Csharp/mqtt";
string clientId = Guid.NewGuid().ToString();
string username = "emqx";
string password = "public";
MqttClient client = ConnectMQTT(broker, port, clientId, username, password);
Subscribe(client, topic);
//异步方法,它不会阻塞当前线程。相反,它会返回一个Task对象,表示指定的时间过去后,操作将完成。在等待这个Task对象完成的过程中,线程可以执行其他操作。这使得程序更加响应,因为用户可以与程序进行交互,而不必等待Task.Delay返回。
var cancellationTokenSource = new CancellationTokenSource();
var publishTask = Task.Run(() => Publish(client, topic), cancellationTokenSource.Token);
await publishTask;
//Publish(client, topic);
}
}
}
Program.cs:主程序,入口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using WS_S_2_WindowsFormsApp1;
namespace WS_C_2_WindowsFormsApp1
{
static class Program
{
///
/// 应用程序的主入口点。
///
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new ServerForm());
}
}
}
Server.cs :功能实现区
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WS_S_2_WindowsFormsApp1
{
public partial class ServerForm : Form
{
List ClientProxSocketList = new List();
public ServerForm()
{
InitializeComponent();
}
Socket proxSocket;
//点击启动按钮
private void btn_Start_Click(object sender, EventArgs e)
{
//1 创建Socket
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2 绑定端口Ip
socket.Bind(new IPEndPoint(IPAddress.Parse(txt_Ip.Text), int.Parse(txt_Port.Text)));
//3 开启监听
socket.Listen(1000); //连接:最大接收请求数,超出返回错误信息
//4 开始接收客户端的连接
ThreadPool.QueueUserWorkItem(new WaitCallback(this.AcceptClientConnect), socket);
}
//将远程连接的客户端的IP地址和Socket存入集合中
Dictionary dicSocket = new Dictionary();
public void AcceptClientConnect(object socket)
{
var serverSocket = socket as Socket;
this.AppendTextToTxtLog("服务器开始接受客户端的连接。");
while (true)
{
//负责跟客户通信的Socket
proxSocket = serverSocket.Accept();
//将远程连接的客户端的IP地址和Socket存入集合中
dicSocket.Add(proxSocket.RemoteEndPoint.ToString(), proxSocket);
//将远程连接的客户端IP地址和端口存储下拉框中
//cboUsers.Items.Add(proxSocket.RemoteEndPoint.ToString());
this.AppendTextToTxtLog(string.Format("客户端:{0}连接上了", proxSocket.RemoteEndPoint.ToString()));
ClientProxSocketList.Add(proxSocket);
//不停的接收当前连接的客户端发送的信息
// proxSocket.Receive();
ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveData), proxSocket);
}
}
//接收客户端的消息
public void ReceiveData(object socket)
{
var proxSocket = socket as Socket;
byte[] data = new byte[1024 * 1024 * 2];
while (true)
{
int len = 0;
try
{
len = proxSocket.Receive(data, 0, data.Length, SocketFlags.None);
}
catch (Exception)
{
//异常退出
AppendTextToTxtLog(string.Format("客户端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()));
ClientProxSocketList.Remove(proxSocket);
return;
}
if (len <= 0)
{
//客户端正常退出
AppendTextToTxtLog(string.Format("客户端:{0}正常退出", proxSocket.RemoteEndPoint.ToString()));
ClientProxSocketList.Remove(proxSocket);
return;//让方法结束,终结当前接受客户端数据的异步线程
}
//把接收到的数据放到文本框中
string str = Encoding.UTF8.GetString(data, 0, len);
AppendTextToTxtLog(string.Format("接收到客户端:{0}的消息是:{1}", proxSocket.RemoteEndPoint.ToString(), str));
//服务器转发信息,除发送方以外,转发信息
//先不需要此功能,客户端A第一次进入时编号1,退出再进编号2;但是1 2 都会被存入队列,服务端转发信息给所有客户端时,会找不到1
//byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);
//foreach (var item in dicSocket)
//{
// if (item.Key != proxSocket.RemoteEndPoint.ToString())
// item.Value.Send(buffer);
//}
//服务器给发送者回响消息,没写好
//byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);
//foreach (var item in dicSocket)
//{
// if (item.Key != proxSocket.RemoteEndPoint.ToString())
// item.Value.Send(buffer);
//}
}
}
//往日志的文本框上追加数据
public void AppendTextToTxtLog(string txt)
{
if (txt_Cont.InvokeRequired)
{
txt_Cont.Invoke(new Action(s =>
{
this.txt_Cont.Text = string.Format("{0}\r\n{1}", s, txt_Cont.Text);
}), txt);
}
else
{
this.txt_Cont.Text = string.Format("{0}\r\n{1}", txt, txt_Cont.Text);
}
}
//服务器给客户端发送信息
private void btn_Send_Click(object sender, EventArgs e)
{
#region
//foreach (var proxSocket in ClientProxSocketList)
//{
// if (proxSocket.Connected)
// {
// byte[] data = Encoding.Default.GetBytes(txt_Msg.Text);
// proxSocket.Send(data, 0, data.Length, SocketFlags.None);
// }
//}
#endregion
string str = txt_Msg.Text;
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);
//获取用户在下拉框中选择的IP地址
//string ip = cboUsers.SelectedItem.ToString();
foreach (var item in dicSocket)
{
//if(item.Key != proxSocket.RemoteEndPoint.ToString())
item.Value.Send(buffer);
}
//proxSocket.Send(buffer);
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
}
private void label3_Click(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
}
Server.Designer.cs
namespace WS_S_2_WindowsFormsApp1
{
partial class ServerForm
{
///
/// 必需的设计器变量。
///
private System.ComponentModel.IContainer components = null;
///
/// 清理所有正在使用的资源。
///
/// 如果应释放托管资源,为 true;否则为 false。
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows 窗体设计器生成的代码
///
/// 设计器支持所需的方法 - 不要修改
/// 使用代码编辑器修改此方法的内容。
///
private void InitializeComponent()
{
this.btn_Start = new System.Windows.Forms.Button();
this.btn_Send = new System.Windows.Forms.Button();
this.txt_Ip = new System.Windows.Forms.TextBox();
this.txt_Port = new System.Windows.Forms.TextBox();
this.txt_Cont = new System.Windows.Forms.TextBox();
this.txt_Msg = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// btn_Start
//
this.btn_Start.Location = new System.Drawing.Point(539, 39);
this.btn_Start.Name = "btn_Start";
this.btn_Start.Size = new System.Drawing.Size(176, 23);
this.btn_Start.TabIndex = 0;
this.btn_Start.Text = "Start";
this.btn_Start.UseVisualStyleBackColor = true;
this.btn_Start.Click += new System.EventHandler(this.btn_Start_Click);
//
// btn_Send
//
this.btn_Send.Location = new System.Drawing.Point(640, 361);
this.btn_Send.Name = "btn_Send";
this.btn_Send.Size = new System.Drawing.Size(75, 23);
this.btn_Send.TabIndex = 1;
this.btn_Send.Text = "Send";
this.btn_Send.UseVisualStyleBackColor = true;
this.btn_Send.Click += new System.EventHandler(this.btn_Send_Click);
//
// txt_Ip
//
this.txt_Ip.Location = new System.Drawing.Point(120, 39);
this.txt_Ip.Name = "txt_Ip";
this.txt_Ip.Size = new System.Drawing.Size(100, 21);
this.txt_Ip.TabIndex = 2;
this.txt_Ip.Text = "192.168.0.105";
//
// txt_Port
//
this.txt_Port.ForeColor = System.Drawing.SystemColors.WindowText;
this.txt_Port.Location = new System.Drawing.Point(377, 39);
this.txt_Port.Name = "txt_Port";
this.txt_Port.Size = new System.Drawing.Size(100, 21);
this.txt_Port.TabIndex = 3;
this.txt_Port.Text = "2000";
//
// txt_Cont
//
this.txt_Cont.Location = new System.Drawing.Point(84, 88);
this.txt_Cont.Multiline = true;
this.txt_Cont.Name = "txt_Cont";
this.txt_Cont.Size = new System.Drawing.Size(631, 260);
this.txt_Cont.TabIndex = 4;
//
// txt_Msg
//
this.txt_Msg.Location = new System.Drawing.Point(84, 363);
this.txt_Msg.Name = "txt_Msg";
this.txt_Msg.Size = new System.Drawing.Size(533, 21);
this.txt_Msg.TabIndex = 5;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(86, 43);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(29, 12);
this.label1.TabIndex = 6;
this.label1.Text = "IP:";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(330, 44);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(41, 12);
this.label2.TabIndex = 7;
this.label2.Text = "Port:";
//
// ServerForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Controls.Add(this.txt_Msg);
this.Controls.Add(this.txt_Cont);
this.Controls.Add(this.txt_Port);
this.Controls.Add(this.txt_Ip);
this.Controls.Add(this.btn_Send);
this.Controls.Add(this.btn_Start);
this.Name = "ServerForm";
this.Text = "Server";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button btn_Start;
private System.Windows.Forms.Button btn_Send;
private System.Windows.Forms.TextBox txt_Ip;
private System.Windows.Forms.TextBox txt_Port;
private System.Windows.Forms.TextBox txt_Cont;
private System.Windows.Forms.TextBox txt_Msg;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
}
}
如有遗漏,参见以前的遗弃版本:
WebSocket通讯实现C++/C#_Suxiang997的博客-CSDN博客
ubuntu C# 和windows C# socket TCP通信(附用emqx开启mqtt服务器)_ubuntu与windows通信_Suxiang997的博客-CSDN博客