转自:http://www.2cto.com/kf/201206/134841.html
本例子写了个简单的TCP数据传送功能。
没有使用BinaryWriter,BinaryReader,而是使用NetworkStream的Read和Write功能,同样这个也可以通过Socket的实现。
发送端程序:
[csharp]
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5. using System.Net;
6. using System.Net.Sockets;
7. using System.IO;
8.
9. namespace Client
10. {
11. class Program
12. {
13. TcpClient tcpClient;
14. int port = 4444;
15. IPAddress serverIP;
16. NetworkStream ns;
17.
18. static void Main(string[] args)
19. {
20.
21. Program tcpConn = new Program();
22. tcpConn.Connect();
23. Console.ReadKey();
24. }
25.
26. private void Connect()
27. {
28. serverIP = IPAddress.Parse("10.108.13.27");
29. tcpClient = new TcpClient();
30. tcpClient.Connect(serverIP, port);
31. if (tcpClient!=null)
32. {
33. ns = tcpClient.GetStream();
34. //for (int i = 0; i < 10;i++ )
35. //{
36. // 发送数据
37. byte[] sendbyte = Encoding.Unicode.GetBytes("远看山有色");
38. ns.Write(sendbyte, 0, sendbyte.Length);
39. sendbyte = Encoding.Unicode.GetBytes("遥知不是雪");
40. ns.Write(sendbyte, 0, sendbyte.Length);
41. sendbyte = Encoding.Unicode.GetBytes("为有暗香来");
42. ns.Write(sendbyte, 0, sendbyte.Length);
43. //}
44.
45. }
46. }
47. }
48. }
这里将发送端程序的循环发送注释掉了,也就是只发送三行数据。
接收端的程序:
[csharp]
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5. using System.Net;
6. using System.Net.Sockets;
7. using System.IO;
8. using System.Threading;
9.
10. namespace Server
11. {
12. class Program
13. {
14. TcpClient tcpClient;
15. TcpListener tcpListener;
16. IPAddress serverIP;
17. int port = 4444;
18. NetworkStream networkStream;
19.
20. static void Main(string[] args)
21. {
22. Program tcpConnect = new Program();
23. tcpConnect.listenTCPClient();
24. }
25.
26. private void listenTCPClient()
27. {
28. Console.WriteLine("开始监听:");
29. serverIP = IPAddress.Parse("10.108.13.27");
30. tcpListener = new TcpListener(serverIP, port);
31. tcpListener.Start();
32. try
33. {
34. while (true)
35. {
36. tcpClient = tcpListener.AcceptTcpClient();
37. if (tcpClient != null)
38. {
39. Console.WriteLine("接收到客户端连接!");
40. networkStream = tcpClient.GetStream();
41. Thread tcpClientThread = new Thread(new ParameterizedThreadStart(tcpReceive));
42. tcpClientThread.IsBackground = true;
43. tcpClientThread.Start(networkStream);
44. }
45. }
46.
47. }
48. catch (System.Exception ex)
49. {
50. //
51. Console.WriteLine("监听出现错误");
52. }
53. }
54.
55. private void tcpReceive(object obj)
56. {
57. NetworkStream ns = obj as NetworkStream;
58. // 循环接收客户端发送来的消息
59. while (true)
60. {
61. byte[] msg=new byte[128];
62. int count = ns.Read(msg, 0, 128);
63. string info = Encoding.Unicode.GetString(msg);
64. Console.WriteLine("接收到客户端发来:");
65. Console.WriteLine(info);
66. Console.WriteLine("\n");
67. }
68. }
69. }
70. }
这个时候的接收端的结果如图:
可以看出,虽然发送端连续发送了三次数据,但是接受端把这三次数据都当成一次接收了。因为发送端没有同步异步的差别,只要是发送,就直接放到数据缓冲区中,而接收端采用的是读指定的byte[]数组长度,所以只要在长度范围内都会读进来。而且这三条语句应该没有超过128个字节的数组长度。所有如果还有数据传送,应该还会读进来。
修改程序连续发送很多数据:
发送程序修改:
[csharp]
1. if (tcpClient!=null)
2. {
3. ns = tcpClient.GetStream();
4. for (int i = 0; i < 10;i++ )
5. {
6. // 发送数据
7. byte[] sendbyte = Encoding.Unicode.GetBytes("远看山有色");
8. ns.Write(sendbyte, 0, sendbyte.Length);
9. sendbyte = Encoding.Unicode.GetBytes("遥知不是雪");
10. ns.Write(sendbyte, 0, sendbyte.Length);
11. sendbyte = Encoding.Unicode.GetBytes("为有暗香来");
12. ns.Write(sendbyte, 0, sendbyte.Length);
13. }
14.
15. }
添加了循环发送语句,这样总共发送的数据肯定会超过128字节。
接收端的接收情况如图:
可以看到,接收到每次要读满128字节的缓冲区才进行显示。
这里的情况和http://www.2cto.com/kf/201206/134840.html这里提到的接收端等待发送端数据不一样,因为在那篇中用的BinaryWriter,BinaryReader来操作网络数据流,每次写入一个字符串,都会在写入的字符串的前面添加字符串的长度。这样每次读一个字符串的时候就会根据前面的长度来读取缓冲区的数据,如果缓冲区数据不够的话则进行阻塞等待数据,这里没有那么使用,所以不会进行阻塞,只要缓冲区有数据就可以读,当然,如果缓冲区没有数据还是会阻塞的。
修改程序,说明当缓冲区没有数据的时候接受会阻塞。
只需要注释掉发送方的发送语句:
[csharp]
1. for (int i = 0; i < 10;i++ )
2. {
3. //// 发送数据
4. //byte[] sendbyte = Encoding.Unicode.GetBytes("远看山有色");
5. //ns.Write(sendbyte, 0, sendbyte.Length);
6. //sendbyte = Encoding.Unicode.GetBytes("遥知不是雪");
7. //ns.Write(sendbyte, 0, sendbyte.Length);
8. //sendbyte = Encoding.Unicode.GetBytes("为有暗香来");
9. //ns.Write(sendbyte, 0, sendbyte.Length);
10. }
这样,当运行服务器和客户端的时候,会有这样的结果:
可以看到,客户端没有发送数据过来,服务器端等待数据阻塞了,因为如果没有阻塞,会不停的输出空行,因为接收循环中存在这样一条 语句。
下面修改程序,让发送方进行发送延迟,看看接收方的反应。
修改的客户端程序如下:
[csharp]
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5. using System.Net;
6. using System.Net.Sockets;
7. using System.IO;
8. using System.Threading;
9.
10. namespace Client
11. {
12. class Program
13. {
14.
15.
16. static void Main(string[] args)
17. {
18. Connect();
19. Console.ReadKey();
20. }
21.
22. static void Connect()
23. {
24. TcpClient tcpClient;
25. int port = 4444;
26. IPAddress serverIP;
27. NetworkStream ns;
28.
29.
30. serverIP = IPAddress.Parse("10.108.13.27");
31. tcpClient = new TcpClient();
32. tcpClient.Connect(serverIP, port);
33. if (tcpClient!=null)
34. {
35. ns = tcpClient.GetStream();
36. for (int i = 0; i < 3;i++ )
37. {
38. // 发送数据
39. byte[] sendbyte = Encoding.Unicode.GetBytes("远看山有色");
40. ns.Write(sendbyte, 0, sendbyte.Length);
41. sendbyte = Encoding.Unicode.GetBytes("遥知不是雪");
42. ns.Write(sendbyte, 0, sendbyte.Length);
43. sendbyte = Encoding.Unicode.GetBytes("为有暗香来");
44. ns.Write(sendbyte, 0, sendbyte.Length);
45. Thread.Sleep(2000);
46. }
47.
48. }
49. }
50. }
51. }
进行了三次发送,每次发送之后睡眠2秒。
看一下接收端的情况:
只有第一次的接收结果是正确的,其他的都是错误的。这是为什么呢?我也不明白具体为什么,还希望有能能给出明确解答。不过我个人认为,可能是在发送方休眠后再次发送数据的时候造成了混乱,解决这个问题的办法就是在发送之前添加一个发送长度的说明,接收方可以根据接收到的长度进行读取缓冲区。
然后修改程序,给接收方添加延时进行测试:
[csharp]
1. private void tcpReceive(object obj)
2. {
3. NetworkStream ns = obj as NetworkStream;
4. // 循环接收客户端发送来的消息
5. while (true)
6. {
7. byte[] msg=new byte[128];
8. int count = ns.Read(msg, 0, 128);
9. string info = Encoding.Unicode.GetString(msg);
10. Console.WriteLine("接收到客户端发来:");
11. Console.WriteLine(info);
12. Console.WriteLine("\n");
13. Thread.Sleep(2000);
14. }
15. }
这里出现了有意思的事情,让客户端延迟500ms,服务器延迟2000毫秒,这样服务器可以充分的等待客户端数据,可以看到第一次接受到了客户端的第一次发送的数据,而第二次接收到了客户端第二次和第三次发送的数据,因为这两次的数据加起来没有超过缓冲区大小。
再次修改程序,调整延迟时间,将客户端延迟设为1000ms,服务器延迟设为500ms,这样可能客户端的数据还没有发送来,服务器就读取了数据。
结果如图:
可见,在第二次发送还没有完全发送过来,服务器就读取了一次缓冲区。
由上面的分析可见,在进行TCP同步传送数据的开发过程中,让服务器和客户端进行搭配的传送数据的过程非常重要。然而, 自己手动控制传送数据大小还有延迟在调试过程中非常麻烦,而且可能效果很差,会发生不可控制的效果。最好的方法就是用BinaryWriter,BinaryReader控制数据的发送和接收,以前我没有用过这种方式,但是使用以后发现太方便了。因为BinaryWriter在发送一个字符串之前,会计算字符串的长度,将长度添加到字符串的前面,BinaryReader在接收的时候会根据提起的前导字符串长度进行读取缓冲区,如果长度不够,则进入数据接收阻塞,知道等到有足够的数据长度可以读取。
下面修改程序,利用BinaryWriter,BinaryReader操作数据的传送。
客户端程序:
[csharp]
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5. using System.Net;
6. using System.Net.Sockets;
7. using System.IO;
8. using System.Threading;
9.
10. namespace Client
11. {
12. class Program
13. {
14.
15.
16. static void Main(string[] args)
17. {
18. Connect();
19. Console.ReadKey();
20. }
21.
22. static void Connect()
23. {
24. TcpClient tcpClient;
25. int port = 4444;
26. IPAddress serverIP;
27. NetworkStream ns;
28. BinaryWriter bw;
29.
30. serverIP = IPAddress.Parse("10.108.13.27");
31. tcpClient = new TcpClient();
32. tcpClient.Connect(serverIP, port);
33. if (tcpClient!=null)
34. {
35. ns = tcpClient.GetStream();
36. bw = new BinaryWriter(ns);
37. //for (int i = 0; i < 3;i++ )
38. //{
39. bw.Write("轻轻地我走了,正如我轻轻地来!");
40. bw.Flush();
41. bw.Write("我挥一挥衣袖,不带走一片云彩!");
42. bw.Flush();
43. bw.Write("那河畔的金柳,是夕阳中的新娘,波光里的艳影,在我的心头荡漾。");
44. bw.Flush();
45. //Thread.Sleep(1000);
46. //}
47.
48. }
49. }
50. }
51. }
服务器端程序:
[csharp]
1. using System;
2. using System.Collections.Generic;
3. using System.Linq;
4. using System.Text;
5. using System.Net;
6. using System.Net.Sockets;
7. using System.IO;
8. using System.Threading;
9.
10. namespace Server
11. {
12. class Program
13. {
14. TcpClient tcpClient;
15. TcpListener tcpListener;
16. IPAddress serverIP;
17. int port = 4444;
18. NetworkStream networkStream;
19. BinaryReader br;
20.
21. static void Main(string[] args)
22. {
23. Program tcpConnect = new Program();
24. tcpConnect.listenTCPClient();
25. }
26.
27. private void listenTCPClient()
28. {
29. Console.WriteLine("开始监听:");
30. serverIP = IPAddress.Parse("10.108.13.27");
31. tcpListener = new TcpListener(serverIP, port);
32. tcpListener.Start();
33. try
34. {
35. while (true)
36. {
37. tcpClient = tcpListener.AcceptTcpClient();
38. if (tcpClient != null)
39. {
40. Console.WriteLine("接收到客户端连接!");
41. networkStream = tcpClient.GetStream();
42. Thread tcpClientThread = new Thread(new ParameterizedThreadStart(tcpReceive));
43. tcpClientThread.IsBackground = true;
44. tcpClientThread.Start(networkStream);
45. }
46. }
47.
48. }
49. catch (System.Exception ex)
50. {
51. //
52. Console.WriteLine("监听出现错误");
53. }
54. }
55.
56. private void tcpReceive(object obj)
57. {
58. NetworkStream ns = obj as NetworkStream;
59. br = new BinaryReader(ns);
60. // 循环接收客户端发送来的消息
61. while (true)
62. {
63. string msg = br.ReadString();
64. Console.WriteLine(msg);
65. }
66. }
67. }
68. }
服务器端运行结果:
可以看到,通过BinaryWriter, BinaryReader发送数据和接收数据,都是获得正确的结果。
然后修改程序,增加循环发送,并增加发送延迟进行测试。
客户端延迟调整:
[csharp]
1. for (int i = 0; i < 3;i++ )
2. {
3. bw.Write("轻轻地我走了,正如我轻轻地来!");
4. bw.Flush();
5. bw.Write("我挥一挥衣袖,不带走一片云彩!");
6. bw.Flush();
7. bw.Write("那河畔的金柳,是夕阳中的新娘,波光里的艳影,在我的心头荡漾。");
8. bw.Flush();
9. Thread.Sleep(1000);
10. }
接收端接收结果:
同样的,给服务器添加延迟:
[csharp]
1. private void tcpReceive(object obj)
2. {
3. NetworkStream ns = obj as NetworkStream;
4. br = new BinaryReader(ns);
5. // 循环接收客户端发送来的消息
6. while (true)
7. {
8. string msg = br.ReadString();
9. Console.WriteLine(msg);
10. Thread.Sleep(500);
11. }
12. }
运行结果:
同样是正确的输出,然后再次调整延迟,让客户端延迟2000ms,服务器延迟500ms,运行结果:
还是正确的。
可以看到,BinaryWriter和BinaryReader可以在很大程度上帮助我们进行网络通信的编程操作。