Android中Socket通訊之TCP與UDP傳輸原理

1、Socket通訊簡介  java

Android與服務器的通訊方式主要有兩種,一是Http通訊,一是Socket通訊。二者的最大差別在於,http鏈接使用的是「請求—響應方式」,即在請求時創建鏈接通道,當客戶端向服務器發送請求後,服務器端才能向客戶端返回數據。 android

而Socket通訊中基於TCP/IP協議的通訊則是在雙方創建起鏈接後就能夠直接進行數據的傳輸,在鏈接時可實現信息的主動推送,而不須要每次由客戶端想服務器發送請求。而UDP則是提供無鏈接的數據報服務,UDP在發送數據報前不需創建鏈接,不對數據報進行檢查便可發送數據包。。 那麼,什麼是socket?Socket又稱套接字,在程序內部提供了與外界通訊的端口,即端口通訊。經過創建socket鏈接,可爲通訊雙方的數據傳輸傳提供通道。 編程


1.1什麼是Socket Socket
是一種抽象層,應用程序經過它來發送和接收數據,使用Socket能夠將應用程序添加到網絡中,與處於同一網絡中的其餘應用程序進行通訊。簡單來講,Socket提供了程序內部與外界通訊的端口併爲通訊雙方的提供了數據傳輸通道。 數組


 1.2Socket的分類
 根據不一樣的的底層協議,Socket的實現是多樣化的。本文中只介紹TCP/IP協議和UDP協議的通訊,在這個協議族當中主要的Socket類型爲流套接字(streamsocket)和數據報套接字(datagramsocket)。流套接字將TCP做爲其端對端協議,提供了一個可信賴的字節流服務。數據報套接字使用UDP協議,提供數據打包發送服務。 下面,咱們來認識一下這兩種Socket類型的基本實現模型。 緩存


2、Socket 基本通訊模型 服務器


3、Socket基本實現原理 網絡


 3.1基於TCP協議的Socket 
一、服務器端首先聲明一個ServerSocket對象而且指定端口號,而後調用Serversocket的accept()方法接收客戶端的數據。accept()方法在沒有數據進行接收的處於堵塞狀態。(Socketsocket=serversocket.accept()),一旦接收到數據,經過inputstream讀取接收的數據。
二、客戶端建立一個Socket對象,指定目標主機(服務器端)的ip地址和端口號(Socketsocket=newSocket("172.168.10.108",8080);),而後獲取客戶端發送數據的輸出流(OutputStreamoutputstream=socket.getOutputStream()),最後將要發送的數據寫入到outputstream便可進行TCP協議的socket數據傳輸。
3.2基於UDP協議的數據傳輸 
一、服務器端首先建立一個DatagramSocket對象,而且指點監聽的端口。接下來建立一個空的DatagramPacket數據包對象並指定大小,用於接收數據(byte[] data=new byte[1024];DatagramPacket  packet=new  DatagramPacket(data,data.length)),使用DatagramSocket的receive方法接收客戶端發送的數據(datagramSocket.receive(packet)),receive()與serversocket的accepet()相似,在沒有數據進行接收的處於堵塞狀態。
二、客戶端也建立個DatagramSocket對象,而且指點監聽的端口。接下來建立一個InetAddress對象,這個對象是一個網絡地址(InetAddress serveraddress=InetAddress.getByName("172.168.1.100")).定義要發送的一個字符串,建立一個DatagramPacke數據包t對象,並制定要講這個數據報包發送到網絡的哪一個地址以及端口號,最後使用DatagramSocket的對象的send()發送數據包。(String str="hello";byte[] data=str.getByte();DatagramPacket packet=new DatagramPacket(data,data.length,serveraddress,4567);socket.send(packet);) app


好了,下面就來看看代碼怎麼實現吧!

4、實現android基於TCP/IP協議的通訊
socket

一、Android端(客戶端)代碼: ide

TCP_Send.java
//TCP向服務端發送數據
	public void TCP_sendMsg(String msg) {
		Socket socket = null;
		OutputStream output = null;
		InputStream input = null;
		try {
			// socket = new Socket(InetAddress.getByName("192.168.1.100"), 8888);//這種形式也行
			socket = new Socket("192.168.1.100", 8888);// 第一個參數是目標主機名或目標主機的ip地址,第二個參數是目標主機端口號
			output = socket.getOutputStream();
			output.write(msg.getBytes());// 把msg信息寫入輸出流中
			//--------接收服務端的返回信息-------------
			socket.shutdownOutput(); // 必定要加上這句,不然收不到來自服務器端的消息返回 ,意思就是結束msg信息的寫入
			input = socket.getInputStream();
			byte[] b = new byte[1024];
			int len = -1;
			sb = new StringBuffer();
			while ((len = input.read(b)) != -1) {
				sb.append(new String(b, 0, len, Charset.forName("gbk")));// 獲得返回信息
			}
			// 在主線程中更新UI
			runOnUiThread(new Runnable() {
				@Override
				public void run() {
					mTextView.setText(sb.toString());// 將返回信息設置到界面顯示
				}
			});
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				// 注意,輸出流不須要關閉,由於它只是在Socket中獲得輸出流對象,並無建立
				if (socket != null) {
					socket.close();// 釋放資源,關閉這個Socket
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}




二、服務端代碼:

public void ReceiveMsg() {
		ServerSocket server = null;
		Socket socket = null;
		try {
			server = new ServerSocket(8888);// 建立一個ServerSocket對象,並讓這個Socket在8080端口監聽
			// 調用ServerSocket的accept()方法,接受客戶端所發送的請求,同時建立一個Socket對象
			// 若是客戶端沒有發送數據,那麼該線程就停滯不繼續,也就是阻塞
			while(true){
				socket = server.accept();
				System.out.println(socket.getInetAddress().getHostName());
				System.out.println(socket.getInetAddress().getHostAddress());//獲得當前發送數據Socket對象的主機名和ip地址
				InputStream input = socket.getInputStream();// 獲得該Socket對象的輸入流
				BufferedInputStream bis = new BufferedInputStream(input);
				byte[] b = new byte[1024];
				int len = -1;
				while ((len = bis.read(b)) != -1) {// 從InputStream當中讀取客戶端所發送的數據
					System.out.println(new String(b, 0, len,"UTF-8"));
				}
				//--------向客戶端的返回信息-------------
				socket.shutdownInput();//結束讀取
				OutputStream outputResult = socket.getOutputStream();//不須要關閉
				outputResult.write("ok,我已經收到!".getBytes());
				
				bis.close();//關閉緩存輸入流,注意,輸入流input不須要關閉,由於它只是在Socket中獲得輸入流對象,並無建立
				socket.close();//接收這個Socket的數據後釋放資源,由於每一次客戶端發送數據都會在服務端建立一個Socket對象,注意ServerSocket不該該關閉,由於這是服務器ServerSocket對象,關閉了客戶端就不能發送數據了
				socket = null;
			}		
		} catch (IOException e) {
			e.printStackTrace();
		}
	}





再貼出結果吧:


服務器顯示:

192.168.1.102
192.168.1.102
HelloKitty,Java,哈哈哈


【總結】基於Socket流式網絡編程,也即便用TCP/IP協議,能夠對文件的傳輸進行操做

5、實現android基於UDP協議的通訊

一、Android端(客戶端)代碼:


//發送數據包給服務端和接收返回的數據
	public void UDP_send(String msg) {
		DatagramSocket socket = null;
		try {
			socket = new DatagramSocket(8880);// 建立DatagramSocket對象並綁定一個本地端口號,注意,若是客戶端須要接收服務器的返回數據,還須要使用這個端口號來接收數據,因此必定要記住
			byte[] data = msg.getBytes();// 把字符串轉爲字節數組
			// Inet4Address inetAddress = (Inet4Address)Inet4Address.getByName("192.168.1.100");//使用這個也行,表示使用4個字節的ip地址
			InetAddress inetAddress = InetAddress.getByName("192.168.1.100");// 獲得ip或主機名爲192.168.1.100的網絡地址對象
			DatagramPacket pack = new DatagramPacket(data, data.length,
					inetAddress, 8881);// 參數分別爲:發送數據的字節數組對象、數據的長度、目標主機的網絡地址、目標主機端口號,發送數據時必定要指定接收方的網絡地址和端口號
			socket.send(pack);//發送數據包
			//-----------接收服務器返回的數據-------------
			byte[] b = new byte[4*1024];//建立一個byte類型的數組,用於存放接收到得數據
			DatagramPacket pack2 = new DatagramPacket(b, b.length);// 定義一個DatagramPacket對象用來存儲接收的數據包,並指定大小和長度
			socket.receive(pack2);//接收數據包
			// data.getData()是獲得接收到的數據的字節數組對象,0爲起始位置,pack.getLength()獲得數據的長度
			final String result = new String(pack2.getData(),0,pack2.getLength(), "gbk");//把返回的數據轉換爲字符串
			socket.close();//釋放資源
			//在線程中更新UI
			runOnUiThread(new Runnable() {
				@Override
				public void run() {
					mTextView1.setText(result);
				}
			});
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}



二、服務端代碼:

public void ReceiveMsg(){
		DatagramSocket socket = null;
		try {
			socket = new DatagramSocket(8881);// 建立DatagramSocket對象並綁定一個本地端口號
			while(true){
				byte[] buf = new byte[4*1024];//建立一個byte類型的數組,用於存放接收到得數據
				DatagramPacket pack = new DatagramPacket(buf, buf.length);//建立一個DatagramPacket對象,並指定DatagramPacket對象的大小和長度
				socket.receive(pack);//讀取接收到得數據 包,若是客戶端沒有發送數據包,那麼該線程就停滯不繼續,這個一樣也是阻塞式的
				String str = new String(pack.getData(), 0,pack.getLength(),"UTF-8");//將接收到的數據包轉爲字符串輸出顯示
				String ip = pack.getAddress().getHostAddress();//獲得發送數據包的主機的ip地址
				System.out.println(ip+"發送:"+str);
				//-----------返回數據給客戶端------------
				InetAddress address = pack.getAddress();//獲得發送數據包主機的網絡地址對象
				byte[] data = "已收到!".getBytes();
				DatagramPacket p = new DatagramPacket(data, data.length, address, 8880);
				socket.send(p);
			}
			//注意不須要關閉服務器的socket,由於它一直等待接收數據
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}



客戶端圖片:

服務器顯示:

192.168.1.102發送:HelloKitty,Java,哈哈哈,這是udp

【注】udp的多播還沒實現,實現後我會加上來的


好了,這樣就實現了基本的Socket網絡編程了,注意:在android端使用時還須要加入權限哦:

[java]  view plain copy print ?
  1. <!--容許應用程序改變網絡狀態-->      
  2. <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>      
  3.       
  4. <!--容許應用程序改變WIFI鏈接狀態-->      
  5. <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>      
  6.       
  7. <!--容許應用程序訪問有關的網絡信息-->      
  8. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>      
  9.       
  10. <!--容許應用程序訪問WIFI網卡的網絡信息-->      
  11. <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>      
  12.       
  13. <!--容許應用程序徹底使用網絡-->      
  14. <uses-permission android:name="android.permission.INTERNET"/>   

總結: 使用UDP方式android端和服務器端接收能夠看出,客戶端和服務端的發送和接收大庭相徑,只要端口號和目標主機ip地址正確了,相互通訊就沒有問題,並且UDP它們之間的通訊是不須要鏈接的,由於它無論服務端開啓準備接收數據包沒,它只管把數據包發出去了就行。而TCP就不同了,若是在服務器沒有開啓的狀況下,它會報一個錯,提示與服務端沒有創建鏈接。TCP使用的是流的方式發送,UDP是以包的形式發送。
相關文章
相關標籤/搜索