|
配合sscom助手当TCP Server端跑起来的效果,接下来一步步讲述开发过程。(如遇大段代码,可用电脑端直接复制到vs或者notepad++中更方便查看,注意看看代码的对新手友好型的注释。)
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;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和portstring 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的编程: <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的文件列表:为了当有新的数据传过来时,有变量存储,我们首先定义了一个 字段:public string newString= "";然后定义了一个自定义事件——代码: <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 </code></code></code></code></code></code></code></code></code></code></code> 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |