前言

关于网络通信:

每一台电脑都有自己的ip地址,每台电脑上的网络应用程序都有自己的通信端口,

张三的电脑(ip:192.168.1.110)上有一个网络应用程序A(通信端口5000),

李四的电脑(ip:192.168.1.220)上有一个网络应用程序B(通信端口8000),

张三给李四发消息,首先你要知道李四的ip地址,向指定的ip(李四ip:192.168.1.220)发信息,

信息就发到了李四的电脑。

再指定一下发送的端口号(通信端口8000),信息就发到了李四电脑的网络应用程序B上。

TCP--一种网络通信方式而已。分为服务器(网络应用程序)和客户端(网络应用程序).


说明

这节教给大家用C#写一个TCP客户端程序


页面

C#开发: 通信篇-TCP客户端_ip地址




请用户在学习这节之前务必先学完

C#开发: 通信篇-串口调试助手

以上文章涉及到的基础知识不再重复赘述


编写连接程序



1.定义一个 socket

C#开发: 通信篇-TCP客户端_数据_02







private Socket MySocket = null;// Socket





如果报错,按照下面步骤添加Socket对应的包(命名空间)

C#开发: 通信篇-TCP客户端_16进制_03






C#开发: 通信篇-TCP客户端_端口号_04









2.编写点击按钮连接/断开程序

C#开发: 通信篇-TCP客户端_数据_05







C#开发: 通信篇-TCP客户端_端口号_06







/// <连接按钮点击事件>         ///          /// </summary>         /// <param name="sender"></param>         /// <param name="e"></param>         private void button1_Click(object sender, EventArgs e)         {             if (button1.Text == "连接"){                 //IP地址 和 端口号输入不为空                 if (string.IsNullOrEmpty(textBox1.Text) == false && string.IsNullOrEmpty(textBox2.Text) == false){                     try{                         IPAddress ipAddress = IPAddress.Parse(textBox1.Text);//获取IP地址                         int Port = Convert.ToInt32(textBox2.Text);          //获取端口号                          MySocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);                          //使用 BeginConnect 异步连接                         MySocket.BeginConnect(ipAddress, Port, new AsyncCallback(ConnectedCallback), MySocket);                     }                     catch (Exception){                         MessageBox.Show("IP地址或端口号错误!", "提示");                     }                 }                 else{                     MessageBox.Show("IP地址或端口号错误!", "提示");                 }             }             else             {                 try{                     button1.Text = "连接";                     MySocket.BeginDisconnect(false,null,null);//断开连接                 }                 catch (Exception){}             }         }         /// <连接异步回调函数>         ///          /// </summary>         /// <param name="ar"></param>         void ConnectedCallback(IAsyncResult ar)         {             Socket socket = (Socket)ar.AsyncState;             try{                 socket.EndConnect(ar);                                  Invoke((new Action(() =>                 {                     textBox3.AppendText("成功连接服务器\n");//对话框追加显示数据                     button1.Text = "断开";                 })));             }             catch (Exception e){                 Invoke((new Action(() =>                 {                     textBox3.AppendText("连接失败:" + e.ToString());//对话框追加显示数据                 })));             }         }




3.测试

C#开发: 通信篇-TCP客户端_端口号_07






使用电脑调试助手建立一个TCP服务器

C#开发: 通信篇-TCP客户端_数据_08




查看一下自己电脑的IP地址

C#开发: 通信篇-TCP客户端_数据_09





或者点击控制面板,按照以下路径进入

C#开发: 通信篇-TCP客户端_ip地址_10




C#开发: 通信篇-TCP客户端_16进制_11




C#开发: 通信篇-TCP客户端_应用程序_12






C#开发: 通信篇-TCP客户端_数据_13




C#开发: 通信篇-TCP客户端_端口号_14




C#开发: 通信篇-TCP客户端_16进制_15





关于同步和异步

就对于上面的连接而言,其实还有一个连接函数


C#开发: 通信篇-TCP客户端_应用程序_16






首先大家应该知道,有些通信需要点时间才能完成

上面的 Connect函数执行以后就一直等待,直至连接成功才接着往下执行

Connect 函数就是个同步函数.

这样会造成程序卡机!如果卡机时间过长,程序就会报错误

一般把同步函数放到任务里面去执行,这样就避免了卡机


异步函数也是底层封装的函数,当执行完以后,不会停在那里

而是接着往下执行,有相应的事件以后才会跳到回调函数里面去.

这是异步的好处


接收数据

https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.beginreceive?view=netframework-4.8" target="_blank">​https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.beginreceive?view=netframework-4.8​


public IAsyncResult BeginReceive (byte[] buffer, int offset, int size, System.Net.Sockets.SocketFlags socketFlags, AsyncCallback callback, object state);



C#开发: 通信篇-TCP客户端_16进制_17





1.定义一个用于缓存数据的数组

C#开发: 通信篇-TCP客户端_端口号_18







public const int TCPBufferSize = 1460;//缓存的最大数据个数         public byte[] TCPBuffer = new byte[TCPBufferSize];//缓存数据的数组





2.在连接回调函数里面调用异步接收函数

C#开发: 通信篇-TCP客户端_应用程序_19






//设置异步读取数据,接收的数据缓存到TCPBuffer,接收完成跳转ReadCallback函数                 socket.BeginReceive(TCPBuffer, 0, TCPBufferSize, 0,new AsyncCallback(ReadCallback), socket);






3.接收到数据回调函数

C#开发: 通信篇-TCP客户端_ip地址_20






/// <接收到数据回调函数>         ///          /// </summary>         /// <param name="ar"></param>         void ReadCallback(IAsyncResult ar)         {             Socket socket = (Socket)ar.AsyncState;//获取链接的Socket             int CanReadLen = socket.EndReceive(ar);//结束异步读取回调,获取读取的数据个数              if (CanReadLen > 0)             {                 string str = Encoding.Default.GetString(TCPBuffer,0, CanReadLen);//Byte值根据ASCII码表转为 String                 Invoke((new Action(() => //C# 3.0以后代替委托的新方法                 {                     textBox3.AppendText(str);//对话框追加显示数据                 })));                  //设置异步读取数据,接收的数据缓存到TCPBuffer,接收完成跳转ReadCallback函数                 socket.BeginReceive(TCPBuffer,0, TCPBufferSize, 0, new AsyncCallback(ReadCallback), socket);             }             else//异常             {                 Invoke((new Action(() => //C# 3.0以后代替委托的新方法                 {                     button1.Text = "连接";                     textBox3.AppendText("\n异常断开\n");//对话框追加显示数据                 })));                 try                 {                     MySocket.BeginDisconnect(false, null, null);//断开连接                 }                 catch (Exception) { }             }         }



注意

string str = Encoding.Default.GetString(TCPBuffer,0, CanReadLen);//Byte值根据ASCII码表为 String

不能直接 Encoding.Default.GetString(TCPBuffer)

因为实际接收的数据个数为 CanReadLen




4.测试

C#开发: 通信篇-TCP客户端_16进制_21





C#开发: 通信篇-TCP客户端_数据_22





16进制显示数据

1.关于16进制显示和前面的串口上位机显示16进制数据是一样的道理

​javascript:void(0)​



/// <字节数组转16进制字符串>         /// <param name="bytes"></param>         /// <returns> String 16进制显示形式</returns>         public static string byteToHexStr(byte[] bytes)         {             string returnStr = "";             try             {                 if (bytes != null)                 {                     for (int i = 0; i < bytes.Length; i++)                     {                         returnStr += bytes[i].ToString("X2");                         returnStr += " ";//两个16进制用空格隔开,方便看数据                     }                 }                 return returnStr;             }             catch (Exception)             {                 return returnStr;             }         }




不过需要改一下,需要传进来数据长度,不能使用 bytes.Length

C#开发: 通信篇-TCP客户端_数据_23






2.添加处理

C#开发: 通信篇-TCP客户端_应用程序_24







Invoke((new Action(() => //C# 3.0以后代替委托的新方法                 {                     if (checkBox1.Checked)//16进制显示                     {                         textBox3.AppendText(byteToHexStr(TCPBuffer, CanReadLen));//对话框追加显示数据                     }                     else                     {                         textBox3.AppendText(Encoding.Default.GetString(TCPBuffer, 0, CanReadLen));//对话框追加显示数据                     }                 })));





3.测试



C#开发: 通信篇-TCP客户端_ip地址_25




C#开发: 通信篇-TCP客户端_应用程序_26





清除接收数据

C#开发: 通信篇-TCP客户端_ip地址_27


textBox3.Clear();



发送数据

C#开发: 通信篇-TCP客户端_ip地址_28






1.点击发送按钮发送数据

C#开发: 通信篇-TCP客户端_ip地址_29







String Str = textBox4.Text.ToString();//获取发送文本框里面的数据             try             {                 if (Str.Length > 0)                 {                     byte[] byteArray = Encoding.Default.GetBytes(Str);//Str 转为 Byte值                     MySocket.BeginSend(byteArray,0, byteArray.Length,0,null,null); //发送数据                 }             }             catch (Exception) { }






2.测试

C#开发: 通信篇-TCP客户端_应用程序_30




C#开发: 通信篇-TCP客户端_ip地址_31






3.发送16进制数据

3.1. 用户参考:

​javascript:void(0)​



3.2换程序如下


/// <字符串转16进制格式,不够自动前面补零>         ///          /// </summary>         /// <param name="hexString"></param>         /// <returns></returns>         private static byte[] strToToHexByte(String hexString)         {             int i;             hexString = hexString.Replace(" ", "");//清除空格             if ((hexString.Length % 2) != 0)//奇数个             {                 byte[] returnBytes = new byte[(hexString.Length + 1) / 2];                 try                 {                     for (i = 0; i < (hexString.Length - 1) / 2; i++)                     {                         returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);                     }                     returnBytes[returnBytes.Length - 1] = Convert.ToByte(hexString.Substring(hexString.Length - 1, 1).PadLeft(2, '0'), 16);                 }                 catch                 {                     MessageBox.Show("含有非16进制字符", "提示");                     return null;                 }                 return returnBytes;             }             else             {                 byte[] returnBytes = new byte[(hexString.Length) / 2];                 try                 {                     for (i = 0; i < returnBytes.Length; i++)                     {                         returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);                     }                 }                 catch                 {                     MessageBox.Show("含有非16进制字符", "提示");                     return null;                 }                 return returnBytes;             }         }




4.编写发送16进制数据程序

C#开发: 通信篇-TCP客户端_数据_32





if (checkBox2.Checked)//选择16进制发送                     {                         byte[] byteHex = strToToHexByte(Str);                         MySocket.BeginSend(byteHex, 0, byteHex.Length, 0, null, null); //发送数据                     }                     else                     {                         byte[] byteArray = Encoding.Default.GetBytes(Str);//Str 转为 Byte值                         MySocket.BeginSend(byteArray, 0, byteArray.Length, 0, null, null); //发送数据                     }




5.测试

C#开发: 通信篇-TCP客户端_数据_33




C#开发: 通信篇-TCP客户端_应用程序_34



6,清除发送

C#开发: 通信篇-TCP客户端_16进制_35





textBox4.Clear();