前言
闊別了很久博客園,雖然看了以前寫的很多東西感覺好幼稚,但是還是覺得應該把一些自己覺得有用的東西和大家分享。廢話不多說,現在開始進入正題。
之前的六年工作經驗,呆過了一些大公司,每個在大公司呆過的人應該知道,在一個大型應用中不斷的增加業務和功能,還有基於性能的考慮,使得很多基礎服務必須進行模塊化,從而讓各子系統方便使用而不是每個系統重新再實現一套,也可以使可能成為瓶頸的基礎功能可以單獨進行擴展,比如(以電商系統舉例)用戶信息管理、交易管理中心、商品管理中心等等。 在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序列化的方式進行序列化。
公司裡還有事,而且我不知道這些是不是各位朋友想看到的內容,忙完今天我會繼續進行補充。 哪裡有講的不對的希望大家來矯正。