基於Java Socket的自定義協議,實現Android與服務器的長鏈接(一)

原文地址:http://blog.csdn.net/u0108184...

1、基礎知識準備

在正式給你們介紹自定義協議以前,咱們先對網絡傳輸和協議解析的相關知識點作一個基本的介紹,儘管這些知識點咱們在學校裏學過,但不免會有所遺忘,這裏先作一個簡單的介紹,以便對後文的內容理解更加順暢。java

1. 網絡七層協議

OSI的7層從上到下分別是:7 應用層、 6 表示層、 5 會話層、 4 傳輸層、 3 網絡層、 2 數據鏈路層、 1 物理層;其中高層(即七、六、五、4層)定義了應用程序的功能,下面3層(即三、二、1層)主要面向經過網絡的端到端的數據流。應用層常見的協議有:HTTP、FTP、SMTP等;常見的傳輸層有:TCP、UDP。本文主要是基於TCP自定義協議實現客戶端與服務端的長鏈接。程序員

2. Socket

Socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員作網絡開發所用的接口,這就是Socket編程接口,一般也稱做"套接字"。套接字之間的鏈接過程能夠分爲三個步驟:客戶端請求,服務端回覆收到,客戶端收到服務端的回覆,即三次握手。鏈接成功時,應用程序兩端都會產生一個Socket實例,操做這個實例完成所需的會話。對於一個網絡鏈接來講,套接字是平等的,並無差異,不由於在服務器端或在客戶端而產生不一樣級別。編程

3. 位(bit)、字節(byte)

「位(bit)」是電子計算機中最小的數據單位。每一位的狀態只能是0或1;「字節(Byte)」由8個二進制位構成(即1byte=8bit),它是存儲空間的基本計量單位,它能表示到數值範圍爲0到255(即2的8次方減1);數組

4. 算術移位運算(符號位不變,低位補0)

  • 左移運算:1<<2,1的二進制位是1,向左移兩位是100,轉爲十進制數即爲4,因此1<<2的運算結果是4;服務器

  • 右移運算:7>>2,7的二進制位是111,向右移兩位是1,因此7>>2的運算結果是1 。網絡

5. Java中各種型佔字節數

byte      8位,1個字節
boolean   8位,1個字節
char      16位,2個字節
short     16位,2個字節
int       32位,4個字節
float     32位,4個字節
double    64位,8個字節
long      64位,8個字節

6. Java中socket相關函數

  • 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();

7. Java中常見流操做類

  • 輸入流

    • InputStream

      • 抽象類,描述流的輸入

    • ByteArrayInputStream

      • 從字節數組讀取的輸入流

    • BufferedInputStream

      • 緩衝輸入流

    • FileInputStream

      • 從文件讀入的輸入流

    • ObjectInputStream

      • 對象輸入流(所讀寫的對象必須實現Serializable接口)

    • DataInputStream

      • 包含了讀取Java標準數據類型的輸入流

  • 輸出流

    • OutputStream

      • 抽象類,描述流的輸入

    • ByteArrayOutputStream

      • 寫入字節數組的輸出流

    • BufferedOutputStream

      • 緩衝輸出流

    • FileOutputStream

      • 寫入文件的輸出流

    • ObjectOutputStream

      • 對象輸出流(所讀寫的對象必須實現Serializable接口)

    • DataOutputStream

      • 包含了寫Java標準數據類型的輸出流

2、一個簡單的socket鏈接例子

注:先運行服務端代碼的main函數,再運行客戶端代碼的main函數,便可看到打印鏈接成功

1. 客戶端

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("斷開鏈接!");
    }
}

2. 服務端

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();
    }
}

3、一個簡單的自定義協議例子

例子中,數據包的定義:消息對象=包類型+包長度+消息內容

  • 包類型 byte 型

  • 包長度 int 型

  • 消息內容 byte[] 型

1. 客戶端

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();
    }
}

2. 服務端

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);
        }
    }
}

4、總結

本文簡單介紹了socket通訊和自定義協議的相關知識點,爲後續的深刻作一些準備工做,下一篇文章《基於Java Socket的自定義協議,實現Android與服務器的長鏈接(二)》將經過一個實例來詳細講解自定義協議實現長鏈接通訊。

相關文章
相關標籤/搜索