首页 > Python基础教程 >
-
C#教程之C# Socket异步实现消息发送--附带源码
前言
看了一百遍,不如动手写一遍。
Socket这块使用不是特别熟悉,之前实现是公司有对应源码改改能用。
但是不理解实现的过程和步骤,然后最近有时间自己写个demo实现看看,熟悉熟悉Socket。
网上也有好的文章,结合别人的理接和自己实践总算写完了。。。
参考: https://www.cnblogs.com/sunev/ 实现
参考:https://blog.csdn.net/woshiyuanlei/article/details/47684221
https://www.cnblogs.com/dotnet261010/p/6211900.html
理解握手过程,关闭时的握手过程(关闭的过程弄了半天,总算弄懂了意思)。
实现过程
总体包含:开启,关闭,断线重连(客户端),内容发送。
说明:服务端和客户端代码基本一致,客户端需要实时监听服务端状态,断开时需要重连。
页面效果图:
服务端实现(实现不包含UI部分 最后面放所有代码和下载地址)
给出一个指定的地址IP+Port,套接字类型初始化Socket
使用Bind方法进行关联,Listen(10) 注释: 挂起连接队列的最大长度。我的通俗理接:你有一个女朋友这时你不能找别的,但是如果分手了就可以找别的,
BeginAccept包含一个异步的回调,获取请求的Socket
var s_socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp); s_socket.Bind(new IPEndPoint(IPAddress.Parse(ip), port)); s_socket.Listen(10);//同时连接的最大连接数 s_socket.BeginAccept(new AsyncCallback(Accept), s_socket);//获取连接
ClientState,自己声明的一个类用于接收数据(对应ReadCallback)和内部处理
Accept异步请求回调,当有Socket请求时会进去该回调,
EndAccept,我的理接为给当前Socket创建一个新的链接释放掉 Listen
BeginReceive,包含一个消息回调(ReadCallback),只需给出指定长度数组接收Socket上传的消息数据
BeginAccept,再次执行等待方法等待后续Socket连接
/// <summary> /// 异步连接回调 获取请求Socket /// </summary> /// <param name="ar">请求的Socket</param> private void Accept(IAsyncResult ar) { try { //获取连接Socket 创建新的连接 Socket myServer = ar.AsyncState as Socket; Socket service = myServer.EndAccept(ar); ClientState obj = new ClientState(); obj.clientSocket = service; //接收连接Socket数据 service.BeginReceive(obj.buffer, 0, ClientState.bufsize, SocketFlags.None, new AsyncCallback(ReadCallback), obj); myServer.BeginAccept(new AsyncCallback(Accept), myServer);//等待下一个连接 } catch (Exception ex) { Console.WriteLine("服务端关闭"+ex.Message+" "+ex.StackTrace); } }
EndReceive,通俗理接买东西的时候老板已经打包好了,付钱走人。
BeginReceive,当数据处理完成之后,和Socket连接一样需再次执行获取下次数据
/// <summary> /// 数据接收 /// </summary> /// <param name="ar">请求的Socket</param> private void ReadCallback(IAsyncResult ar) { //获取并保存 ClientState obj = ar.AsyncState as ClientState; Socket c_socket = obj.clientSocket; int bytes = c_socket.EndReceive(ar); //接收完成 重新给出buffer接收 obj.buffer = new byte[ClientState.bufsize]; c_socket.BeginReceive(obj.buffer, 0, ClientState.bufsize, 0, new AsyncCallback(ReadCallback), obj); }
Send,消息发送比较简单,将发送的数组转成数组的形式进行发送
/// <summary> /// 发送消息 /// </summary> /// <param name="c_socket">指定客户端socket</param> /// <param name="by">内容数组</param> private void Send(Socket c_socket, byte[] by) { //发送 c_socket.BeginSend(by, 0, by.Length, SocketFlags.None, asyncResult => { try { //完成消息发送 int len = c_socket.EndSend(asyncResult); } catch (Exception ex) { Console.WriteLine("error ex=" + ex.Message + " " + ex.StackTrace); } }, null); }
客户端实现(实现不包含UI部分 最后面放所有代码和下载地址)
相对于服务端差别不大,只是需要在链接之后增加一个监听机制,进行断线重连,我写的这个比较粗糙。
BeginConnect,使用指定的地址ip+port进新一个异步的链接
Connect,链接回调
ConnectionResult,初始值为-2 在消息接收(可检测服务端断开),链接(链接失败)重新赋值然后判断是否为错误码然后重连
/// <summary> /// 连接Socket /// </summary> public void Start() { try { var endPoint = new IPEndPoint(IPAddress.Parse(ip), port); c_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); c_socket.BeginConnect(endPoint, new AsyncCallback(Connect), c_socket);//链接服务端 th_socket = new Thread(MonitorSocker);//监听线程 th_socket.IsBackground = true; th_socket.Start(); } catch (SocketException ex) { Console.WriteLine("error ex=" + ex.Message + " " + ex.StackTrace); } } //监听Socket void MonitorSocker() { while (true) { if (ConnectionResult != 0 && ConnectionResult != -2)//通过错误码判断 { Start(); } Thread.Sleep(1000); } }
Connec链接部分
/// <summary> /// 连接服务端 /// </summary> /// <param name="ar"></param> private void Connect(IAsyncResult ar) { try { ServiceState obj = new ServiceState(); Socket client = ar.AsyncState as Socket; obj.serviceSocket = client; //获取服务端信息 client.EndConnect(ar); //接收连接Socket数据 client.BeginReceive(obj.buffer, 0, ServiceState.bufsize, SocketFlags.None, new AsyncCallback(ReadCallback), obj); catch (SocketException ex) { ConnectionResult = ex.ErrorCode; Console.WriteLine(ex.Message + " " + ex.StackTrace); } }
整体数据发送和实现部分
声明一个指定的类,给出指定的标识,方便数据处理,
在发送文字消息,图片消息,震动时需要对应的判断其实最简便的方法是直接发送一个XML报文过去直接解析,
也可以采用在头部信息里面直接给出传送类型。
数组中给出了一个0-15的信息头
0-3 标识码,确认身份
4-7 总长度,总体长度可用于接收时判断所需长度
8-11 内容长度,判断内容是否接收完成
12-15 补0,1111(震动) 2222(图片数据,图片这块为了接收图片名称所以采用XML报文形式发送)
16开始为内容
/// <summary> /// 0-3 标识码 4-7 总长度 8-11 内容长度 12-15补0 16开始为内容 /// </summary> public class SendSocket { /// <summary> /// 头 标识8888 /// </summary> byte[] Header = new byte[4] { 0x08, 0x08, 0x08, 0x08 }; /// <summary> /// 文本消息 /// </summary> public string Message; /// <summary> /// 是否发送震动 /// </summary> public bool SendShake = false; /// <summary> /// 是否发送图片 /// </summary> public bool SendImg = false; /// <summary> /// 图片名称 /// </summary> public string ImgName; /// <summary> /// 图片数据 /// </summary> public string ImgBase64; /// <summary> /// 组成特定格式的byte数据 /// 12-15 为指定发送内容 1111(震动) 2222(图片数据) /// </summary> /// <returns>特定格式的byte</returns> public byte[] ToArray() { if (SendImg)//是否发送图片 { //组成XML接收 可以接收相关图片数据 StringBuilder xmlResult = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); xmlResult.Append("<ImgMessage>"); xmlResult.AppendFormat("<ImgName>{0}</ImgName>", ImgName); xmlResult.AppendFormat("<ImgBase64>{0}</ImgBase64>", ImgBase64); xmlResult.Append("</ImgMessage>"); Message = xmlResult.ToString(); } byte[] byteData = Encoding.UTF8.GetBytes(Message);//内容 int count = 16 + byteData.Length;//总长度 byte[] SendBy = new byte[count]; Array.Copy(Header, 0, SendBy, 0, Header.Length);//添加头 byte[] CountBy = BitConverter.GetBytes(count); Array.Copy(CountBy, 0, SendBy, 4, CountBy.Length);//总长度 byte[] ContentBy = BitConverter.GetBytes(byteData.Length); Array.Copy(ContentBy, 0, SendBy, 8, ContentBy.Length);//内容长度 if (SendShake)//发动震动 { var shakeBy = new byte[4] { 1, 1, 1, 1 }; Array.Copy(shakeBy, 0, SendBy, 12, shakeBy.Length);//震动 } if (SendImg)//发送图片 { var imgBy = new byte[4] { 2, 2, 2, 2 }; Array.Copy(imgBy, 0, SendBy, 12, imgBy.Length);//图片 } Array.Copy(byteData, 0, SendBy, 16, byteData.Length);//内容 return SendBy; } }
代码:
服务端:
窗体(UI)代码:
namespace SocketService { partial class Form1 { /// <summary> /// 必需的设计器变量。 /// </summary> private System.ComponentModel.IContainer components = null; /// <summary> /// 清理所有正在使用的资源。 /// </summary> /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param> protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows 窗体设计器生成的代码 /// <summary> /// 设计器支持所需的方法 - 不要修改 /// 使用代码编辑器修改此方法的内容。 /// </summary> private void InitializeComponent() { this.label1 = new System.Windows.Forms.Label(); this.txt_ip = new System.Windows.Forms.TextBox(); this.txt_port = new System.Windows.Forms.TextBox(); this.label2 = new System.Windows.Forms.Label(); this.btn_StartSocket = new System.Windows.Forms.Button(); this.txt_Monitor = new System.Windows.Forms.TextBox(); this.label3 = new System.Windows.Forms.Label(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.btn_SendImg = new System.Windows.Forms.Button(); this.btn_SendShake = new System.Windows.Forms.Button(); this.btn_SendMes = new System.Windows.Forms.Button(); this.txt_Mes = new System.Windows.Forms.TextBox(); this.label4 = new System.Windows.Forms.Label(); this.dataGridView1 = new System.Windows.Forms.DataGridView(); this.Column1 = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.Column2 = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.groupBox2 = new System.Windows.Forms.GroupBox(); this.lab_ImgName = new System.Windows.Forms.Label(); this.pictureBox1 = new System.Windows.Forms.PictureBox(); this.listBox_Mes = new System.Windows.Forms.ListBox(); this.listBox_attribute = new System.Windows.Forms.ListBox(); this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog(); this.btn_Stop = new System.Windows.Forms.Button(); this.label5 = new System.Windows.Forms.Label(); this.label6 = new System.Windows.Forms.Label(); this.label7 = new System.Windows.Forms.Label(); this.groupBox1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); this.groupBox2.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); this.SuspendLayout(); // // label1 // this.label1.AutoSize = true; this.label1.Location = new System.Drawing.Point(13, 13); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(17, 12); this.label1.TabIndex = 0; this.label1.Text = "IP"; // // txt_ip // this.txt_ip.Location = new System.Drawing.Point(36, 10); this.txt_ip.Name = "txt_ip"; this.txt_ip.Size = new System.Drawing.Size(100, 21); this.txt_ip.TabIndex = 1; this.txt_ip.Text = "127.0.0.1"; // // txt_port // this.txt_port.Location = new System.Drawing.Point(183, 10); 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 = "9999"; // // label2 // this.label2.AutoSize = true; this.label2.Location = new System.Drawing.Point(148, 15); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(29, 12); this.label2.TabIndex = 2; this.label2.Text = "端口"; // // btn_StartSocket // this.btn_StartSocket.Location = new System.Drawing.Point(307, 8); this.btn_StartSocket.Name = "btn_StartSocket"; this.btn_StartSocket.Size = new System.Drawing.Size(75, 23); this.btn_StartSocket.TabIndex = 4; this.btn_StartSocket.Text = "开始监听"; this.btn_StartSocket.UseVisualStyleBackColor = true; this.btn_StartSocket.Click += new System.EventHandler(this.btn_StartSocket_Click); // // txt_Monitor // this.txt_Monitor.Location = new System.Drawing.Point(460, 8); this.txt_Monitor.Name = "txt_Monitor"; this.txt_Monitor.ReadOnly = true; this.txt_Monitor.Size = new System.Drawing.Size(155, 21); this.txt_Monitor.TabIndex = 6; // // label3 // this.label3.AutoSize = true; this.label3.Location = new System.Drawing.Point(401, 11); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(53, 12); this.label3.TabIndex = 5; this