动图:
1 先利用VS自带的socket类来写好TCP_CORE:
类目录如下:
点击查看代码
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和portstring 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的编程:
点击查看代码
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);}
`