前言
闊別了很久博客園,雖然看了以前寫的很多東西感覺好幼稚,但是還是覺得應該把一些自己覺得有用的東西和大家分享。廢話不多說,現在開始進入正題。
之前的六年工作經驗,呆過了一些大公司,每個在大公司呆過的人應該知道,在一個大型應用中不斷的增加業務和功能,還有基於性能的考慮,使得很多基礎服務必須進行模塊化,從而讓各子系統方便使用而不是每個系統重新再實現一套,也可以使可能成為瓶頸的基礎功能可以單獨進行擴展,比如(以電商系統舉例)用戶信息管理、交易管理中心、商品管理中心等等。 在rpc發展最初,服務進行模塊塊以後,各個子系統、模塊實現的技術五花八門,如:hessian、WebService、Socket、http等進行互相調用,各個子系統之間的交互方式和方法不統一,使得各系統間很難很好的整合。並且這些方式還涉及超時、加密解密、參數的傳遞等各種問題。 在這種情況下,hsf、dubbo這種高性能rpc中間件出現了。 現在我就已最簡單的方式從頭開始講起其中的原理。
我將分為一個系列為大家進行解剖
一、RPC實現原理(HSF、dubbo) 從頭開始(一)
二、RPC實現原理(HSF、dubbo)發布一個服務與訂閱一個服務(三)
三、RPC實現原理(HSF、dubbo)zookeeper進行集群配置管理(二)
四、RPC實現原理(HSF、dubbo)netty替換java socket(四)
五、待補充
NO.1 TCP傳輸協議
為什麼選擇TCP作為傳輸協議?HTTP在TCP的上一層,位於應用層,TCP位於網絡層,以越往底層越快的原理,我就不過多解釋為什麼選擇tcp作為傳輸協議了。 那麼在項目中我們怎麼使用tcp進行調用呢?直接上個例子代碼:
socket服務端:
import java.net.*; import java.io.*; /** * socket編程之:簡單socket server * * @author chengwei.lcw 2016-11-27 */ public class SocketServer { private ServerSocket serverSocket; private Socket socket; private BufferedReader in; private PrintWriter out; public SocketServer() { try { serverSocket = new ServerSocket(9999); while (true) { // 此處會阻塞,後面會講到nio的作用 socket = serverSocket.accept(); in = new BufferedReader(new InputStreamReader( socket.getInputStream())); out = new PrintWriter(socket.getOutputStream(), true); String line = in.readLine(); // 打印出來看看結果 System.out.println("line:" + line); // 返回給client端,通知我已收到數據 out.println("you input is :" + line); out.close(); in.close(); socket.close(); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { new SocketServer(); } }
scoket客戶端:
import java.io.*; import java.net.*; /** * socket編程之:簡單socket client * * @author chengwei.lcw 2016-11-27 */ public class SocketClient { private Socket socket; private BufferedReader in; private PrintWriter out; public SocketClient() { try { socket = new Socket("127.0.0.1", 9999); in = new BufferedReader(new InputStreamReader( socket.getInputStream())); out = new PrintWriter(socket.getOutputStream(), true); // 向服務端寫數據 BufferedReader line = new BufferedReader(new InputStreamReader( System.in)); out.println(line.readLine()); line.close(); // 打印出來服務端發回來的回執 System.out.println(in.readLine()); in.close(); out.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { new SocketClient(); } }
先啟動server,再啟動client,輸入參數,回車,兩者第一次會話完成。
小結總結:
目前例子中我們使用了標准io socket,這裡的很多時候會阻塞,如accept()、read()時都會阻塞。測試的時候可以讓客戶端睡眠幾秒,在這期間啟動第二個客戶端,這個時候第一個客戶端未完成前,第二個客戶端是被阻塞在accept()中的。 這種情況可以給每個客戶端都單獨分配一個線程,但是這樣創建過多的線程,可能會嚴重影響服務器的性能。 第二種解決方案就是使用NIO 非阻塞的通信方式,jdk1.4之後已經引入了這個功能,這樣可以使得服務器只要啟動一個線程就能處理所有的客戶端socket請求。netty就是基於NIO的高性能框架,相比jdk nio做了很多改進,修復了一些缺陷。 (這裡不對netty與jdk nio做過多贅述,這不在我們討論原理細節裡,如果大家對這方面有興趣,我會單獨寫篇隨筆進行深度講解)
NO.2 序列化方式
在真正的項目中,很多時候我們傳的都是自己定義的類。在遠程通訊中,類的傳輸我們需要對類進行序列化和反序列化。序列化的方式有多種,如二進制、xml、soap。我們就以用的最多的二進制進行舉例:
socket服務端:
import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.ServerSocket; import java.net.Socket; /** * socket編程之:傳輸對象server * * @author chengwei.lcw 2016-11-27 */ public class SocketObjectSever { private ServerSocket serverSocket; private ObjectInputStream in; private ObjectOutputStream out; public SocketObjectSever() { try { serverSocket = new ServerSocket(9999); while (true) { // 此處會阻塞,後面會講到nio的作用 Socket socket = serverSocket.accept(); in = new ObjectInputStream(socket.getInputStream()); out = new ObjectOutputStream(socket.getOutputStream()); // 接收server端傳來的數據,並轉為Student Student student = (Student) in.readObject(); // 重寫了toString()方法,打印出來看看 System.out.println("Server: " + student); // 返回給client端,通知我已收到數據 out.writeObject("yes client, I receive"); out.flush(); } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args) { new SocketObjectSever(); } }
socket客戶端:
import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; import java.net.UnknownHostException; /** * socket編程之:傳輸對象client * * @author chengwei.lcw 2016-11-27 */ public class SocketObjectClient { private Socket socket; private ObjectInputStream in; private ObjectOutputStream out; public SocketObjectClient() { try { socket = new Socket("127.0.0.1",9999); out = new ObjectOutputStream(socket.getOutputStream()); in = new ObjectInputStream(socket.getInputStream()); /* * 建一個student對象,用於傳輸 */ Student s = new Student("chengwei.lcw", 28); // 把對象寫到管道中,client端進行接收 out.writeObject(s); out.flush(); String receive = (String) in.readObject(); System.out.println("Client Receive :"+receive); in.close(); out.close(); socket.close(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static void main(String[] args) { new SocketObjectClient(); } }
另外定義一個要傳輸的類:
import java.io.Serializable; /** * socket編程之:要進行傳輸的類,需要繼承Serializable接口 * * @author chengwei.lcw 2016-11-27 * */ public class Student implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } public String toString() { return "name=" + this.name + ", age=" + this.age; } }
依然先啟動server,再啟動client,server端控制台輸出:
Server: name=chengwei.lcw, age=28
這樣為止,我們的socket可以傳輸對象了。
這裡我們使用的序列化方式為java直接進行序列化,而hessian序列化比Java序列化高效很多,生成的字節流也要短很多,因為hessian在序列化時會把字節流進行壓縮。在後面的升級版中我會使用hessian序列化的方式進行序列化。
公司裡還有事,而且我不知道這些是不是各位朋友想看到的內容,忙完今天我會繼續進行補充。 哪裡有講的不對的希望大家來矯正。