c#网络编程学习笔记02_Tcp编程(中)_简单的同步tcp聊天程序
2015-10-19 13:13
846 查看
/*
写一个同步tcp程序,功能为,客户端发送一个字符串给服务器,服务器将字符串打印,服务器再将字符串全部转化为大写字母,发送给客户端,然后客户端接受打印
*/
参考大牛地址:http://www.tracefact.net/CSharp-Programming/Network-Programming-Part2.aspx
回忆一下编写服务端的一般步骤:
1.取得服务器的ip和端口号,创建TcpListener对象,调用Start方法开始监听。
2.利用TcpListener的AcceptTcpClient方法得到监听的客户端TcpClient对象,利用GetStream得到NetStream对象,即字节流。然后可选取其他流处理方法进行字符或者字节流处理。
3.进行通信。
4. 关闭流。关闭监听。
上代码:
服务器端:
客户端程序1:测试连接:
先运行服务器,再运行客户端发现:
连接成功!!,但是,,我们发现服务器端并没有执行流处理,即int bytesRead = streamToClient.Read(buffer, 0, BufferSize);说明Read方法是一个同步方法,这里没有接受字符串,发生了阻塞。我们接下来开始发送字符串,完善客户端程序。
客户端程序二:完善版
运行结果:
恩。。看上去挺爽了,但是我在这里要强调两个问题!!!
问题1:
记得在程序结束的时候关闭流,关闭连接。
重要的重要的重要的问题2:
我在写这段代码的时候脑袋中突然出现一个问题,在服务器和客户单进行连接后,服务器和客户端的代码都在向下执行,为什么客户端写入了数据后,服务器一定就能接收到呢?为什么不是服务器先接受然后为空?过了几分钟,我明白了,NetStream中的write和read都是同步方法,都会阻塞的。。(沃日...),严格来说,与AcceptTcpClient()方法类似,这个Read()方法也是同步的,只有当客户端发送数据的时候,服务端才会读取数据、运行此方法,否则它便会一直等待。。
问题三:这个程序只能实现一个客户端,并且只能发送一条信息。
这明显是不行的,我们如何实现一个客户端,多条消息呢?
当我们需要一个服务端对同一个客户端的多次请求服务时,可以将Read()方法放入到do/while循环中。
现在,我们大致可以得出这样几个结论:
如果不使用do/while循环,服务端只有一个listener.AcceptTcpClient()方法和一个TcpClient.GetStream().Read()方法,则服务端只能处理到同一客户端的一条请求。
如果使用一个do/while循环,并将listener.AcceptTcpClient()方法和TcpClient.GetStream().Read()方法都放在这个循环以内,那么服务端将可以处理多个客户端的一条请求。
如果使用一个do/while循环,并将listener.AcceptTcpClient()方法放在循环之外,将TcpClient.GetStream().Read()方法放在循环以内,那么服务端可以处理一个客户端的多条请求。
如果使用两个do/while循环,对它们进行分别嵌套,那么结果是什么呢?结果并不是可以处理多个客户端的多条请求。因为里层的do/while循环总是在为一个客户端服务,因为它会中断在TcpClient.GetStream().Read()方法的位置,而无法执行完毕。即使可以通过某种方式让里层循环退出,比如客户端往服务端发去“exit”字符串时,服务端也只能挨个对客户端提供服务。如果服务端想执行多个客户端的多个请求,那么服务端就需要采用多线程。主线程,也就是执行外层do/while循环的线程,在收到一个TcpClient之后,必须将里层的do/while循环交给新线程去执行,然后主线程快速地重新回到listener.AcceptTcpClient()的位置,以响应其它的客户端。
对于第四种,我们在之后的博文里面讲述,现在搞下第二种和第三种。
对于第二种,我们改下服务端的代码,客户端不变:
然后启动多个客户端,结果:
下面说第三种,要稍微改动下客户端和服务器的代码:
服务端:
客户端:
运行结果:
这里还需要注意一点,当客户端在TcpClient实例上调用Close()方法,或者在流上调用Dispose()方法,服务端的streamToClient.Read()方法会持续地返回0,但是不抛出异常,所以会产生一个无限循环;而如果直接关闭掉客户端,或者客户端执行完毕但没有调用stream.Dispose()或者TcpClient.Close(),如果服务器端此时仍阻塞在Read()方法处,则会在服务器端抛出异常:“远程主机强制关闭了一个现有连接”。因此,我们将服务端的streamToClient.Read()方法需要写在一个try/catch中。同理,如果在服务端已经连接到客户端之后,服务端调用remoteClient.Close(),则客户端会得到异常“无法将数据写入传输连接:
您的主机中的软件放弃了一个已建立的连接。”;而如果服务端直接关闭程序的话,则客户端会得到异常“无法将数据写入传输连接: 远程主机强迫关闭了一个现有的连接。”。因此,它们的读写操作必须都放入到try/catch块中。
/*-----------------------------------------------无敌分割线--------------------------------------------------*/
回到开始,完成那个程序。
客户端发送到服务端是搞定了,接下来搞定字符处理,并发送回客户端打印。
具体做法就是和客户端发送给服务器一样。除此之外,我们最好对流的操作加上lock(这是个什么东西)。
服务器:
客户端:
运行结果:
这种一对一的方式,在实际开发中几乎是不存在,这只是一个概念,一个流程,接下来会有些难度了。恩恩,今天就这样了~~~
地方、
写一个同步tcp程序,功能为,客户端发送一个字符串给服务器,服务器将字符串打印,服务器再将字符串全部转化为大写字母,发送给客户端,然后客户端接受打印
*/
参考大牛地址:http://www.tracefact.net/CSharp-Programming/Network-Programming-Part2.aspx
回忆一下编写服务端的一般步骤:
1.取得服务器的ip和端口号,创建TcpListener对象,调用Start方法开始监听。
2.利用TcpListener的AcceptTcpClient方法得到监听的客户端TcpClient对象,利用GetStream得到NetStream对象,即字节流。然后可选取其他流处理方法进行字符或者字节流处理。
3.进行通信。
4. 关闭流。关闭监听。
上代码:
服务器端:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.Net.Sockets; namespace Server { class Program { static void Main(string[] args) { const int BufferSize = 8192; Console.Write("Server is running!"); IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 }); TcpListener listener = new TcpListener(ip, 8500); listener.Start(); Console.WriteLine("服务器开始侦听"); TcpClient remoteClient = listener.AcceptTcpClient(); Console.WriteLine("Client conneted! {0}----{1}", remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint); //获得流,并且写到buffer中 NetworkStream streamToClient = remoteClient.GetStream(); byte[] buffer = new byte[BufferSize]; int bytesRead = streamToClient.Read(buffer, 0, BufferSize); Console.WriteLine("Reading data, {0} bytes。。。",bytesRead); //获得请求的字符串 string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead); Console.WriteLine("Received: {0}", msg); //按下Q退出 Console.WriteLine("按下Q键退出"); ConsoleKey key; do { key = Console.ReadKey(true).Key; } while (key != ConsoleKey.Q); } } }
客户端程序1:测试连接:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.Net.Sockets; namespace Client { class Program { static void Main(string[] args) { Console.WriteLine("Client is running:"); TcpClient client = new TcpClient(); try { client.Connect("localhost", 8500); } catch (Exception e) { Console.WriteLine(e.Message); throw; } Console.WriteLine("Server Conneted!{0} --- > {1}", client.Client.LocalEndPoint.ToString(), client.Client.RemoteEndPoint.ToString()); Console.Read(); } } }
先运行服务器,再运行客户端发现:
连接成功!!,但是,,我们发现服务器端并没有执行流处理,即int bytesRead = streamToClient.Read(buffer, 0, BufferSize);说明Read方法是一个同步方法,这里没有接受字符串,发生了阻塞。我们接下来开始发送字符串,完善客户端程序。
客户端程序二:完善版
<span style="white-space:pre"> </span>const int BufferSize = 8192; Console.Write("Server is running!"); IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 }); TcpListener listener = new TcpListener(ip, 8500); listener.Start(); Console.WriteLine("服务器开始侦听"); TcpClient remoteClient = listener.AcceptTcpClient(); Console.WriteLine("Client conneted! {0}----{1}", remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint); //获得流,并且写到buffer中 NetworkStream streamToClient = remoteClient.GetStream(); byte[] buffer = new byte[BufferSize]; //同步的方法,会阻塞,不要担心顺序问题。 int bytesRead = streamToClient.Read(buffer, 0, BufferSize); Console.WriteLine("Reading data, {0} bytes。。。", bytesRead); //获得请求的字符串 string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead); Console.WriteLine("Received: {0}", msg); //按下Q退出 Console.WriteLine("按下Q键退出"); ConsoleKey key; do { key = Console.ReadKey(true).Key; remoteClient.Close(); streamToClient.Close(); } while (key != ConsoleKey.Q);
运行结果:
恩。。看上去挺爽了,但是我在这里要强调两个问题!!!
问题1:
记得在程序结束的时候关闭流,关闭连接。
重要的重要的重要的问题2:
我在写这段代码的时候脑袋中突然出现一个问题,在服务器和客户单进行连接后,服务器和客户端的代码都在向下执行,为什么客户端写入了数据后,服务器一定就能接收到呢?为什么不是服务器先接受然后为空?过了几分钟,我明白了,NetStream中的write和read都是同步方法,都会阻塞的。。(沃日...),严格来说,与AcceptTcpClient()方法类似,这个Read()方法也是同步的,只有当客户端发送数据的时候,服务端才会读取数据、运行此方法,否则它便会一直等待。。
问题三:这个程序只能实现一个客户端,并且只能发送一条信息。
这明显是不行的,我们如何实现一个客户端,多条消息呢?
当我们需要一个服务端对同一个客户端的多次请求服务时,可以将Read()方法放入到do/while循环中。
现在,我们大致可以得出这样几个结论:
如果不使用do/while循环,服务端只有一个listener.AcceptTcpClient()方法和一个TcpClient.GetStream().Read()方法,则服务端只能处理到同一客户端的一条请求。
如果使用一个do/while循环,并将listener.AcceptTcpClient()方法和TcpClient.GetStream().Read()方法都放在这个循环以内,那么服务端将可以处理多个客户端的一条请求。
如果使用一个do/while循环,并将listener.AcceptTcpClient()方法放在循环之外,将TcpClient.GetStream().Read()方法放在循环以内,那么服务端可以处理一个客户端的多条请求。
如果使用两个do/while循环,对它们进行分别嵌套,那么结果是什么呢?结果并不是可以处理多个客户端的多条请求。因为里层的do/while循环总是在为一个客户端服务,因为它会中断在TcpClient.GetStream().Read()方法的位置,而无法执行完毕。即使可以通过某种方式让里层循环退出,比如客户端往服务端发去“exit”字符串时,服务端也只能挨个对客户端提供服务。如果服务端想执行多个客户端的多个请求,那么服务端就需要采用多线程。主线程,也就是执行外层do/while循环的线程,在收到一个TcpClient之后,必须将里层的do/while循环交给新线程去执行,然后主线程快速地重新回到listener.AcceptTcpClient()的位置,以响应其它的客户端。
对于第四种,我们在之后的博文里面讲述,现在搞下第二种和第三种。
对于第二种,我们改下服务端的代码,客户端不变:
do { TcpClient remoteClient = listener.AcceptTcpClient(); Console.WriteLine("Client conneted! {0}----{1}", remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint); //获得流,并且写到buffer中 NetworkStream streamToClient = remoteClient.GetStream(); byte[] buffer = new byte[BufferSize]; //同步的方法,会阻塞,不要担心顺序问题。 int bytesRead = streamToClient.Read(buffer, 0, BufferSize); Console.WriteLine("Reading data, {0} bytes。。。", bytesRead); //获得请求的字符串 string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead); Console.WriteLine("Received: {0}", msg); } while (true);
然后启动多个客户端,结果:
下面说第三种,要稍微改动下客户端和服务器的代码:
服务端:
do { remoteClient = listener.AcceptTcpClient(); Console.WriteLine("Client conneted! {0}----{1}", remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint); //获得流,并且写到buffer中 streamToClient = remoteClient.GetStream(); byte[] buffer = new byte[BufferSize]; //同步的方法,会阻塞,不要担心顺序问题。 int bytesRead = streamToClient.Read(buffer, 0, BufferSize); Console.WriteLine("Reading data, {0} bytes。。。", bytesRead); //获得请求的字符串 string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead); Console.WriteLine("Received: {0}", msg); } while (true);
客户端:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.Net.Sockets; namespace Client3 { class Program { static void Main(string[] args) { TcpClient client=null; NetworkStream streamToServer = null; ConsoleKey key; do { key = Console.ReadKey(true).Key; try { client = new TcpClient(); client.Connect("localhost", 8500); streamToServer = client.GetStream(); if (key == ConsoleKey.S) { Console.WriteLine("Input the message : "); string msg = Console.ReadLine(); byte[] buffer = Encoding.Unicode.GetBytes(msg); streamToServer.Write(buffer, 0, buffer.Length); Console.WriteLine("send : {0}", msg); } } catch (Exception e) { Console.WriteLine(e.Message); throw; } } while (key!=ConsoleKey.K); } } }
运行结果:
这里还需要注意一点,当客户端在TcpClient实例上调用Close()方法,或者在流上调用Dispose()方法,服务端的streamToClient.Read()方法会持续地返回0,但是不抛出异常,所以会产生一个无限循环;而如果直接关闭掉客户端,或者客户端执行完毕但没有调用stream.Dispose()或者TcpClient.Close(),如果服务器端此时仍阻塞在Read()方法处,则会在服务器端抛出异常:“远程主机强制关闭了一个现有连接”。因此,我们将服务端的streamToClient.Read()方法需要写在一个try/catch中。同理,如果在服务端已经连接到客户端之后,服务端调用remoteClient.Close(),则客户端会得到异常“无法将数据写入传输连接:
您的主机中的软件放弃了一个已建立的连接。”;而如果服务端直接关闭程序的话,则客户端会得到异常“无法将数据写入传输连接: 远程主机强迫关闭了一个现有的连接。”。因此,它们的读写操作必须都放入到try/catch块中。
/*-----------------------------------------------无敌分割线--------------------------------------------------*/
回到开始,完成那个程序。
客户端发送到服务端是搞定了,接下来搞定字符处理,并发送回客户端打印。
具体做法就是和客户端发送给服务器一样。除此之外,我们最好对流的操作加上lock(这是个什么东西)。
服务器:
const int bufferSize = 8192; TcpListener listener; TcpClient client; NetworkStream stream; IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 }); try { listener = new TcpListener(ip, 8500); listener.Start(); Console.WriteLine("开始监听"); byte[] buffer = new byte[bufferSize]; client = listener.AcceptTcpClient(); stream = client.GetStream(); lock (stream) { int readnum = stream.Read(buffer, 0, bufferSize); } string msg = Encoding.Unicode.GetString(buffer); Console.WriteLine("Msg = {0}", msg); msg = msg.ToUpper(); buffer = Encoding.Unicode.GetBytes(msg); lock (stream) { stream.Write(buffer, 0, bufferSize); } } catch (Exception) { throw; } stream.Close(); client.Close(); ConsoleKey key; do { key = Console.ReadKey(true).Key; } while (key!=ConsoleKey.Q);
客户端:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.Net.Sockets; namespace Client { class Program { //接下来我们编写客户端向服务器发送字符串的代码,与服务端类似, //他先获取连接服务器端的流,将字符串保存在缓存中,写入流这一个过程,相当于将消息发送服务端 static void Main(string[] args) { Console.WriteLine("Client is running:"); TcpClient client =null; try { client = new TcpClient(); client.Connect("localhost", 8500); } catch (Exception e) { Console.WriteLine(e.Message); throw; } //打印连接到服务器的信息 Console.WriteLine("Server Conneted!{0} --- > {1}", client.Client.LocalEndPoint.ToString(), client.Client.RemoteEndPoint.ToString()); string msg = "\"What a Big Shit\""; NetworkStream streamToServer = client.GetStream(); byte[] buffer = Encoding.Unicode.GetBytes(msg); lock (streamToServer) { streamToServer.Write(buffer, 0, buffer.Length); } Console.WriteLine("Sent: {0}", msg); lock (streamToServer) { streamToServer.Read(buffer, 0, buffer.Length); } msg = Encoding.Unicode.GetString(buffer); Console.WriteLine("转换后: {0}", msg); //按下q退出 ConsoleKey key; Console.WriteLine("按下q退出"); do { key = Console.ReadKey(true).Key; client.Close(); streamToServer.Close(); } while (key != ConsoleKey.Q); } } }
运行结果:
这种一对一的方式,在实际开发中几乎是不存在,这只是一个概念,一个流程,接下来会有些难度了。恩恩,今天就这样了~~~
地方、
相关文章推荐
- 机器学习(八)前馈神经网络
- 你可能不知道的 Linux 命令行网络监控工具
- Linux服务器上监控网络带宽的18个常用命令(转)
- HTTP协议详解
- 定时关闭华为交换机的端口
- JAVA使用apache http组件发送POST请求
- php异步http请求
- http://www.php100.com/manual/jquery/
- 网络IP转换函数
- 5种服务器网络编程模型讲解
- tcp_tw_recycle导致NAT网络TCP连接失败
- 关于网络编程中一些常用函数的理解
- php://input,$_POST,$HTTP_RAW_POST_DATA区别
- http 错误代码表
- 谈谈HttpClient使用详解
- linux0.99网络模块-数据链路层(接收)
- iOS 判断网络类型
- 【转】HBase技术介绍 转载自 http://www.searchtb.com/2011/01/understanding-hbase.html
- Ubuntu安装的flask上做一个简单的网络配置
- 使用AsyncHttpClient碰到的问题及解决方法