TCP的调试助手开发笔记

0 动图:

1 先利用VS自带的socket类来写好TCP_CORE:

类目录如下:

点击查看TCP_CORE class的完整代码
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    //【01】声明委托
    public delegate void SetListBoxDelegate(string str);
    class TCP_Core
    {
    //【02】创建委托对象
        public SetListBoxDelegate SetLibxBoxDelegate;

        private Socket _socket;//定义私有字段存放soket句柄
        private string IP;
        private int port;

        private int receiveCount = 0;

        private int sendCount = 0;
        private Boolean isConnected = false;
        public Boolean IsConnected {
            get { return isConnected; }
            set { isConnected = value; }
        }

        public int SendCount { get { return sendCount; } }
        public int RecieveCount {
            get { return receiveCount; } 
        }
        public void ResetCount( ) {
            sendCount = 0;
            receiveCount = 0;
        }
        public Socket Socket
        {//提供给外部访问的属性
            get { return _socket; }
            set { _socket = value; }
        }

        private void GetIP_PortByParameter( string par ) {//从参数获取到IP和port
            string st = par.Trim( );
            string[] sArray = st.Split(':');// 一定是单引  
            IP = sArray[0];
            port =Convert.ToInt32(sArray[1]);
        }
        public int TCP_Open( string par ) {
            try
            {
                Socket client_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                GetIP_PortByParameter(par);
                IPAddress ipAdress = IPAddress.Parse(IP);
                //网络端点:为待请求连接的IP地址和端口号
                IPEndPoint ipEndpoint = new IPEndPoint(ipAdress, port);
                //connect()向服务端发出连接请求。客户端不需要bind()绑定ip和端口号,
                //因为系统会自动生成一个随机的地址(具体应该为本机IP+随机端口号)
                client_socket.Connect(ipEndpoint);
                _socket = client_socket;
                isConnected = true;
                return 0;
            }
            catch (Exception)
            {
                _socket = null;
                return -1;
            }
        }
        public int TCP_Send( Socket sk,string sd ) {
            try
            {
                if (isConnected)
                {
                    sk.Send(Encoding.UTF8.GetBytes(sd));
                    sendCount += sd.Length;
                    return 0;
                }
                return -1;
            }
            catch (Exception)
            {
                return -1;
            }
        }

       public enum EndChar
        {
            None=0,
            OD=1,
            OA=2,
            ODOA=3,
        }
        public int TCP_Read( Socket sk, string match, EndChar endChar, int timeout_ms,out string str) {
            str = "NullYK";
            if (isConnected)
            {
                Stopwatch stopwatch = new Stopwatch( );
                string recvStr = "";
                byte[] recvBytes = new byte[1024];
                int bytes;
                stopwatch.Start( );
                sk.ReceiveTimeout = timeout_ms;
                    while (true)
                    {
                        try
                        {
                            bytes = sk.Receive(recvBytes, recvBytes.Length, SocketFlags.None);//从客户端接受信息
                            recvStr += Encoding.ASCII.GetString(recvBytes, 0, bytes);
                            receiveCount += bytes;
                            if (recvStr.Length != 0 )
                            {
                                SetListBox(recvStr);
                                break;
                            }
                            else if (stopwatch.ElapsedMilliseconds > timeout_ms);//超时退出while;)
                            {
                                break;
                            }
                        }
                        catch (Exception)
                        {
                            break;
                        }
                    }

                    if (match != null)
                    {
                        if (recvStr.Contains(match))
                        {
                            str = recvStr;
                            return 0;
                        }
                    }
                    else//没有match标志就判断结束符;
                    {
                        switch (endChar)
                        {
                            case EndChar.None:
                                break;
                            case EndChar.OD:
                                if (recvStr.Contains("\r"))
                                {
                                    str = recvStr;
                                    return 0;
                                }
                                break;
                            case EndChar.OA:
                                if (recvStr.Contains("\n"))
                                {
                                    str = recvStr;
                                    return 0;
                                }
                                break;
                            case EndChar.ODOA:
                                if (recvStr.Contains("\r\n"))
                                {
                                    str = recvStr;
                                    return 0;
                                }
                                break;
                            default:
                                break;
                        }
                    }
                str = recvStr;

            }
            return 0;
        }
        private void SetListBox(string dataREC ) {
            if (dataREC.Length > 0)
            {
              String  str = $"{CommonTool.GetShortTimeMillisecond( )}←⯁{dataREC}";
                SetLibxBoxDelegate?.Invoke(str);//【执行委托】
            }

        }

        public int TCP_Close( Socket sk ) {
            if (isConnected)
            {
                sk.Close( );
                sk.Dispose( );
            }
            return 0;
        }
    }
}

说明下:这个委托的目的,用来在此类中能操作触发对界面控件的刷新。

2 Form1的编程:


点击查看Form1代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{

    public partial class Form1 : Form
    {
        Form2 form2=null;

        readonly TCP_Core TCP_Core1 = new TCP_Core( );
       readonly CommonTool commonTool = new CommonTool();

       private  string dataREC = "";

       const int Intervaltime = 2000;//读取中断时间


       public Boolean AppIsRun = false;
        /// <summary>
        /// 具体刷新Listbox的函数
        /// </summary>
        /// <param name="str"></param>
        /// 定义一个委托(delegate),委托(delegate)可以将参数与方法传递给控件所在的线程,并由控件所在的线程执行,通过Invoke来调用,这样可以完美的解决此类问题。
        public void RefreshLisBox(String str ) {//【03】委托函数(匿名委托),否则独立线程中试图刷新界面,会报错
            Action action = ( ) =>
            {
                listBox1.Items.Add(str);
                ScrollListBox(listBox1);

                toolStripStatusLabelSendcount.Text = TCP_Core1.SendCount.ToString( );
                toolStripStatusLabelReceive.Text = TCP_Core1.RecieveCount.ToString( );

                //更新数据到Form2的dataGridView控件上
                if (form2!=null)
                {
                    form2.newString = str;
                    form2.SetNewString( );
                }
            };
            Invoke(action);
        }
        public Form1( ) {

            InitializeComponent( );

            //【04】委托绑定
            TCP_Core1.SetLibxBoxDelegate+= RefreshLisBox;
        }


        private void button1_Click( object sender, EventArgs e ) {
            if (TCP_Core1.TCP_Open(textBox1.Text) == 0)
            {
                listBox1.Items.Add($"Connect [{textBox1.Text}] Successfully.");
                btn_send.Enabled = true;
                btn_close.Enabled = true;
                this.toolStripStatusLabel0.Text = "Ready";

                //Task task = new Task(TaskReadLoop);
                //task.Start( );

                Thread thread1 = new Thread(TaskReadLoop);//独立线程运行TCP接受函数,此函数内部有 执行委托TCP_Core1.SetLibxBoxDelegate 动作
                thread1.Start( );


                timer1.Start( );
            }
            else
            {
                listBox1.Items.Add("Connect Failed.");
                this.toolStripStatusLabel0.Text = "Error";
                timer1.Stop( );
            }
        }
        private void btnSend_Click( object sender, EventArgs e ) {
            string data = $"{CommonTool.GetShortTimeMillisecond( )}→⟐{textBox2.Text}";
            listBox1.Items.Add(data);
            TCP_Core1.TCP_Send(TCP_Core1.Socket,textBox2.Text );
            ScrollListBox( listBox1);

            if (form2!=null)
            {
                form2.newString = data;
                form2.SetNewString( );
            }
        }

        private void button3_Click( object sender, EventArgs e ) {
            timer1.Stop( );
            btn_open.Enabled = true;
            btn_send.Enabled = false;
            btn_close.Enabled = false;

            TCP_Core1.IsConnected = false;
            TCP_Core1.TCP_Close( TCP_Core1.Socket);

        }

        private void Form1_Load( object sender, EventArgs e ) {
            textBox1.Text = "127.0.0.1:7200";
            textBox2.Text = "yk test TCP CORE";

            tableLayoutPanel1.Dock = DockStyle.Fill;
            timer1.Interval =100;

            this.Text = "TCP Debug Tool";
            label1.Text = "Log:";
            label2.Text = "IP:port";
            label3.Text = "Send:";
            listBox1.Items.Clear( );
            listBox1.GetType( ).GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(listBox1, true, null);
            btn_open.Enabled = true;
            btn_send.Enabled = false;
            btn_close.Enabled = false;

            this.toolStripStatusLabel0.Text = "Please Open a connection";

            AppIsRun = true;
        }

       
        private void ScrollListBox(ListBox listBox ) {//在添加新记录前,先计算滚动条是否在底部,从而决定添加后是否自动滚动。
// 既可以在需要时实现自动滚动,又不会在频繁添加记录时干扰用户对滚动条的控制。

            int ctl_rows =Convert.ToInt32( listBox1.Height / this.listBox1.ItemHeight);
            if (listBox1.Items.Count > (ctl_rows - 1)){
                listBox1.TopIndex = listBox1.Items.Count - ctl_rows+3;
            }
            else {
                listBox1.TopIndex = 0;
            }
        }

        public void TaskReadLoop( ) {
            while (AppIsRun)
            {
                TCP_Core1.TCP_Read(TCP_Core1.Socket, null, TCP_Core.EndChar.ODOA, Intervaltime, out dataREC);
            }
        }
        private void timer1_Tick( object sender, EventArgs e ) {
            //在timer里面来接受数据会造成界面卡顿。
        }


        private void btn_clear_Click( object sender, EventArgs e ) {
            listBox1.Items.Clear( );
            if (form2!=null)
            {
                form2.CleardataGridView( );
            }
            TCP_Core1.ResetCount( );
        }

        private void checkBox1_CheckedChanged( object sender, EventArgs e ) {
           commonTool.Enable= checkBox1.Checked;
        }

        private void button1_Click_1( object sender, EventArgs e ) {

        }

        private void groupBox1_Enter( object sender, EventArgs e ) {

        }

        private void openTestWindowToolStripMenuItem_Click( object sender, EventArgs e ) {

                if (form2==null)
                {
                    form2 = new Form2( );
                    form2.NewDataIn += form2.OnNewString;//[A4]挂接委托
                    form2.Show( );
                }
            else
            {
                form2.Visible = true;
            }
        }

        private void tb_time_TextChanged( object sender, EventArgs e ) {

        }

        private void Form1_FormClosing( object sender, FormClosingEventArgs e ) {
            if (MessageBox.Show("Are you sure to exit?","Information",MessageBoxButtons.YesNo,MessageBoxIcon.Question)==DialogResult.Yes)
            {
                TCP_Core1.IsConnected = false;
                TCP_Core1.TCP_Close(TCP_Core1.Socket);
                AppIsRun = false;
                e.Cancel = false;
            }
            else
            {
                e.Cancel = true;
            }
        }
    }
}

这里有几点重要的要说明:
(1)我们定义了一个一直循环接受的方法:

  public void TaskReadLoop( ) {
            while (AppIsRun)
            {
                TCP_Core1.TCP_Read(TCP_Core1.Socket, null, TCP_Core.EndChar.ODOA, Intervaltime, out dataREC);
            }
        }

AppIsRun是存储软件知否在运行的字段;
(2)如果我们把上述TaskReadLoop( )直接在界面线程中运行的话,那么UI就会卡死;
所以我们得单独开辟个线程来运行:

 private void button1_Click( object sender, EventArgs e ) {
            if (TCP_Core1.TCP_Open(textBox1.Text) == 0)
            {
                listBox1.Items.Add($"Connect [{textBox1.Text}] Successfully.");
                btn_send.Enabled = true;
                btn_close.Enabled = true;
                this.toolStripStatusLabel0.Text = "Ready";

                //Task task = new Task(TaskReadLoop);
                //task.Start( );

                Thread thread1 = new Thread(TaskReadLoop);//独立线程运行TCP接受函数,此函数内部有 执行委托TCP_Core1.SetLibxBoxDelegate 动作
                thread1.Start( );

(3)委托 TCP_Core1.SetLibxBoxDelegate+= RefreshLisBox的挂接的方法,RefreshLisBox这里要使用匿名委托,否则会报错:不可以跨线程访问Listbox控件;

public void RefreshLisBox(String str ) {//【03】委托函数(匿名委托),否则独立线程中试图刷新界面,会报错
            Action action = ( ) =>
            {
                listBox1.Items.Add(str);
                ScrollListBox(listBox1);

                toolStripStatusLabelSendcount.Text = TCP_Core1.SendCount.ToString( );
                toolStripStatusLabelReceive.Text = TCP_Core1.RecieveCount.ToString( );

                //更新数据到Form2的dataGridView控件上
                if (form2!=null)
                {
                    form2.newString = str;
                    form2.SetNewString( );
                }
            };
            Invoke(action);
        }

3 Form2的编程

由于Form1上使用的Listbox控件来显示收发数据记录,会发生闪烁,尤其是在将窗口最大化时候,更明显。尝试了很多方法,比如 开双缓存等,几乎没效果。
而Form2上我用dataGridView来显示收发数据记录,我们会发现,同样的情况下,dataGridView控件不闪烁。关于Form2有几个知识点:
(1)如何将收发的数据也能同事传到Form2上的dataGridView控件?
Form2的文件列表:

为了当有新的数据传过来时,有变量存储,我们首先定义了一个 字段:
public string newString= "";
然后定义了一个自定义事件——
代码:

namespace WindowsFormsApp1
{
    public delegate void deleUserEvent( );//【A1】声明委托类型
    public partial class Form2 : Form
    {
       public event deleUserEvent NewDataIn = null;//[A2]声明Form2类的一个事件

        public string newString= "";

当有新的数据时触发事件,我们的事件处理器如下:

public void OnNewString( ) {//[A3]事件处理器
            dataGridView1.Rows.Add(newString);
            this.dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.Rows[dataGridView1.Rows.Count - 1].Index;
        }

事件的挂架:在打开Form2窗口时,我们变用Form2自己的事件处理器来挂接自己的事件

private void openTestWindowToolStripMenuItem_Click( object sender, EventArgs e ) {

                if (form2==null)
                {
                    form2 = new Form2( );
                    form2.NewDataIn += form2.OnNewString;//[A4]挂接委托
                    form2.Show( );
                }
            else
            {
                form2.Visible = true;
            }
        }

接下来在Form1中修改如下RefreshListBox代码:

  public void RefreshLisBox(String str ) {//【03】委托函数(匿名委托),否则独立线程中试图刷新界面,会报错
            Action action = ( ) =>
            {
                listBox1.Items.Add(str);
                ScrollListBox(listBox1);

                toolStripStatusLabelSendcount.Text = TCP_Core1.SendCount.ToString( );
                toolStripStatusLabelReceive.Text = TCP_Core1.RecieveCount.ToString( );

                //更新数据到Form2的dataGridView控件上
                if (form2!=null)
                {
                    form2.newString = str;
                    form2.SetNewString( );
                }
            };
            Invoke(action);
        }

其中form2.SetNewString( )的方法是为了执行委托:

 public void SetNewString() {
            NewDataIn?.Invoke( );//【A5】执行方法
        }
点击查看Form2的完整代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public delegate void deleUserEvent( );//【A1】声明委托类型
    public partial class Form2 : Form
    {
       public event deleUserEvent NewDataIn = null;//[A2]声明Form2类的一个事件

        public string newString= "";

        public void SetNewString() {
            NewDataIn?.Invoke( );//【A5】执行方法
        }

        public void CleardataGridView( ) {
            dataGridView1.Rows.Clear( );
        }
        public void OnNewString( ) {//[A3]事件处理器
            dataGridView1.Rows.Add(newString);
            this.dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.Rows[dataGridView1.Rows.Count - 1].Index;
        }
        public Form2( ) {
            InitializeComponent( );

            /// 新添加这一句调用就行了,如果有ListViews也是这样添加,
            /// 但要注意方法里改为有关ListViews的声明即可
            dataGridView1.DoubleBufferedDataGirdView(true);
            this.Text = "Showed in \"dataGridView\"";
        }

        private void Form2_Load( object sender, EventArgs e ) {
            dataGridView1.Columns.Add( "data","Data");
            dataGridView1.Columns.Add( "Note","note");
            dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dataGridView1.Rows.Add( "test data show with no shake!!!!");
            dataGridView1.Dock = DockStyle.Fill;
        }

        private void Form2_FormClosing( object sender, FormClosingEventArgs e ) {
            e.Cancel = true;
            this.Visible = false;
        }
    }
    public static class DoubleBufferDataGridView
    {
        /// <summary>
        /// 双缓冲,解决闪烁问题
        /// </summary>
        public static void DoubleBufferedDataGirdView( this DataGridView dgv, bool flag ) {
            Type dgvType = dgv.GetType( );
            PropertyInfo pi = dgvType.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic);
            pi.SetValue(dgv, flag, null);
        }
    }
}

4 还有一个CommonTool类

这个类用来获取各种格式的时间信息,几个方法都返回不同格式的时间字符串,有一个enable属性控制是否启用;

点击查看class CommonTool代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WindowsFormsApp1
{
    /*This class contain some unival tool like time stamp,string convertion etc.*/
    class CommonTool
    {
        static  bool enable=false;
        static  string formattedDateTime = string.Empty;

        
        public bool Enable { get { return enable; }  set { enable = value; } }
        public static string GetLongTime( ) {
            if (enable)
            {
                DateTime dateTime = DateTime.Now;
                formattedDateTime = dateTime.ToString("[yyyy-MM-dd HH:mm:ss]"); //
            }
            else
            {
                formattedDateTime = string.Empty;
            }
            return formattedDateTime; 
        }

        public static string GetShortTime( ) {
            if (enable)
            {
                DateTime dateTime = DateTime.Now;
                formattedDateTime = dateTime.ToString("[HH:mm:ss]"); //
            }
            else
            {
                formattedDateTime = string.Empty;
            }
            return formattedDateTime;
        }
        public static string GetShortTimeMillisecond( ) {
            if (enable)
            {
                DateTime dateTime = DateTime.Now;
                formattedDateTime = dateTime.ToString("[HH:mm:ss.fff]"); //
            }
            else
            {
                formattedDateTime = string.Empty;
            }
            return formattedDateTime;
        }
    }
}

posted @ 2024-08-26 17:26  Stephen_Young  阅读(10)  评论(0编辑  收藏  举报