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