VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 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