『7x24小时有问必答』
今天用分享一个C开发的  TCP 调试工具,下载链接在最后有贴出,重要的是我们通过这个工具开发熟悉C的编程基本技巧,重点巩固一下前两节的内容:C重难点1:委托  C基础委托2C重难点2:事件
1.png

配合sscom助手当TCP Server端跑起来的效果,接下来一步步讲述开发过程。(如遇大段代码,可用电脑端直接复制到vs或者notepad++中更方便查看,注意看看代码的对新手友好型的注释。)

2.gif

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

类目录如下:

3.png
点击查看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;

namespaceWindowsFormsApp1

{

//【01】声明委托

publicdelegatevoidSetListBoxDelegate(string  str);

classTCP_Core

      {

//【02】创建委托对象

public  SetListBoxDelegate SetLibxBoxDelegate;

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

privatestring  IP;

privateint  port;

privateint  receiveCount =  0;

privateint  sendCount =  0;

private  Boolean isConnected =  false;

public  Boolean IsConnected {

get  {  return  isConnected; }

set  { isConnected =  value; }

            }

publicint  SendCount {  get  {  return  sendCount; } }

publicint  RecieveCount {

get  {  return  receiveCount; }  

            }

publicvoidResetCount()  {

                  sendCount =  0;

                  receiveCount =  0;

            }

public  Socket Socket

            {//提供给外部访问的属性

get  {  return  _socket; }

set  { _socket =  value; }

            }

privatevoidGetIP_PortByParameter(string  par  )  {//从参数获取到IP和port

string  st = par.Trim( );

string[] sArray = st.Split(':');// 一定是单引   

                  IP = sArray[0];

                  port =Convert.ToInt32(sArray[1]);

            }

publicintTCP_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;

return0;

                  }

catch  (Exception)

                  {

                        _socket =  null;

return-1;

                  }

            }

publicintTCP_Send(  Socket sk,string  sd  )  {

try

                  {

if  (isConnected)

                        {

                              sk.Send(Encoding.UTF8.GetBytes(sd));

                              sendCount += sd.Length;

return0;

                        }

return-1;

                  }

catch  (Exception)

                  {

return-1;

                  }

            }

publicenum  EndChar

            {

                  None=0,

                  OD=1,

                  OA=2,

                  ODOA=3,

            }

publicintTCP_Read(  Socket sk,  string  match, EndChar endChar,  int  timeout_ms,outstring  str)  {

                  str =  "NullYK";

if  (isConnected)

                  {

                        Stopwatch stopwatch =  new  Stopwatch( );

string  recvStr =  "";

byte[] recvBytes =  newbyte[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;

                                          }

elseif  (stopwatch.ElapsedMilliseconds > timeout_ms);//超时退出while;)

                                          {

break;

                                          }

                                    }

catch  (Exception)

                                    {

break;

                                    }

                              }

if  (match !=  null)

                              {

if  (recvStr.Contains(match))

                                    {

                                          str = recvStr;

return0;

                                    }

                              }

else//没有match标志就判断结束符;

                              {

switch  (endChar)

                                    {

case  EndChar.None:

break;

case  EndChar.OD:

if  (recvStr.Contains("\r"))

                                                {

                                                      str = recvStr;

return0;

                                                }

break;

case  EndChar.OA:

if  (recvStr.Contains("\n"))

                                                {

                                                      str = recvStr;

return0;

                                                }

break;

case  EndChar.ODOA:

if  (recvStr.Contains("\r\n"))

                                                {

                                                      str = recvStr;

return0;

                                                }

break;

default:

break;

                                    }

                              }

                        str = recvStr;

                  }

return0;

            }

privatevoidSetListBox(string  dataREC  )  {

if  (dataREC.Length >  0)

                  {

                     String   str =  $"{CommonTool.GetShortTimeMillisecond( )}←⯁{dataREC}";

                        SetLibxBoxDelegate?.Invoke(str);//【执行委托】

                  }

            }

publicintTCP_Close(  Socket sk  )  {

if  (isConnected)

                  {

                        sk.Close( );

                        sk.Dispose( );

                  }

return0;

            }

      }

}

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

2 Form1的编程:

4.png

5.png
<summary>点击查看Form1代码</summary>
<code>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;

namespaceWindowsFormsApp1

{

publicpartialclassForm1  :  Form

      {

            Form2 form2=null;

readonly  TCP_Core TCP_Core1 =  new  TCP_Core( );

readonly  CommonTool commonTool =  new  CommonTool();

privatestring  dataREC =  "";

constint  Intervaltime =  2000;//读取中断时间

public  Boolean AppIsRun =  false;

///<summary>

///  具体刷新Listbox的函数

///</summary>

///<param name="str">

///  定义一个委托(delegate),委托(delegate)可以将参数与方法传递给控件所在的线程,并由控件所在的线程执行,通过Invoke来调用,这样可以完美的解决此类问题。

publicvoidRefreshLisBox(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);

            }

publicForm1()  {

                  InitializeComponent( );

//【04】委托绑定

                  TCP_Core1.SetLibxBoxDelegate+= RefreshLisBox;

            }

privatevoidbutton1_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( );

                  }

            }

privatevoidbtnSend_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( );

                  }

            }

privatevoidbutton3_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);

            }

privatevoidForm1_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;

            }

privatevoidScrollListBox(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;

                  }

            }

publicvoidTaskReadLoop()  {

while  (AppIsRun)

                  {

                        TCP_Core1.TCP_Read(TCP_Core1.Socket,  null, TCP_Core.EndChar.ODOA, Intervaltime,  out  dataREC);

                  }

            }

privatevoidtimer1_Tick(object  sender, EventArgs e  )  {

//在timer里面来接受数据会造成界面卡顿。

            }

privatevoidbtn_clear_Click(object  sender, EventArgs e  )  {

                  listBox1.Items.Clear( );

if  (form2!=null)

                  {

                        form2.CleardataGridView( );

                  }

                  TCP_Core1.ResetCount( );

            }

privatevoidcheckBox1_CheckedChanged(object  sender, EventArgs e  )  {

                 commonTool.Enable= checkBox1.Checked;

            }

privatevoidbutton1_Click_1(object  sender, EventArgs e  )  {

            }

privatevoidgroupBox1_Enter(object  sender, EventArgs e  )  {

            }

privatevoidopenTestWindowToolStripMenuItem_Click(object  sender, EventArgs e  )  {

if  (form2==null)

                        {

                              form2 =  new  Form2( );

                              form2.NewDataIn += form2.OnNewString;//[A4]挂接委托

                              form2.Show( );

                        }

else

                  {

                        form2.Visible =  true;

                  }

            }

privatevoidtb_time_TextChanged(object  sender, EventArgs e  )  {

            }

privatevoidForm1_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)我们定义了一个一直循环接受的方法:
<code>publicvoidTaskReadLoop()  {

while  (AppIsRun)

                  {

                        TCP_Core1.TCP_Read(TCP_Core1.Socket,  null, TCP_Core.EndChar.ODOA, Intervaltime,  out  dataREC);

                  }

            }

AppIsRun是存储软件是否在运行的字段;

(2)如果我们把上述TaskReadLoop( )直接在界面线程中运行的话,那么UI就会卡死;

所以我们得单独开辟个线程来运行:
<code>privatevoidbutton1_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控件;
<code>publicvoidRefreshLisBox(String str )  {//【03】委托函数(匿名委托),否则独立线程中试图刷新界面,会报错

Actionaction=  ( ) =>

                  {

                        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的文件列表:

6.png

为了当有新的数据传过来时,有变量存储,我们首先定义了一个 字段:

public string newString= "";

然后定义了一个自定义事件——
7.png

代码:
<code>namespaceWindowsFormsApp1

{

publicdelegatevoiddeleUserEvent();//【A1】声明委托类型

publicpartialclassForm2  :  Form

      {

publicevent  deleUserEvent NewDataIn =  null;//[A2]声明Form2类的一个事件

publicstring  newString=  "";

当有新的数据时触发事件,我们的事件处理器如下:
<code>publicvoidOnNewString( ){//[A3]事件处理器

                  dataGridView1.Rows.Add(newString);

this.dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.Rows[dataGridView1.Rows.Count -  1].Index;

            }

事件的挂架:在打开Form2窗口时,我们变用Form2自己的事件处理器来挂接自己的事件
<code>privatevoidopenTestWindowToolStripMenuItem_Click(object  sender, EventArgs e  ) {

if  (form2==null)

                        {

                              form2 =  newForm2( );

                              form2.NewDataIn  += form2.OnNewString;//[A4]挂接委托

                              form2.Show( );

                        }

else

                  {

                        form2.Visible  =  true;

                  }

            }

接下来在Form1中修改如下RefreshListBox代码:
<code>publicvoidRefreshLisBox(String str )  {//【03】委托函数(匿名委托),否则独立线程中试图刷新界面,会报错

Actionaction=  ( ) =>

                  {

                        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( )的方法是为了执行委托:
<code>publicvoidSetNewString()  {

                  NewDataIn?.Invoke( );//【A5】执行方法

            }

<summary>点击查看Form2的完整代码</summary>
<code>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;

namespaceWindowsFormsApp1

{

publicdelegatevoiddeleUserEvent();//【A1】声明委托类型

publicpartialclassForm2  :  Form

      {

publicevent  deleUserEvent NewDataIn =  null;//[A2]声明Form2类的一个事件

publicstring  newString=  "";

publicvoidSetNewString()  {

                  NewDataIn?.Invoke( );//【A5】执行方法

            }

publicvoidCleardataGridView()  {

                  dataGridView1.Rows.Clear( );

            }

publicvoidOnNewString()  {//[A3]事件处理器

                  dataGridView1.Rows.Add(newString);

this.dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.Rows[dataGridView1.Rows.Count -  1].Index;

            }

publicForm2()  {

                  InitializeComponent( );

///  新添加这一句调用就行了,如果有ListViews也是这样添加,

///  但要注意方法里改为有关ListViews的声明即可

                  dataGridView1.DoubleBufferedDataGirdView(true);

this.Text =  "Showed in \"dataGridView\"";

            }

privatevoidForm2_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;

            }

privatevoidForm2_FormClosing(object  sender, FormClosingEventArgs e  )  {

                  e.Cancel =  true;

this.Visible =  false;

            }

      }

publicstaticclassDoubleBufferDataGridView

      {

///<summary>

///  双缓冲,解决闪烁问题

///</summary>

publicstaticvoidDoubleBufferedDataGirdView(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属性控制是否启用;
<summary>点击查看class CommonTool代码</summary>
<code>using  System;

using  System.Collections.Generic;

using  System.Linq;

using  System.Text;

using  System.Threading.Tasks;

namespaceWindowsFormsApp1

{

/*This class contain some unival tool like time stamp,string convertion etc.*/

classCommonTool

      {

staticbool  enable=false;

staticstring  formattedDateTime =  string.Empty;

publicbool  Enable {  get  {  return  enable; }   set  { enable =  value; } }

publicstaticstringGetLongTime()  {

if  (enable)

                  {

                        DateTime dateTime = DateTime.Now;

                        formattedDateTime = dateTime.ToString("[yyyy-MM-dd HH:mm:ss]");  //

                  }

else

                  {

                        formattedDateTime =  string.Empty;

                  }

return  formattedDateTime;  

            }

publicstaticstringGetShortTime()  {

if  (enable)

                  {

                        DateTime dateTime = DateTime.Now;

                        formattedDateTime = dateTime.ToString("[HH:mm:ss]");  //

                  }

else

                  {

                        formattedDateTime =  string.Empty;

                  }

return  formattedDateTime;

            }

publicstaticstringGetShortTimeMillisecond()  {

if  (enable)

                  {

                        DateTime dateTime = DateTime.Now;

                        formattedDateTime = dateTime.ToString("[HH:mm:ss.fff]");  //

                  }

else

                  {

                        formattedDateTime =  string.Empty;

                  }

return  formattedDateTime;

            }

      }

}

如果有些无法展开看代码的部分,请移步我的博客:
https://www.cnblogs.com/StephenYoung/p/18381337
看得更清楚,舒服。
需要整个完整项目文件,请后台私信我,或者加我微信。
打包成TCP Debug Tool.exe后的下载链接,解压缩密码:LabVIEW9527
__EOF__
8.png
</code></code></code></code></code></code></code></code></code></code></code>

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码