本例子寫了個簡單的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.BkJia.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可以在很大程度上幫助我們進行網絡通信的編程操作。
摘自 Watkins.Song