在正式給你們介紹自定義協議以前,咱們先對網絡傳輸和協議解析的相關知識點作一個基本的介紹,儘管這些知識點咱們在學校裏學過,但不免會有所遺忘,這裏先作一個簡單的介紹,以便對後文的內容理解更加順暢。java
OSI的7層從上到下分別是:7 應用層、 6 表示層、 5 會話層、 4 傳輸層、 3 網絡層、 2 數據鏈路層、 1 物理層;其中高層(即七、六、五、4層)定義了應用程序的功能,下面3層(即三、二、1層)主要面向經過網絡的端到端的數據流。應用層常見的協議有:HTTP、FTP、SMTP等;常見的傳輸層有:TCP、UDP。本文主要是基於TCP自定義協議實現客戶端與服務端的長鏈接。程序員
Socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員作網絡開發所用的接口,這就是Socket編程接口,一般也稱做"套接字"。套接字之間的鏈接過程能夠分爲三個步驟:客戶端請求,服務端回覆收到,客戶端收到服務端的回覆,即三次握手。鏈接成功時,應用程序兩端都會產生一個Socket實例,操做這個實例完成所需的會話。對於一個網絡鏈接來講,套接字是平等的,並無差異,不由於在服務器端或在客戶端而產生不一樣級別。編程
「位(bit)」是電子計算機中最小的數據單位。每一位的狀態只能是0或1;「字節(Byte)」由8個二進制位構成(即1byte=8bit),它是存儲空間的基本計量單位,它能表示到數值範圍爲0到255(即2的8次方減1);數組
左移運算:1<<2,1的二進制位是1,向左移兩位是100,轉爲十進制數即爲4,因此1<<2的運算結果是4;服務器
右移運算:7>>2,7的二進制位是111,向右移兩位是1,因此7>>2的運算結果是1 。網絡
byte 8位,1個字節 boolean 8位,1個字節 char 16位,2個字節 short 16位,2個字節 int 32位,4個字節 float 32位,4個字節 double 64位,8個字節 long 64位,8個字節
Socket構造函數socket
Socket(InetAddress address, int port)throws UnknownHostException, IOException函數
Socket(InetAddress address, int port, InetAddress localAddress, int localPort)throws IOException.net
Socket(String host, int port)throws UnknownHostException, IOExceptioncode
Socket(String host, int port, InetAddress localAddress, int localPort)throws IOException
還能夠經過如下方式生成socket:
SocketFactory.getDefault().createSocket(String address, String port) throws ConnectException
Socket方法
getInetAddress(); // 遠程服務端的IP地址
getPort(); // 遠程服務端的端口
getLocalAddress(); // 本地客戶端的IP地址
getLocalPort(); // 本地客戶端的端口
getInputStream(); // 得到輸入流
getOutStream(); // 得到輸出流
Socket狀態
isClosed(); // 鏈接是否已關閉,若關閉,返回true;不然返回false
isConnect(); // 若是曾經鏈接過,返回true;不然返回false
isBound(); // 若是Socket已經與本地一個端口綁定,返回true;不然返回false
判斷Socket的狀態是否處於鏈接中
boolean isConnected = socket.isConnected() && !socket.isClosed(); // 判斷當前是否處於鏈接
ServerSocket構造函數
ServerSocket()throws IOException
ServerSocket(int port)throws IOException
ServerSocket(int port, int backlog)throws IOException
ServerSocket(int port, int backlog, InetAddress bindAddr)throws IOException
服務端接收客戶端的鏈接請求:
Socket socket = serverSocket.accept();
輸入流
InputStream
抽象類,描述流的輸入
ByteArrayInputStream
從字節數組讀取的輸入流
BufferedInputStream
緩衝輸入流
FileInputStream
從文件讀入的輸入流
ObjectInputStream
對象輸入流(所讀寫的對象必須實現Serializable接口)
DataInputStream
包含了讀取Java標準數據類型的輸入流
輸出流
OutputStream
抽象類,描述流的輸入
ByteArrayOutputStream
寫入字節數組的輸出流
BufferedOutputStream
緩衝輸出流
FileOutputStream
寫入文件的輸出流
ObjectOutputStream
對象輸出流(所讀寫的對象必須實現Serializable接口)
DataOutputStream
包含了寫Java標準數據類型的輸出流
注:先運行服務端代碼的main函數,再運行客戶端代碼的main函數,便可看到打印鏈接成功
import java.net.Socket; /** * Created by meishan on 16/12/1. */ public class Client { public static void main(String[] args) throws Exception { boolean isConnected; String host = "127.0.0.1"; int port = 1122; Socket socket = null; try { socket = SocketFactory.getDefault().createSocket(host, port); isConnected = true; System.out.println("鏈接成功!"); } catch (ConnectException e) { isConnected = false; e.printStackTrace(); System.out.println("鏈接失敗!"); } if (!isConnected) { return; } Thread.sleep(5000); socket.close(); System.out.println("斷開鏈接!"); } }
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * Created by meishan on 16/12/1. */ public class Server { private int port = 1122; private ServerSocket serverSocket; public Server() throws Exception { serverSocket = new ServerSocket(port, 3);//顯式設置鏈接請求隊列的長度爲3 System.out.println("服務器啓動!"); } public void service() { while (true) { Socket socket = null; try { socket = serverSocket.accept(); System.out.println("New connection accepted " + socket.getInetAddress() + ":" + socket.getPort()); } catch (IOException e) { e.printStackTrace(); } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } } public static void main(String[] args) throws Exception { Server server = new Server(); Thread.sleep(3000); server.service(); } }
例子中,數據包的定義:消息對象=包類型+包長度+消息內容
包類型 byte 型
包長度 int 型
消息內容 byte[] 型
import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; import java.util.Scanner; /** * Created by meishan on 16/12/1. */ public class Client { public static void main(String[] args) { try { Socket client = new Socket("127.0.0.1", 9091); OutputStream out = client.getOutputStream(); DataOutputStream outs = new DataOutputStream(out); while (true) { Scanner scaner = new Scanner(System.in); genProtocol(outs, scaner.next()); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 構造協議 * * @param out * @param msg * @throws IOException */ private static void genProtocol(DataOutputStream out, String msg) throws IOException { int type = 1; //消息類型 byte[] bytes = msg.getBytes(); //消息內容 int totalLen = 1 + 4 + bytes.length; //消息長度 out.writeByte(type); //寫入消息類型 out.writeInt(totalLen); //寫入消息長度 out.write(bytes); //寫入消息內容 out.flush(); } }
import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; /** * Created by meishan on 16/12/1. */ public class Server { public static void main(String[] args) { try { ServerSocket server = new ServerSocket(9091); while (true) { Socket client = server.accept(); System.out.println("客戶端" + client.getRemoteSocketAddress() + "鏈接成功"); parseProtocol(client); } } catch (IOException e) { e.printStackTrace(); } } /** * 消息解析 * * @param client * @throws IOException */ private static void parseProtocol(Socket client) throws IOException { InputStream is = client.getInputStream(); DataInputStream dis = new DataInputStream(is); //讀取Java標準數據類型的輸入流 //協議解析 while (true) { byte type = dis.readByte(); //讀取消息類型 int totalLen = dis.readInt(); //讀取消息長度 byte[] data = new byte[totalLen - 4 - 1]; //定義存放消息內容的字節數組 dis.readFully(data); //讀取消息內容 String msg = new String(data); //消息內容 System.out.println("接收消息類型" + type); System.out.println("接收消息長度" + totalLen); System.out.println("發來的內容是:" + msg); } } }
本文簡單介紹了socket通訊和自定義協議的相關知識點,爲後續的深刻作一些準備工做,下一篇文章《基於Java Socket的自定義協議,實現Android與服務器的長鏈接(二)》將經過一個實例來詳細講解自定義協議實現長鏈接通訊。