上一篇学习日记C#网络编程之--TCP协议(一)中以服务端接受客户端的请求链接结尾
既然服务端已经与客户端创建了链接,那么沟统统道已经打通,载满数据的小火车就能够彼此传送和接收了。如今让咱们来看看数据的传送与接收html
先把服务端与客户端的链接代码敲出来编程
服务端 IPAddress ip = new IPAddress(new byte[] { 127, 1, 1, 1 }); TcpListener server = new TcpListener(ip, 8005); server.Start();//服务端启动侦听
TcpClient client = server.AcceptTcpClient();//接受发起链接对象的同步方法
Console.WriteLine("收到客户端链接请求")//若是没有客户端请求链接,这句话是没法Print out的
客户端 IPAddress ip=IPAddress.Parse("127.1.1.1"); TcpClient client=new TcpClient(); client.Connect(ip,8005);//8005端口号,必须与服务端给定的端口号一致,不然天堂无门
先看看服务端的特殊标记的那句代码小程序
AcceptTcpClient() 这个方法是一个同步方法,在没有接受到链接请求的时候,位于它下面的代码是不会被执行的,也就是线程阻塞在这里,进行不下去了,想出城没有城防长官的批复是不能的,嘿嘿...数组
链接后,客户端要发送数据给服务端,先贴代码再说缓存
NetworkStream dataStream=client.GetStream(); string msg="服务端亲启!"; byte[] buffer=Encoding.default.getBytes(msg); stream.write(buffer,0,buffer.length);
//这段代码呈接上面那段客户端代码
NetworkStream 在网络中进行传输的数据流,也就是说传输数据必须写入此流中,才可以互通有无。
首先客户端先获取用于发送信息的流,而后将要发送的信息存入byte[] 数组中(数据必须是byte[] 才可以写入流中),最后就是写入传输的数据流,发送安全
聪明的你想必已经知道如何在服务端获取数据了
既然客户端费力的把数据包装发给服务端了,那么服务端天然要把包装拆了,获得数据,上代码:网络
NetworkStream dataStream=client.GetStream(); byte[] buffer=new byte[8192]; int dataSize=dataStream.Read(buffer,0,8192); Console.write(Encoding.default.GetString(buffer,0,dataSize)); //这段代码呈接上面那段服务端代码
代码一写,我以为再说多余了,不过还要在说一两句,嘿嘿
Read() 方法须要三个参数,1,存储数据的缓存空间。2,写入数据的起始点就是从存储空间的什么位置开始写入数据。3,就是存储空间的大小。返回写入数据的大小值
Encoding.default.GetString() 参数解析
1,存储数据的缓存空间。2,从什么位置开始接收数据。3,接收多少数据多线程
以上只是再简单不过的数据发送,并且只是客户端发给服务端,只能发一条信息而已,那若是想彼此互发,而且想发多少条信息均可以,怎么办呢异步
首先基于以上的代码,编写一个WPF的小程序post
下图分别是客户端和服务端
界面很简单,要实现的功能就是客户端与服务端互发信息。
感受仍是直接上代码吧
服务端的所有代码以下:
public delegate void showData(string msg);//委托,防止跨线程的访问控件,引发的安全异常 private const int bufferSize = 8000;//缓存空间 private TcpClient client; private TcpListener server; /// <summary> /// 结构体:Ip、端口 /// </summary> struct IpAndPort { public string Ip; public string Port; } /// <summary> /// 开始侦听 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStart_Click(object sender, RoutedEventArgs e) { if (txtIP.Text.Trim() == string.Empty) { return; } if (txtPort.Text.Trim() == string.Empty) { return; } Thread thread = new Thread(reciveAndListener); //若是线程绑定的方法带有参数的话,那么这个参数的类型必须是object类型,因此讲ip,和端口号 写成一个结构体进行传递 IpAndPort ipHePort = new IpAndPort(); ipHePort.Ip = txtIP.Text; ipHePort.Port = txtPort.Text; thread.Start((object)ipHePort); } /// <summary> /// 发送信息给客户端 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, RoutedEventArgs e) { if (txtSendMsg.Text.Trim() != string.Empty) { NetworkStream sendStream = client.GetStream();//得到用于数据传输的流 byte[] buffer = Encoding.Default.GetBytes(txtSendMsg.Text.Trim());//将数据存进缓存中 sendStream.Write(buffer,0,buffer.Length);//最终写入流中 txtSendMsg.Text = string.Empty; } } /// <summary> /// 侦听客户端的链接并接收客户端发送的信息 /// </summary> /// <param name="ipAndPort">服务端Ip、侦听端口</param> private void reciveAndListener(object ipAndPort) { IpAndPort ipHePort = (IpAndPort)ipAndPort; IPAddress ip = IPAddress.Parse(ipHePort.Ip); server = new TcpListener(ip, int.Parse(ipHePort.Port)); server.Start();//启动监听 rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText), "服务端开启侦听....\n"); // btnStart.IsEnabled = false; //获取链接的客户端对象 client = server.AcceptTcpClient(); rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText),"有客户端请求链接,链接已创建!");//AcceptTcpClient 是同步方法,会阻塞进程,获得链接对象后才会执行这一步 //得到流 NetworkStream reciveStream = client.GetStream(); #region 循环监听客户端发来的信息 do { byte[] buffer = new byte[bufferSize]; int msgSize; try { lock (reciveStream) { msgSize = reciveStream.Read(buffer, 0, bufferSize); } if (msgSize == 0) return; string msg = Encoding.Default.GetString(buffer, 0, bufferSize); rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText), "\n客户端曰:" + Encoding.Default.GetString(buffer, 0, msgSize)); } catch { rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText), "\n 出现异常:链接被迫关闭" ); break; } } while (true); #endregion }
客户端代码:
TcpClient client; private const int bufferSize = 8000; NetworkStream sendStream; public delegate void showData(string msg); private void btnConnect_Click(object sender, RoutedEventArgs e) { if (txtIP.Text.Trim() == string.Empty) { return; } if (txtPort.Text.Trim() == string.Empty) { return; } IPAddress ip = IPAddress.Parse(txtIP.Text); client = new TcpClient(); client.Connect(ip, int.Parse(txtPort.Text)); rtbtxtShowData.AppendText("开始链接服务端....\n"); rtbtxtShowData.AppendText("已经链接服务端\n"); //获取用于发送数据的传输流 sendStream = client.GetStream(); Thread thread = new Thread(ListenerServer); thread.Start(); } private void btnSend_Click(object sender, RoutedEventArgs e) { if (client != null) { //要发送的信息 if (txtSendMsg.Text.Trim() == string.Empty) return; string msg = txtSendMsg.Text.Trim(); //将信息存入缓存中 byte[] buffer = Encoding.Default.GetBytes(msg); //lock (sendStream) //{ sendStream.Write(buffer, 0, buffer.Length); //} rtbtxtShowData.AppendText("发送给服务端的数据:" + msg + "\n"); txtSendMsg.Text = string.Empty; } } private void ListenerServer() { do { try { int readSize; byte[] buffer = new byte[bufferSize]; lock (sendStream) { readSize = sendStream.Read(buffer, 0, bufferSize); } if (readSize == 0) return; rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText), "服务端曰:" + Encoding.Default.GetString(buffer, 0, readSize)+"\n"); } catch { rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText), "报错"); } //将缓存中的数据写入传输流 } while (true); }
其中用到了,多线程处理还有委托,由于以上咱们用到的不论是Connect,仍是AcceptTcpClient方法 都是同步方法,会阻塞进程,致使窗口没法自由移动
rtbtxtShowData.Dispatcher.Invoke(new showData(rtbtxtShowData.AppendText), "服务端开启侦听....\n");
上面这句代码或许有些人不解,我也花了一些时间才懂这样写的
其实因为在WPF中不容许跨线程访问,访问了会抛异常,可是在WPF中的窗口控件都有一个Dispatcher(调度器)属性,容许访问控件的线程;既然不容许直接访问,就告诉控件咱们要干什么就行了。
因此在多线程中使用控件的Dispatcher属性,这样就不是跨线程访问了,而后咱们在看看Invoke方法
经过上面的标示,看的出须要一个委托类型的方法,因此就将RichTextBox 的赋值方法AppendText 绑定到一个委托showData上。
下面是一段引用,看了或许能更明白点
WPF的UI线程都交给一个叫作调度器的类了。 WPF 应用程序启动时具备两个线程:一个用于处理呈现,另外一个用于管理 UI。 呈现线程实际上隐藏在后台运行,而 UI 线程则接收输入、处理事件、绘制屏幕以及运行应用程序代码。UI 线程在一个名为 Dispatcher 的对象中将工做项进行排队。 Dispatcher 根据优先级选择工做项,并运行每个工做项直到完成。Dispatcher 类提供两种注册工做项的方法:Invoke 和 BeginInvoke。 这两个方法都会安排执行一个委托。Invoke 是同步调用,即它直到 UI 线程实际执行完该委托时才返回。BeginInvoke 是异步调用,于是将当即返回。------引用自WPF笔记12: 线程处理模型
执行以上程序的效果图:
Ok,至此客户端与服务端的数据传递就大功告成了,这只是一个很简单的操做,若是有多个客户端呢?要求异步通讯,怎么办?不急,慢慢来,不积跬步无以致千里
若是有什么错的,但愿指正。