Socket的TCP通訊
一、 socket的通訊原理
服務(wù)器端的步驟如下。
(1)建立服務(wù)器端的Socket,開始偵聽整個網(wǎng)絡(luò)中的連接請求。
(2)當(dāng)檢測到來自客戶端的連接請求時,向客戶端發(fā)送收到連接請求的信息,并建立與客戶端之間的連接。
(3)當(dāng)完成通信后,服務(wù)器關(guān)閉與客戶端的Socket連接。
客戶端的步驟如下。
(1)建立客戶端的Socket,確定要連接的服務(wù)器的主機(jī)名和端口。
(2)發(fā)送連接請求到服務(wù)器,并等待服務(wù)器的回饋信息。
(3)連接成功后,與服務(wù)器進(jìn)行數(shù)據(jù)的交互。
(4)數(shù)據(jù)處理完畢后,關(guān)閉自身的Socket連接。
二、 socket的通訊方式
socket通訊方式有兩種:同步和異步
同步工作方式:
用TCP協(xié)議進(jìn)行編程時程序執(zhí)行到發(fā)送、接收和監(jiān)聽語句的時候,在未完成工作前不再繼續(xù)往下執(zhí)行,即處于阻塞狀態(tài),直到該語句完成某個工作后才繼續(xù)執(zhí)行下一條語句。
異步工作方式
程序執(zhí)行到發(fā)送、接收和監(jiān)聽語句的時候,不論工作是否完成,都會繼續(xù)往下執(zhí)行。
三、 socket的C#實(shí)現(xiàn)
服務(wù)端客戶端通信
在與服務(wù)端的連接建立以后,我們就可以通過此連接來發(fā)送和接收數(shù)據(jù)。端口與端口之間以流(Stream)的形式傳輸數(shù)據(jù),因?yàn)閹缀跞魏螌ο蠖伎梢员4娴搅髦?,所以?shí)際上可以在客戶端與服務(wù)端之間傳輸任何類型的數(shù)據(jù)。對客戶端來說,往流中寫入數(shù)據(jù),即為向服務(wù)器傳送數(shù)據(jù);從流中讀取數(shù)據(jù),即為從服務(wù)端接收數(shù)據(jù)。對服務(wù)端來說,往流中寫入數(shù)據(jù),即為向客戶端發(fā)送數(shù)據(jù);從流中讀取數(shù)據(jù),即為從客戶端接收數(shù)據(jù)。
服務(wù)端:
(1)服務(wù)端對端口進(jìn)行偵聽:
服務(wù)器端建立一個socket,設(shè)置好本機(jī)的ip和監(jiān)聽的端口與socket進(jìn)行綁定,開始監(jiān)聽連接請求,當(dāng)接收到連接請求后,發(fā)送確認(rèn),同客戶端建立連接,開始與客戶端進(jìn)行通信。
(2)檢測來自客戶端的連接請求
TcpClient remoteClient = listener.AcceptTcpClient(); |
//接收客戶端 這里體現(xiàn)了同步的含義,如果客戶端對該服務(wù)端發(fā)起連接的時候,程序在這里就會等待(阻塞),直到有客戶端的連接請求為止
(3)建立和連接的客戶端的數(shù)據(jù)流(傳輸數(shù)據(jù))
NetworkStream streamToClient = remoteClient.GetStream(); |
該數(shù)據(jù)流只要是用來接收和發(fā)送數(shù)據(jù),同步也分多客戶端和單個客戶端,如果分的詳細(xì)一點(diǎn)的話,還有客戶端的一條以及多條數(shù)據(jù)的情況,如果是單個客戶端的多條數(shù)據(jù)的話,連接客戶端之后,在建立數(shù)據(jù)流的前面添加一個循環(huán)就可以了,如果是多個客戶端的話,在(2)前面加個循環(huán)就可以了。為了接收數(shù)據(jù)的效率,建議不管是同步還是異步,服務(wù)端都做成線程,詳細(xì)見Demo
(4)接收客戶端發(fā)送過來的數(shù)據(jù)(用緩存來接收)
byte [] buffer = new byte [BufferSize]; // BufferSize為緩存的大小 int bytesRead; try { lock (streamToClient) //為了保證數(shù)據(jù)的完整性以及安全性 鎖定數(shù)據(jù)流 { bytesRead = streamToClient.Read(buffer, 0, BufferSize); } |
(5)向連接的客戶端發(fā)送數(shù)據(jù)
lock (streamToClient) { streamToClient.Write(buffer, 0, buffer.Length); //buffer為發(fā)送的字符數(shù)組 } |
(6)釋放數(shù)據(jù)流和TcpClient(以便下次的數(shù)據(jù)以及客戶端的收發(fā))
streamToClient.Dispose(); //釋放數(shù)據(jù)流中的數(shù)據(jù) remoteClient.Close(); //釋放TcpClient實(shí)例 |
客戶端
(1) 連接服務(wù)器
TcpClient tcp = new TcpClient(); tcp.Connect(IP,Port); //根據(jù)服務(wù)器的IP地址和偵聽的端口連接 if (tcp.Connected) { //連接成功的消息機(jī)制 詳細(xì)見DEMO ShowGetData( "成功連接上了服務(wù)器:" , this .strIP.Text.ToString()); } |
這里需要注意的是,不管是使用有參數(shù)的構(gòu)造函數(shù)與服務(wù)器連接,或者是通過Connect()方法與服務(wù)器建立連接,都是同步方法(或者說是阻塞的,英文叫block)。它的意思是說,客戶端在與服務(wù)端連接成功、從而方法返回,或者是服務(wù)端不存、從而拋出異常之前,是無法繼續(xù)進(jìn)行后繼操作的。這里還有一個名為BeginConnect()的方法,用于實(shí)施異步的連接,這樣程序不會被阻塞,可以立即執(zhí)行后面的操作,這是因?yàn)榭赡苡捎诰W(wǎng)絡(luò)擁塞等問題,連接需要較長時間才能完成。網(wǎng)絡(luò)編程中有非常多的異步操作,凡事都是由簡入難,關(guān)于異步操作,我們后面再討論,現(xiàn)在只看同步操作。
(2) 建立連接服務(wù)端的數(shù)據(jù)流
NetworkStream streamToServer = tcp.GetStream(); |
(3) 接收和發(fā)送數(shù)據(jù)
//發(fā)送字符串 byte [] buffer = Encoding.Unicode.GetBytes(msg); //msg為發(fā)送的字符串 try { lock (streamToServer) { streamToServer.Write(buffer, 0, buffer.Length); // 發(fā)往服務(wù)器 } //接收字符串 buffer = new byte [BufferSize]; lock (streamToServer) { bytesRead = streamToServer.Read(buffer, 0, BufferSize); } } |
相對于同步,異步中的連接,接收和發(fā)送數(shù)據(jù)的方法都不一樣,都有一個回調(diào)函數(shù),就是即使不能連接或者接收不到數(shù)據(jù),程序還是會一直執(zhí)行下去,如果連接上了或者接到數(shù)據(jù),程序會回到這個回調(diào)函數(shù)的地方重新往下執(zhí)行。詳細(xì)見下面:
服務(wù)器:
1、 開啟偵聽接口
private TcpListener listener; //監(jiān)聽類 listener = new TcpListener( new IPEndPoint(IPAddress.Parse(ip), port)); listener.Start(); //開啟偵聽,對連接的客戶端的數(shù)目沒有限制 或者 listener.Start( int i); // 開啟偵聽,最多只能連接i個客戶端數(shù)目 |
2、 接收客戶端
listener.BeginAcceptSocket(clientConnect, listener); //異步接受客戶端的連接請求 clientConnect為連接的回調(diào)函數(shù) /// <summary> /// 接收回調(diào)函數(shù) /// </summary> /// <param name="ar"></param> private void clientConnect(IAsyncResult ar) { try { TcpListener listener = (TcpListener)ar.AsyncState; //接受客戶的連接,得到連接的Socket Socket client = listener.EndAcceptSocket(ar); } catch { } } |
3、 接收客戶端發(fā)送的數(shù)據(jù)
/// <summary> /// 異步接收數(shù)據(jù) /// </summary> private void receiveData(Socket client) { // 調(diào)用異步方法 BeginReceive 來告知 socket 如何接收數(shù)據(jù) IAsyncResult iar = client.BeginReceive(buffer, 0, BagSize, SocketFlags.None, out errorCode, receiveCallback, buffer); } } /// <summary> /// 接收數(shù)據(jù)回調(diào)函數(shù) /// </summary> /// <param name="ar"></param> private void receiveCallback(IAsyncResult ar) { //接收到的數(shù)據(jù)長度. int receLen = 0; try { receLen = client.EndReceive(ar, out errorCode); if (receLen > 0) { OnReceiveData(client); //接收到數(shù)據(jù)之后的處理函數(shù) } } catch { } } else { } } |
4、接收成功之后,回發(fā)數(shù)據(jù)給客戶端
/// <summary> /// 異步發(fā)送報(bào)文 /// </summary> /// <param name="data"></param> private void OnReceiveData (Socket socket) { string strLogin = “succeed recived”; byte [] data = Encoding.ASCII.GetBytes(strLogin); socket.BeginSend(data, 0, data.Length, SocketFlags.None, out errorCode, sendCallBack, socket); //異步發(fā)送數(shù)據(jù) } else { } } /// <summary> /// 異步發(fā)送回調(diào)事件 /// </summary> /// <param name="ar"></param> private void sendCallBack(IAsyncResult ar) { socket.EndSend(ar, out errorCode); } |
客戶端
1、連接服務(wù)器
private TcpClient tcpcz = null tcpcz = new TcpClient() tcpcz.BeginConnect(ipaddress, Convert.ToInt32(port), new AsyncCallback(ConnectCallback), tcpcz); //根據(jù)服務(wù)器的IP地址和端口號 異步連接服務(wù)器 /// <summary> /// 異步連接的回調(diào)函數(shù) /// </summary> /// <param name="ar"></param> private void ConnectCallback(IAsyncResult ar) { TcpClient t = (TcpClient)ar.AsyncState; try { if (t.Connected) { t.EndConnect(ar); //函數(shù)運(yùn)行到這里就說明連接成功 } else { } } catch () { } } |
2、發(fā)送和接收字符串
NetworkStream stream = tcp.GetStream(); //創(chuàng)建于服務(wù)器連接的數(shù)據(jù)流 //發(fā)送字符串 string strLogin = “ this is socket example”; byte [] data = Encoding.ASCII.GetBytes(strLogin); stream.BeginWrite(data, 0, data.Length, new AsyncCallback(SendCallback),stream); //異步發(fā)送數(shù)據(jù) //接收字符串 byte [] result = new byte [tcp.Available]; // tcp.Available為接受的字符串大小 try { stream.BeginRead(result, 0, result.Length, new AsyncCallback(ReadCallback), stream); //異步接受服務(wù)器回報(bào)的字符串 } catch { } string strResponse = Encoding.ASCII.GetString(result).Trim(); //從服務(wù)器接受到的字符串 } } catch () { } } |
以上是這一段時間對socket的一些心得,還在不斷學(xué)習(xí)中,如果上面的講解有什么不到位的或者錯誤的,可以交流一下。