一個簡單的自定義通訊協議(socket)

一直不知道socket通訊時候自定義數據包是什麼樣子的,偶然作了個小例子。java

 

先來講說數據包的定義,我這裏是包頭+內容 組成的:其中包頭內容分爲包類型+包長度, 那就是 消息對象=包類型+包長度+消息體apache

 

包類型 byte 型編程

包長度 int 型服務器

消息體 byte[]多線程

 

包總長度爲 1 + 4 +  消息體.getBytes().lengthapp

 

發包方法以下:框架

 

[java] view plain copysocket

  1. private void sendTextMsg(DataOutputStream out,String msg ) throws IOException {    
  2.         byte[] bytes= msg.getBytes();    
  3.         int totalLen = 1 + 4 + bytes.length;    
  4.                                 out.writeByte(1);    
  5.         out.writeInt(totalLen);    
  6.         out.write(bytes);    
  7.         out.flush();    
  8.     }    

 

 

客戶端發送消息類爲:測試

 

[java] view plain copyspa

  1. import <a href="http://lib.csdn.net/base/javaee" class='replace_word' title="Java EE知識庫" target='_blank' style='color:#df3434; font-weight:bold;'>Java</a>.io.DataOutputStream;    
  2. import java.io.IOException;    
  3. import java.io.InputStream;    
  4. import java.io.OutputStream;    
  5. import java<a href="http://lib.csdn.net/base/dotnet" class='replace_word' title=".NET知識庫" target='_blank' style='color:#df3434; font-weight:bold;'>.NET</a>.Socket;    
  6. import java<a href="http://lib.csdn.net/base/dotnet" class='replace_word' title=".NET知識庫" target='_blank' style='color:#df3434; font-weight:bold;'>.Net</a>.UnknownHostException;    
  7. import java.util.Scanner;    
  8.     
  9. public class MsgClient {    
  10.     
  11.     private DataOutputStream outs;    
  12.         
  13.     public static void main(String[] args) {    
  14.         try {    
  15.             MsgClient client = new MsgClient();    
  16.             client.connServer("127.0.0.1", 9292);               
  17.         } catch (UnknownHostException e) {    
  18.             e.printStackTrace();    
  19.         } catch (IOException e) {    
  20.             e.printStackTrace();    
  21.         }    
  22.     }    
  23.         
  24.         
  25.     private void sendTextMsg(DataOutputStream out,String msg ) throws IOException {    
  26.         byte[] bytes= msg.getBytes();    
  27.         int totalLen = 1 + 4 + bytes.length;    
  28.         out.writeByte(1);    
  29.         out.writeInt(totalLen);    
  30.         out.write(bytes);    
  31.         out.flush();    
  32.     }       
  33.         
  34.     public void connServer(String ip,int port) throws UnknownHostException, IOException {    
  35.         Socket client = new Socket(ip,port);    
  36.         InputStream in = client.getInputStream();    
  37.         OutputStream out = client.getOutputStream();    
  38.         outs = new DataOutputStream(out);    
  39.         while(true) {    
  40.             Scanner scaner = new Scanner(System.in);    
  41.             sendTextMsg(outs, "<a href="http://lib.csdn.net/base/softwaretest" class='replace_word' title="軟件測試知識庫" target='_blank' style='color:#df3434; font-weight:bold;'>測試</a>消");    
  42.         }           
  43.     }    

 

 

服務端接收類爲:

 

[java] view plain copy

  1. import java.io.DataInputStream;    
  2. import java.io.FileOutputStream;    
  3. import java.io.IOException;    
  4. import java.io.InputStream;    
  5. import java.net.ServerSocket;    
  6. import java.net.Socket;    
  7.     
  8. public class MsgServer {    
  9.     public static void main(String[] args) {            
  10.         try {    
  11.             MsgServer server = new MsgServer();    
  12.             server.setUpServer(9090);    
  13.         } catch (IOException e) {    
  14.             e.printStackTrace();    
  15.         }    
  16.     }    
  17.         
  18.     public void setUpServer(int port) throws IOException {    
  19.         ServerSocket server = new ServerSocket(port);    
  20.         while(true) {    
  21.             Socket client = server.accept();    
  22.             System.out.println("客戶端IP:"+client.getRemoteSocketAddress());    
  23.             processMesage(client);    
  24.         }    
  25.     }    
  26.         
  27.     private void processMesage(Socket client) throws IOException {    
  28.         InputStream ins = client.getInputStream();          
  29.         DataInputStream dins = new DataInputStream(ins);    
  30.         //服務端解包過程    
  31.         while(true) {    
  32.             int totalLen = dins.readInt();    
  33.             byte flag = dins.readByte();    
  34.             System.out.println("接收消息類型"+flag);    
  35.                 
  36.             byte[] data = new byte[totalLen - 4 - 1];    
  37.             dins.readFully(data);    
  38.             String msg = new String(data);    
  39.             System.out.println("發來的內容是:"+msg);      
  40.         }    
  41.     }    
  42. }    

 

 

 

這樣就基本完成了,但實際還有好多問題,好比說服務端用如何用多線程服務來完成客戶端的請求已提升效率,若是是NIO方式怎麼來實現?多個消息類型時候怎麼抽象?這些都沒有考慮

 

另外有兩個開源的框架不錯,一個是apache  mina 還有個是netty ,有機會試試。

 

 

另外一篇文章中敘述:

 

------------------

TCP Socket協議定義

------------------

本文從這裏開始,主要介紹TCP的socket編程。

新手們(例如當初的我),第一次寫socket,老是覺得在發送方壓入一個"Helloworld",接收方收到了這個字符串,就「精通」了Socket編程了。而實際上,這種編程根本不可能用在現實項目,由於:

 

1. socket在傳輸過程當中,helloworld有可能被拆分了,分段到達客戶端),例如 hello   +   world,一個分段就是一個包(Package),這個就是分包問題

 

2. socket在傳輸過成功,不一樣時間發送的數據包有可能被合併,同時到達了客戶端,這個就是黏包問題。例如發送方發送了hello+world,而接收方可能一次就接受了helloworld.

 

3. socket會自動在每一個包後面補n個 0x0 byte,分割包。具體怎麼去補,這個我就沒有深刻了解。

 

4. 不一樣的數據類型轉化爲byte的長度是不一樣的,例如int轉爲byte是4位(int32),這樣咱們在製做socket協議的時候要特別當心了。具體可使用如下代碼去測試:

代碼

        public void test()
        {
            int myInt = 1;
            byte[] bytes = new byte[1024];
            BinaryWriter writer = new BinaryWriter(new MemoryStream(bytes));
            writer.Write(myInt);
            writer.Write("j");
            writer.Close();
        }

 

 

儘管socket環境如此惡劣,可是TCP的連接也至少保證了:

  • 包發送順序在傳輸過程當中是不會改變的,例如發送方發送 H E L L,那麼接收方必定也是順序收到H E L L,這個是TCP協議承諾的,所以這點成爲咱們解決分包、黏包問題的關鍵。
  • 若是發送方發送的是helloworld, 傳輸過程當中分割成爲hello+world,那麼TCP保證了在hello與world之間沒有其餘的byte。可是不能保證helloworld和下一個命令之間沒有其餘的byte。

 

所以,若是咱們要使用socket編程,就必定要編寫本身的協議。目前業界主要採起的協議定義方式是:包頭+包體長度+包體。具體以下:

 

1. 通常包頭使用一個int定義,例如int = 173173173;做用是區分每個有效的數據包,所以咱們的服務器能夠經過這個int去切割、合併包,組裝出完整的傳輸協議。有人使用回車字符去分割包體,例如常見的SMTP/POP協議,這種作法在特定的協議是沒有問題的,但是若是咱們傳輸的信息內容自帶了回車字符串,那麼就糟糕了。因此在設計協議的時候要特別當心。

 

2. 包體長度使用一個int定義,這個長度表示包體所佔的比特流長度,用於服務器正確讀取並分割出包。

 

3. 包體就是自定義的一些協議內容,例如是對像序列化的內容(現有的系統已經很常見了,使用對象序列化、反序列化可以極大簡化開發流程,等版本穩定後再轉入手工壓入byte操做)。

 

一個實際編寫的例子:好比我要傳輸2個整型 int = 1, int = 2,那麼實際傳輸的數據包以下:

   173173173               8                  1         2

|------包頭------|----包體長度----|--------包體--------|

這個數據包就是4個整型,總長度 = 4*4  = 16。

相關文章
相關標籤/搜索