1. 程式人生 > >Java Socket和網路模型

Java Socket和網路模型

Java Socket和TCP/IP以及OSI,RPC和RESTful協議

Java Socket和網路模型

Java Socket是JVM通過作業系統操控CPU、網絡卡與外界通訊的一個元件,包括BIO、NIO、AIO等網路IO元件的底層也是Socket。

在瞭解Java Socket之前先得了解網路模型的相關概念

  • OSI七層模型

  • TCP/IP四層模型

  • RPC協議

  • Restful協議

OSI七層模型

  1. 應用層:主機中的各個程序,提供埠號供訪問

  2. 表示層:將主機中的程序傳遞的資料通過不同的OS編譯/轉換/加密處理後交給硬體(網絡卡)

  3. 會話層:標識相互通訊的主機間正在進行的回話,負責建立、管理、終止會話

  4. 傳輸層:資料脫離網絡卡後即進入傳輸層,記錄了即將訪問的埠號(TCP/UDP),將資料分段

  5. 網路層:通過網線尋找目標主機(TCP/IP)

  6. 鏈路層:將位元組合成位元組進而組合成幀,用MAC地址訪問介質,錯誤發現但不能糾正。

  7. 物理層:建立、維護、斷開物理連線。

TCP/IP四層模型

  1. 主機到網路層
    實際上TCP/IP參考模型沒有真正描述這一層的實現,只是要求能夠提供給其上層-網路互連層一個訪問介面,以便在其上傳遞IP分組。由於這一層次未被定義,所以其具體的實現方法將隨著網路型別的不同而不同。
  2. 網路互連層
    a. 網路互連層是整個TCP/IP協議棧的核心。它的功能是把分組發往目標網路或主機。同時,為了儘快地傳送分組,可能需要沿不同的路徑同時進行分組傳遞。因此,分組到達的順序和傳送的順序可能不同,這就需要上層必須對分組進行排序。
    b. 網路互連層定義了分組格式和協議,即IP協議(Internet Protocol)。
    c. 網路互連層除了需要完成路由的功能外,也可以完成將不同型別的網路(異構網)互連的任務。除此之外,網路互連層還需要完成擁塞控制的功能。
  3. 傳輸層
    a. 在TCP/IP模型中,傳輸層的功能是使源端主機和目標端主機上的對等實體可以進行會話。在傳輸層定義了兩種服務質量不同的協議。即:傳輸控制協議TCP(transmission control protocol)和使用者資料報協議UDP(user datagram protocol)。
    b. TCP協議是一個面向連線的、可靠的協議。它將一臺主機發出的位元組流無差錯地發往網際網路上的其他主機。在傳送端,它負責把上層傳送下來的位元組流分成報文段並傳遞給下層。在接收端,它負責把收到的報文進行重組後遞交給上層。
    c. TCP協議還要處理端到端的流量控制,以避免緩慢接收的接收方沒有足夠的緩衝區接收發送方傳送的大量資料。UDP協議是一個不可靠的、無連線協議,主要適用於不需要對報文進行排序和流量控制的場合。
  4. 應用層
    a. TCP/IP模型將OSI參考模型中的會話層和表示層的功能合併到應用層實現。
    b. 應用層面向不同的網路應用引入了不同的應用層協議。其中,有基於TCP協議的,如檔案傳輸協議(File Transfer Protocol,FTP)、虛擬終端協議(TELNET)、超文字連結協議(Hyper Text Transfer Protocol,HTTP),也有基於UDP協議的。

OSI和TCP/IP的區別

OSI和TCP/IP都是概念,TCP/IP是OSI的合併,簡化
desc

工作原理

在這裡插入圖片描述

RPC協議

Remote Procedure Call 遠端過程通訊

  1. RPC可以基於HTTP,rmi等協議,所謂的存根,就是兩個應用之間可建立固定的Socket Channel,只在建立Channel時三次握手,之後的每次通訊不需三次握手,資料格式可以為xml,json等格式
  2. 是長連線,在客戶端和服務端都有存根,不必每次通訊都要去3次握手什麼的,減少了網路開銷
  3. 一般都有註冊中心,有豐富的監控管理;釋出、下線介面、動態擴充套件等,對呼叫方來說是無感知、統一化的操作
  4. 比如dubbo,傳送方只有介面,而接收方有介面,並去真正的實現,通過這種方式在傳送機建立port(傳輸層),在接收機也建立相同的固定的port
    在這裡插入圖片描述

Restful協議

Representational State Transfer 表象性狀態傳遞

  1. RESTful基於HTTP,對其語意強化為web應用常用的4種CRUD請求,資料格式可以為xml,json等格式
  2. RESTful基於HTTP,HTTP基於tcp,是短連線,每次請求都需要3次握手

Java Socket

直接上程式碼

  • TCP Socket 客戶端

package com.td.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class SocketTCPClient {
	public static void main(String args[]) throws Exception {
		String host = "127.0.0.1";
		int port = 55533;
		transportToServer(host, port, "data body");
		heartbeatCheck(3000l, host, port);
	}
	
	/**
	 * 傳輸資料到服務端
	 */
	public static void transportToServer(String host, int port, String data) {
		try {
			
			Socket socket = new Socket(host, port);
			
			socket.setReuseAddress(true);		//使用 bind(SocketAddress)時,關閉 TCP 連線時,該連線可能在關閉後的一段時間內保持超時狀態,不建議使用bind(),不建議使用次配置
			socket.setSoLinger(true, 65535);	//啟用/禁用具有指定逗留時間的 SO_LINGER,Socket會等待指定的時間傳送完資料包,當資料量傳送過大丟擲異常時,再來設定這個值
			socket.setTcpNoDelay(true);			//客戶向伺服器傳送資料的時候,會根據當前資料量來決定是否傳送,如果資料量過小,那麼系統將會根據Nagle 演算法(暫時還沒研究),來決定傳送包的合併,也就是說傳送會有延遲,預設開啟。這個操作可能導致拆包和粘包
			socket.setSendBufferSize(10);		//預設都是8K,如果有需要可以修改,通過相應的set方法。不建議修改的太小,設定太小資料傳輸將過於頻繁。太大了將會造成訊息停留。setTcpNoDelay為true時設定無效
			socket.setReceiveBufferSize(10);	//預設都是8K,如果有需要可以修改,通過相應的set方法。不建議修改的太小,設定太小資料傳輸將過於頻繁。太大了將會造成訊息停留。setTcpNoDelay為true時設定無效
			socket.setKeepAlive(true);			//構建長時間連線的Socket還是配置上SO_KEEPALIVE比較好,!!!!長連線!!!!
			
			OutputStream outputStream = socket.getOutputStream();
			InputStream inputStream = socket.getInputStream();
			BufferedReader read = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
			outputStream.write(data.getBytes("UTF-8"));
			
			socket.shutdownOutput();			//通過關閉流來告知服務端傳輸完畢,可以處理收到的訊息,可用outputStream.close();替代,原理一樣
			readStream(inputStream, read);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 讀流
	 */
	private static void readStream(InputStream inputStream, BufferedReader read) {
		try {
			String line;
			StringBuilder sb = new StringBuilder();
			while ((line = read.readLine()) != null) {
				sb.append(line);
			}
			System.out.println("get message from server: " + sb.toString());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 心跳檢測機制
	 */
	private static void heartbeatCheck(Long timeCycle, String host, int port) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep(timeCycle);
						transportToServer(host, port, "heartbeat package");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
	}
}


  • TCP Socket 服務端

package com.td.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SocketTCPServer {
	private static ExecutorService threadPool = Executors.newFixedThreadPool(100);
	
	public static void main(String[] args) throws Exception {
		int port = 55533;
		startSocketServer(port);
	}
	
	/**
	 * 啟動服務端監聽
	 */
	public static void startSocketServer(int port) {
		try (ServerSocket server = new ServerSocket(port);) {
			while(true) {
				server.setSoTimeout(0);
				//TCP三次握手操作後,系統才會將這個連線交給應用層
				Socket socket = server.accept();
				threadPool.submit(new Runnable() {
					@Override
					public void run() {
						dealQueue(socket);
					}
				});
			}
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
	
	/**
	 * 處理收到的訊息
	 */
	private static void dealQueue(Socket socket) {
		try (InputStream inputStream = socket.getInputStream();
				BufferedReader read = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
				OutputStream outputStream = socket.getOutputStream();) {
				String result = readStream(inputStream, read);
				//檢測心跳包
				if (result != null && "heartbeat package".equals(result)) 
					return;
				outputStream.write("Hello Client,I get the message.".getBytes("UTF-8"));
			} catch (IOException e) {
				e.printStackTrace();
			}
	}

	/**
	 * 讀流
	 */
	private static String readStream(InputStream inputStream, BufferedReader read) {
		try {
			String line;
			StringBuilder sb = new StringBuilder();
			while ((line = read.readLine()) != null) {
				sb.append(line);
			}
			System.out.println(sb.toString());
			return sb.toString();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
}


  • UDP Socket 客戶端

package com.td.socket;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

public class SocketUDPClient {
	public static void main(String[] args) {
		try {
			DatagramSocket socket = new DatagramSocket();
			sendUDP("127.0.0.1", 10010, "send data to server.", socket);
			System.out.println("receive from server:" + (String) receiveUDP(socket));
			socket.close();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 客戶端傳送報文
	 */
	public static void sendUDP(String host, int port, Object dataObj, DatagramSocket socket) throws IOException {
		InetAddress address = InetAddress.getByName(host);
		byte[] data = dataObj.toString().getBytes();
		DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
		socket.send(packet);
	}

	/**
	 * 客戶端傳送後接收報文,通過DatagramSocket為中介建立起新的接收Port
	 */
	public static Object receiveUDP(DatagramSocket socket) throws IOException {
		byte[] dataReply = new byte[1024];
		DatagramPacket packet2 = new DatagramPacket(dataReply, dataReply.length);
		socket.receive(packet2);
		String reply = new String(dataReply, 0, packet2.getLength());
		return reply;
	}
}


  • UDP Socket 服務端

package com.td.socket;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class SocketUDPServer {
	public static void main(String[] args) {
		try {
			int port = 10010;
			DatagramSocket socket = new DatagramSocket(port);
			DatagramPacket reply = receiveUDP(socket);
			sendUDP(reply.getAddress(), reply.getPort(), "welcome to server!", socket);
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * UDP接收報文後傳送報文
	 * @param port	服務端收文後通過DatagramPacket獲取"動態"Port及來訪IP
	 */
	public static void sendUDP(InetAddress inetAddress, int port, Object dataObj, DatagramSocket socket) throws IOException {
		byte[] data = dataObj.toString().getBytes();
		DatagramPacket packet = new DatagramPacket(data, data.length, inetAddress, port);
		socket.send(packet);
	}

	/**
	 * UDP接收報文
	 */
	public static DatagramPacket receiveUDP(DatagramSocket socket) throws IOException {
		byte[] dataReply = new byte[1024];
		DatagramPacket packet = new DatagramPacket(dataReply, dataReply.length);
		socket.receive(packet);
		String reply = new String(dataReply, 0, packet.getLength());
		System.out.println("receive from client : " + reply);
		return packet;
	}
}