android TCP 和 UDP總結

之前寫過一些關於TCP和UDP數據傳輸的代碼,比如使用TCP傳輸音視頻數據包,P2P打洞中使用UDP等。寫好之後就直接丟下瞭,沒有總結下都。最近準備找工作,再拿來溫習下。

1、還是先說點啥

暫時把自己的定位很明確,就是android應用層的開發,所以關於TCP/UDP的實現細節,暫時也不想去深究。但是心裡清楚這個必須去看的,有時間推薦大傢看看《TCP/IP詳解》,或者網上有很多大牛的總結。

2、TCP

不知道為什麼,這個總結不想寫的太細,不貼代碼寫的細又不知道能總結啥,好糾結,可能就是認識有限吧,公司要是有個架構就好瞭。不說瞭,還是安安穩穩的寫總結吧。TCP這個可能是我們用的比較多的或者說我用的比較多的,主要的工作還是進行大量數據的傳輸和心跳保持(想想去年面試的時候都不知道啥是心跳,汗……)。對於心跳保持,就是一個簡單的小心跳包;大量數據的傳輸,這個也總結不瞭啥東西,代碼就那樣,就是一些細節說一下。

客戶端:這個是我們關註的最多的,也是作為一個手機APP主要關註的,因為我們就是client

a、創建一個client socket

new Socket(ip, prot);

我們可以通過上面的方式創建一個socket,如果失敗,會拋出IOException。參數中的IP和Port是目標服務器的IP和端口號。若你想得到本地的IP和端口可通過這個socket拿到。當然,創建socket還有多種構造方法,比如 new Socket(proxy) ,如果有需要你可以查閱相關說明。

b、發送和接收數據

拿到socket對象之後我們接著要做的事情就是從這個socket中拿到輸入和輸出流,這樣我們才可以進行數據的發送和接收:

InputStream is = mSocket.getInputStream();
OutputStream out = mSocket.getOutputStream();

通過輸出流,我們可以使用is.read(receiveBuffer)和out.write(data);來進行數據的收發。這裡給兩個簡單實例:

接收數據:

	@Override
	public void execute() {
		try {
			int count = is.read(receiveBuffer);
			if (count == -1) {
				notifyError();
			}
			byte[] data = getPacket(receiveBuffer, count, is);
			mReceiverQueue.put(data);
		} catch (InterruptedException e) {
			e.printStackTrace();
			notifyError();
		} catch (IOException e) {
			e.printStackTrace();
			notifyError();
		}
	}

假如我們的數據包協議格式如下:

這個時候,如果進行大量數據傳輸的時候我們從InputStream中一次讀取的數據可能不是一個完整的數據包。這個時候我們需要做下面的判斷:
a、讀取的數據長度是否達到包頭大小,若沒有包頭長,繼續讀,直到達到或者超過包頭大小;
b、從包頭中解出長度字段,循環讀取,直到讀到長度字段標示長度為止;
c、校驗數據,去除包頭,取出包體;
d、重復上述步驟。

發送數據:

	@Override
	public void execute() {
		try {
			byte[] data = mSenderQueue.take();
			out.write(data);
		} catch (InterruptedException e) {
			e.printStackTrace();
			notifyError();
		} catch (IOException e) {
			e.printStackTrace();
			notifyError();
		}
	}

mSenderQueue和mReceiverQueue一樣,都是阻塞隊列。發送的時候,我們從隊列中取出要發送的數據,然後通過輸出流寫入即可。這個地方比發送簡單瞭一些。可能你已經註意到,我的發送和接收都用瞭阻塞隊列,這個原因就是考慮到大量數據的時候做一個緩沖,如果沒有緩沖,可能導致代碼的阻塞。另外就是當我們的阻塞隊列充滿的時候可以手動丟棄一些數據,這個就是具體應用瞭。

服務端:這個可以簡單瞭解下,可以java實現,也可C++等實現

首先,我們創建一個ServerSocket對象並指定一個端口號,通過這個對象的accept()方法等待客戶的連接,這個方法是阻塞的;當有客戶端連接上來之後,我們就拿到瞭連接的Socket,拿到這個socket之後的操作就和客戶端一樣瞭,最後看一下關鍵代碼:

	try {
		ServerSocket serverSocket = new ServerSocket(9559);
		while (true) {
			Socket socket = serverSocket.accept();
			// new ServiceSocketThread(socket).start();
		}
	} catch (Exception e) {
		e.printStackTrace();
	}

因為服務端不可能隻與一個客戶端連接,因此上面的代碼寫在一個死循環中。拿到socket之後起一個新的線程來處理這個socket。

最後,還有一點需要註意的是:從Socket中拿到InputStream和OutputStream之後如果關閉它們,socket也將隨之關閉(未驗證)。

3、UDP

這個東西用的很少,就是當初測試P2P的時候用過。能想到的問題就是數據大小的問題,比如發送數據我們的數據定義為多大合適。但是最後沒有實際的項目驗證,在此也不好回答。先貼一段代碼出來:

public class UDPServer extends BaseThread {

	/** 發送隊列大小 */
	public static final int SENDQUEUESIZE = 10;
	/** 接收隊列大小 */
	public static final int RECEIVEQUEUESIZE = 10;
	/** TCP接收緩存大小 */
	public static final int RECEIVERBUFFERSIZE = 1024;
	/** UDP接收緩存大小 */
	public static final int RECEIVERPACKETSIZE = 1024 * 64;

	private int count = 0;
	private DatagramPacket receivePacket;
	private DatagramSocket mSocket;

	@Override
	public boolean prepare() {
		receivePacket = new DatagramPacket(new byte[RECEIVERPACKETSIZE], RECEIVERPACKETSIZE);
		try {
			mSocket = new DatagramSocket(9559);
		} catch (SocketException e) {
			System.out.println(String.format("udp connect init error: %s", e.getMessage()));
			return false;
		}
		return true;
	}

	@Override
	public void execute() {
		try {
			mSocket.receive(receivePacket);

			byte[] data = receivePacket.getData();
			int length = receivePacket.getLength();
			int offset = receivePacket.getOffset();
			System.out.print(++count
					+ String.format("length:%d|%d, offset:%d, data: %s \n", length, data.length, offset, new String(data, "gbk")));
			System.out.println(data[1024]);
		} catch (SocketException e) {
			System.out.println(String.format("udp connect init error: %s", e.getMessage()));
		} catch (IOException e) {
			System.out.println(String.format("udp connect init error: %s", e.getMessage()));
		}
	}
}

byte[] data = receivePacket.getData();這個地方拿到的data是緩沖區的大小,他們地址是樣的,這個大傢可以試試就知道。至於這個data中有多少數據,就需要我們通過receivePacket.getLength();拿到。

發佈留言