異步這個詞以前在課堂上也聽過,那時候只是聽,直到在做項目的時候用到多線程,在體會到異步是怎樣的,到最近做的東西對異步更加深刻了,進程通信時調Windows API SendMessage和PostMessage的區別。最近搞的Socket編程也是有異步的,Socket當然要有異步才行,不然服務端Accept一次就卡一次在那里,客戶端Connect一次就卡一次。每Send一次,Receive一次都會卡一次,這樣不好。
在網上谷歌過一下,發現Socket的異步可以有兩種方式,一種是用 SocketAsyncEventArgs 配合AcceptAsync,SendAsync,ReceiveAsync,ConnectAsync等方法實現的。通信要用到的二進制數組byte[]就通過SocketAsyncEventArgs實例的的SetBuffer方法設置,在網上看見有人評價SocketAsyncEventArgs很占用資源,在一個通訊過程中,要用到多個SocketAsyncEventArgs的實例,當初以為SocketAsyncEventArgs比BeginXXX浪費資源,所以我就用另一種方式了。用BeginXXX / EndXXX 方法的 。后來有位員友指點,BeginXXX反而是浪費資源的每次必須創建一個IAsyncResult ,而SocketAsyncEventArgs是可以提供重復使用的,不需要每次創建。想了想的確是這樣。
BeginXXX / EndXXX 的方式用到的是AsyncCallback委托異步調用一些自己定義的方法。此外BeginXXX / EndXXX 方法的一個重載可以傳入一個Object類型的參數,這樣可以把一些需要用到的對象傳進去,在方法內部,通過IAsyncResult類型的參數的 AsyncState 屬性把那個Object類型的參數取出來。
通過這次的學習還知道了數據包過大和粘包要處理,之前同步的時候也會有這個情況的,只是當時不知道沒考慮到這個情況。
正如上面所說的,通信過程會遇到數據包過大,因此異步接收會只是一次就能接收完,需要有地方存儲那些已接收的數據,下面先定義一個數據結構
1 class ConnectInfo 2 { 3 public byte[] buffers; 4 public Socket clientSocket; 5 public Socket serverSocket; 6 public ArrayList tmpAl; 7 }
這個數據結構中buffers就用來存放最終接收完的完整數據,tempAl是暫時存放已經接收的數據,其余兩個Socket的不用說了。
另外定義兩個變量
1 static int BUFFER_SIZE = 1024; 2 private static SocketError _socketError;
上面的BUFFER_SIZE是一次接收的數據量,另一個SocketError的是在異步通信時用到的。
Main方法里的代碼,跟往常的差別不大,在Accept方面就調用BeginAccept方法。傳入的State參數是serverSocket,服務端的Socket。在客戶端Connet之后還需要用這個Socket實例。
1 IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 8081);//本機預使用的IP和端口 2 Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 3 serverSocket.Bind(ipep);//綁定 4 serverSocket.Listen(10);//監聽 5 Console.WriteLine("waiting for a client"); 6 7 serverSocket.BeginAccept(AsyncAccept, serverSocket); 8 Console.ReadLine();
服務端Accept到客戶端的Connect之后,就會調用到下面這個方法,也就是上面BeginAccept方法的第一個參數的那個方法名的方法
1 static void AsyncAccept(IAsyncResult e) 2 { 3 Socket serverSocket = e.AsyncState as Socket; 4 if (serverSocket == null) return; 5 Socket clientSocket = serverSocket.EndAccept(e);//結束這次異步Accept 6 7 ConnectInfo info = new ConnectInfo(); 8 info.buffers = new byte[BUFFER_SIZE]; 9 info.tmpAl = new ArrayList(BUFFER_SIZE); 10 info.clientSocket = clientSocket; 11 info.serverSocket = serverSocket; 12 13 //異步發送消息 14 clientSocket.BeginSend(Encoding.ASCII.GetBytes("welocome"),0,8, SocketFlags.None, new AsyncCallback(AsyncSend), info); 15 //異步接收消息 16 clientSocket.BeginReceive(info.buffers, 0, info.buffers.Length, 0, out _socketError, AsyncReceive, info); 17 }
e.AsyncState as Socket就是樣取回BeginAccept傳進來的serverSocket。調用這類BeginXXX的方法,都會在與AsyncCallBack委托綁定的方法里調用一次EndXXX方法。這里就可以開始設置通信了,都是異步的,所以都調用BeginXXX的方法,但接收的方法BeginReceive還需要傳入一個byte[]的buffer放接收到的數據,在接收到消息之后還需要對這些數據作處理,又要用到clientSocket,而State參數只有一個,所以這里才需要定義一個數據結構把這些對象集合起來傳到相應的回調方法里頭。
接下來是異步發送和異步接收的回調方法。
1 static void AsyncReceive(IAsyncResult e) 2 { 3 ConnectInfo info = e.AsyncState as ConnectInfo; 4 if (info == null) return; 5 Socket clientSocket = info.clientSocket; 6 //暫存本次接受時接收到的數據量 7 int bytesRead = 0; 8 try 9 { 10 //終止本次異步接收 11 bytesRead = clientSocket.EndReceive(e,out _socketError); 12 if (_socketError == SocketError.Success) 13 { 14 if (bytesRead > 0) 15 { 16 //因未知本次接收的數據是客戶端發來數據的 17 //那一部分,因此暫時建一個byte[]把它提取出來, 18 //存放到ArrayList里面去,等到所有數據都接收完時統一取出 19 byte[] tmpbytes = new byte[bytesRead]; 20 Array.Copy(info.buffers, 0, tmpbytes, 0, bytesRead); 21 info.tmpAl.AddRange(tmpbytes); 22 23 24 //查看還有沒有數據沒讀完,有則用建一個新的buffer,再次進行異步讀取 25 if (clientSocket.Available > 0) 26 { 27 //已知還有多少數據還沒讀取,就按數據量的大小 28 //新建buffer,這樣子就可以減少循環讀取的次數了 29 info.buffers = new byte[clientSocket.Available]; 30 clientSocket.BeginReceive(info.buffers, 0, clientSocket.Available, 0, out _socketError, new AsyncCallback(AsyncReceive), info); 31 } 32 //數據已經讀取完了,接下來就按積累下來的所有數據 33 //量新建一個byte[],把所有的數據一次取出來,同時把暫 34 //存數據的ArrayList清空,以備下次使用 35 else 36 { 37 byte[] endreceive = new byte[info.tmpAl.Count]; 38 info.tmpAl.CopyTo(endreceive); 39 int recLength=info.tmpAl.Count; 40 info.tmpAl.Clear(); 41 string content = Encoding.ASCII.GetString(endreceive); 42 Array.Clear(endreceive, 0, endreceive.Length); 43 44 //把數據稍作處理,回傳一些消息給客戶端。 45 string senddata = string.Format("rec={0}\r\nreceivedata={1}",recLength , content); 46 47 if (!string.IsNullOrEmpty(senddata)) 48 { 49 if (senddata.Length > BUFFER_SIZE) senddata = senddata.Substring(0, BUFFER_SIZE); 50 //Send(handler, senddata); 51 byte[] data = Encoding.ASCII.GetBytes(senddata); 52 info.clientSocket.BeginSend(data, 0, data.Length, 0, out _socketError,AsyncSend ,info); 53 } 54 //再次調用異步接收以接收下一條客戶端 55 //發來的消息,繼續跟客戶端通信過程 56 Array.Clear(info.buffers, 0, info.buffers.Length); 57 clientSocket.BeginReceive(info.buffers, 0, BUFFER_SIZE, 0, out _socketError, new AsyncCallback(AsyncReceive), info); 58 } 59 } 60 } 61 } 62 catch(Exception ex) 63 { 64 clientSocket.BeginReceive(info.buffers, 0, BUFFER_SIZE, 0, out _socketError, new AsyncCallback(AsyncReceive), info); 65 } 66 67 68 }
下面這個則是發送消息的回調方法
1 static void AsyncSend(IAsyncResult e) 2 { 3 ConnectInfo info = e.AsyncState as ConnectInfo; 4 if (info == null) return; 5 Socket clientSocket = info.clientSocket; 6 clientSocket.EndSend(e, out _socketError); 7 }
發送倒是比接收的簡單,因為不用判斷數據是否讀取完畢,少了粘包的情況。
客戶端的代碼就不放了,大致上也跟服務端的差不多,客戶端用同步或異步也沒啥問題,經過了這次的學習,發現無論同步和異步,都得對粘包的情況進行處理。
還是那句話,本人的對Socket編程理解和了解尚淺,上面有什么說錯的請各位指出,有什么說漏的,請各位提點,多多指導。謝謝!
最后附上源碼

1 class ConnectInfo 2 { 3 public byte[] buffers; 4 public Socket clientSocket; 5 public Socket serverSocket; 6 7 public ArrayList tmpAl; 8 } 9 10 class Program 11 { 12 static int BUFFER_SIZE = 1024; 13 14 private static SocketError _socketError; 15 16 /// <summary> 17 /// Server 18 /// </summary> 19 /// <param name="args"></param> 20 static void Main(string[] args) 21 { 22 IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 8081);//本機預使用的IP和端口 23 Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 24 newsock.Bind(ipep);//綁定 25 newsock.Listen(10);//監聽 26 Console.WriteLine("waiting for a client"); 27 28 newsock.BeginAccept(AsyncAccept, newsock); 29 Console.ReadLine(); 30 } 31 32 static void AsyncAccept(IAsyncResult e) 33 { 34 Socket serverSocket = e.AsyncState as Socket; 35 if (serverSocket == null) return; 36 Socket clientSocket = serverSocket.EndAccept(e); 37 38 ConnectInfo info = new ConnectInfo(); 39 info.buffers = new byte[BUFFER_SIZE]; 40 info.tmpAl = new ArrayList(BUFFER_SIZE); 41 info.clientSocket = clientSocket; 42 info.serverSocket = serverSocket; 43 clientSocket.BeginSend(Encoding.ASCII.GetBytes("welocome"),0,8, SocketFlags.None, new AsyncCallback(AsyncSend), info); 44 clientSocket.BeginReceive(info.buffers, 0, info.buffers.Length, 0, out _socketError, AsyncReceive, info); 45 } 46 47 static void AsyncSend(IAsyncResult e) 48 { 49 ConnectInfo info = e.AsyncState as ConnectInfo; 50 if (info == null) return; 51 Socket clientSocket = info.clientSocket; 52 clientSocket.EndSend(e, out _socketError); 53 } 54 55 static void AsyncReceive(IAsyncResult e) 56 { 57 ConnectInfo info = e.AsyncState as ConnectInfo; 58 if (info == null) return; 59 Socket clientSocket = info.clientSocket; 60 61 int bytesRead = 0; 62 try 63 { 64 bytesRead = clientSocket.EndReceive(e,out _socketError); 65 if (_socketError == SocketError.Success) 66 { 67 if (bytesRead > 0) 68 { 69 byte[] tmpbytes = new byte[bytesRead]; 70 Array.Copy(info.buffers, 0, tmpbytes, 0, bytesRead); 71 info.tmpAl.AddRange(tmpbytes); 72 73 if (clientSocket.Available > 0) 74 { 75 info.buffers = new byte[clientSocket.Available]; 76 clientSocket.BeginReceive(info.buffers, 0, clientSocket.Available, 0, out _socketError, new AsyncCallback(AsyncReceive), info); 77 } 78 else 79 { 80 byte[] endreceive = new byte[info.tmpAl.Count]; 81 info.tmpAl.CopyTo(endreceive); 82 int recLength=info.tmpAl.Count; 83 info.tmpAl.Clear(); 84 string content = Encoding.ASCII.GetString(endreceive); 85 Array.Clear(endreceive, 0, endreceive.Length); 86 87 string senddata = string.Format("rec={0}\r\nreceivedata={1}",recLength , content); 88 89 90 if (!string.IsNullOrEmpty(senddata)) 91 { 92 if (senddata.Length > BUFFER_SIZE) senddata = senddata.Substring(0, BUFFER_SIZE); 93 byte[] data = Encoding.ASCII.GetBytes(senddata); 94 info.clientSocket.BeginSend(data, 0, data.Length, 0, out _socketError,AsyncSend ,info); 95 } 96 97 Array.Clear(info.buffers, 0, info.buffers.Length); 98 clientSocket.BeginReceive(info.buffers, 0, BUFFER_SIZE, 0, out _socketError, new AsyncCallback(AsyncReceive), info); 99 } 100 } 101 } 102 } 103 catch(Exception ex) 104 { 105 clientSocket.BeginReceive(info.buffers, 0, BUFFER_SIZE, 0, out _socketError, new AsyncCallback(AsyncReceive), info); 106 } 107 108 109 } 110 }