一渺杉、thrift 定義
“什么是 thrift” 這個(gè)問(wèn)題蛇数,我曾問(wèn)過(guò)別人,也有人拿來(lái)問(wèn)過(guò)我是越。
無(wú)論是官網(wǎng)耳舅、百度、谷歌還是博客倚评,得到的答案都差不多浦徊,挨個(gè)補(bǔ)齊定語(yǔ)無(wú)非就是:
- thrift 是一個(gè)服務(wù)框架。
- thrift 是一個(gè) rpc 服務(wù)框架天梧。
- thrift 是一個(gè)跨語(yǔ)言的 rpc 服務(wù)框架盔性。
- thrift 是一個(gè) Facebook 公布的開(kāi)源跨語(yǔ)言的 rpc 服務(wù)框架。
那 “rpc 又是什么” 呢呢岗?
二冕香、什么是 RPC?
RPC 定義
- RPC = Remote Procedure Call后豫,遠(yuǎn)程過(guò)程調(diào)用悉尾。是一種進(jìn)程間通信方式,允許像調(diào)用本地服務(wù)一樣調(diào)用遠(yuǎn)程服務(wù)挫酿。
- 比較另辟蹊徑的解釋是:RPC 是一種編程模式构眯,把對(duì)服務(wù)器的調(diào)用抽象為過(guò)程調(diào)用,通常還伴隨著框架代碼自動(dòng)生成等功能早龟。
- 為了便于理解惫霸,先把“遠(yuǎn)程過(guò)程調(diào)用”和“本地過(guò)程調(diào)用”做個(gè)對(duì)比猫缭。
“遠(yuǎn)程過(guò)程調(diào)用(RPC)” 與 “本地過(guò)程調(diào)用” 對(duì)比
由上圖可以看出:
- 本地過(guò)程調(diào)用,所有的過(guò)程都在同一個(gè)服務(wù)器上它褪,依次調(diào)用即可饵骨。
- 遠(yuǎn)程過(guò)程調(diào)用(即 RPC),部分過(guò)程在 Client 部分過(guò)程在 Server茫打,Client 想要使用 Server 中的過(guò)程并沒(méi)有本地過(guò)程調(diào)用那樣簡(jiǎn)單居触,需要兩個(gè)端(Client 和 Server)交互。
RPC 與本地過(guò)程調(diào)用的的差異具體體現(xiàn)在兩方面:
- RPC 要求雙方具有網(wǎng)絡(luò)通信能力老赤。網(wǎng)絡(luò)傳輸開(kāi)銷增加轮洋。
- RPC 要求雙方統(tǒng)一數(shù)據(jù)結(jié)構(gòu)和傳輸協(xié)議,因?yàn)檫h(yuǎn)程過(guò)程與主調(diào)方運(yùn)行在完全不同的地址空間中抬旺,無(wú)法通過(guò)傳遞指針直接調(diào)用弊予。編程復(fù)雜度增高。
這就很容易讓人想到基于 HTTP 的各種 webService 實(shí)現(xiàn)开财,因?yàn)樗耆珴M足前述兩個(gè)要求汉柒。
HTTP 和 RPC
這里單獨(dú)說(shuō)一下我的理解。
- “基于 HTTP 的 webService 實(shí)現(xiàn)”责鳍,廣義上可以算是一種 RPC 的實(shí)現(xiàn)方式碾褂,因?yàn)椴煌?webService 之間可以通過(guò) HTTP 接口相互通信。
- 但是 RPC 又不僅限于使用 HTTP 協(xié)議實(shí)現(xiàn)历葛,HTTP 僅僅是 RPC 實(shí)現(xiàn)可選的一種傳輸協(xié)議正塌,除了 HTTP 協(xié)議外,還可以選擇 TCP/MQ 等其他方式恤溶。
回到正題乓诽,接下來(lái)看 thrift 作為一個(gè) “RPC框架” 到底做了什么,或者說(shuō)能做什么咒程。
三鸠天、從 sayHi 開(kāi)始
為了便于理解,我們從一個(gè)非常簡(jiǎn)單的小需求入手:要求是客戶端輸入姓名(例如:小明)帐姻,服務(wù)端輸出打招呼的句子(例如:Hi,小明!)稠集。
PS:請(qǐng)不要把這里的 demo 當(dāng)范本“摘抄”,僅僅是為了說(shuō)明問(wèn)題卖宠,寫(xiě)的略挫 ==
3.1 原生 Socket 裸寫(xiě) sayHi
以 java 語(yǔ)言為例巍杈,用原生 jdk 類庫(kù)提供的能力實(shí)現(xiàn) sayHi 功能。
定義三個(gè)類:
- V1SayHiService.java扛伍,sayHi 服務(wù)類筷畦。
- V1SocketDemoServer.java,服務(wù)端。
- V1SocketDemoClient.java鳖宾,客戶端吼砂。
// V1SayHiService.java
package com.ann.javas.projects.javacores.socket.demo;
public class V1SayHiService {
public static String sayHi(String name) {
return "Hi," + name + "!";
}
}
// V1SocketDemoServer.java
package com.ann.javas.projects.javacores.socket.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by liyanling on 2018/6/27.
* ************************************
* <p>
* 交互方式(短連接)
* <p>
* 1、服務(wù)端啟動(dòng)監(jiān)聽(tīng)鼎文。
* 2渔肩、服務(wù)端【每】獲得一個(gè)客戶端連接,處理完一條消息后即【關(guān)閉連接】拇惋,并【等待下一個(gè)客戶端連接】周偎。
* 3、服務(wù)端按行讀取消息內(nèi)容撑帖,作為姓名參數(shù)蓉坎,回寫(xiě) sayHi 消息
*/
public class V1SocketDemoServer {
private static Logger logger = LoggerFactory.getLogger(V1SocketDemoServer.class);
public static final int PORT = 9091;
private static int count = 1;
public static void main(String[] args) throws Exception {
startServer();
}
private static void startServer() throws Exception {
try {
ServerSocket serverSocket = new ServerSocket(PORT);
logger.info("{}:服務(wù)端(端口:{})啟動(dòng)監(jiān)聽(tīng),等待客戶端連接", count++, PORT);
while (true) {
Socket socket = serverSocket.accept();
logger.info("{}:服務(wù)端已與客戶端建立連接胡嘿,開(kāi)始接收客戶端消息...", count++);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String name = reader.readLine();
logger.info("{}:服務(wù)端獲取輸入流蛉艾,讀取客戶端信息:{}", count++, name);
String sayHiResult = V1SayHiService.sayHi(name);
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
writer.write(sayHiResult + "\n");
writer.flush();
logger.info("{}:服務(wù)端獲取輸出流,響應(yīng)客戶端請(qǐng)求衷敌,回寫(xiě):{}", count++, sayHiResult);
writer.close();
reader.close();
socket.close();
// serverSocket.close();// 這里不要把 serverSocket 關(guān)了勿侯。還得繼續(xù)監(jiān)聽(tīng)呢
logger.info("{}:服務(wù)端關(guān)閉資源", count++);
logger.info("{}:等待下一個(gè)客戶端連接", count++);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
// V1SocketDemoClient.java
package com.ann.javas.projects.javacores.socket.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* Created by liyanling on 2018/6/27.
*/
public class V1SocketDemoClient {
private static Logger logger = LoggerFactory.getLogger(V1SocketDemoClient.class);
public static final String IP = "127.0.0.1";
public static Scanner scanner;
public static int count = 1;
public static void main(String[] args) throws Exception {
scanner = new Scanner(System.in);
logger.info("{}:demo1-----------",count++);
startClient(IP, V1SocketDemoServer.PORT);
Thread.sleep(1000);
logger.info("{}:demo2-----------",count++);
startClient(IP, V1SocketDemoServer.PORT);
scanner.close();
}
public static void startClient(String IP, int PORT) throws Exception {
try {
logger.info("{}:準(zhǔn)備連接服務(wù)端(IP:{},PORT:{})", count++, IP, PORT);
Socket socket = new Socket(IP, PORT);
logger.info("{}:服務(wù)端已連接成功(IP:{},PORT:{})", count++, IP, PORT);
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
logger.info("{}:客戶端獲取輸出流,向服務(wù)端發(fā)送信息:", count++);
logger.info("{}:請(qǐng)輸入姓名:", count++);
String name = scanner.nextLine();
writer.write(name + "\n");
writer.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
logger.info("{}:客戶端獲取輸入流缴罗,讀取服務(wù)端返回信息:{}", count++, reader.readLine());
reader.close();
writer.close();
socket.close();
logger.info("{}:關(guān)閉資源", count++);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
先啟動(dòng) Server 再啟動(dòng) Client助琐,然后在客戶端終端輸入“小明”并回車,兩端分別輸出的日志如下:
// V1SocketDemoServer.java 日志
00:28:02.023 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoServer - 1:服務(wù)端(端口:9091)啟動(dòng)監(jiān)聽(tīng)瞒爬,等待客戶端連接
00:28:06.032 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoServer - 2:服務(wù)端已與客戶端建立連接弓柱,開(kāi)始接收客戶端消息...
00:28:09.817 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoServer - 3:服務(wù)端獲取輸入流沟堡,讀取客戶端信息:小明
00:28:09.822 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoServer - 4:服務(wù)端獲取輸出流侧但,響應(yīng)客戶端請(qǐng)求,回寫(xiě):Hi,小明!
00:28:09.823 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoServer - 5:服務(wù)端關(guān)閉資源
00:28:09.823 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoServer - 6:等待下一個(gè)客戶端連接
00:28:10.828 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoServer - 7:服務(wù)端已與客戶端建立連接航罗,開(kāi)始接收客戶端消息...
// V1SocketDemoClient.java 日志
00:28:06.028 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoClient - 1:demo1-----------
00:28:06.029 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoClient - 2:準(zhǔn)備連接服務(wù)端(IP:127.0.0.1,PORT:9091)
00:28:06.032 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoClient - 3:服務(wù)端已連接成功(IP:127.0.0.1,PORT:9091)
00:28:06.033 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoClient - 4:客戶端獲取輸出流禀横,向服務(wù)端發(fā)送信息:
00:28:06.033 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoClient - 5:請(qǐng)輸入姓名:
小明
00:28:09.822 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoClient - 6:客戶端獲取輸入流,讀取服務(wù)端返回信息:Hi,小明!
00:28:09.824 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoClient - 7:關(guān)閉資源
00:28:10.827 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoClient - 8:demo2-----------
00:28:10.828 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoClient - 9:準(zhǔn)備連接服務(wù)端(IP:127.0.0.1,PORT:9091)
00:28:10.828 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoClient - 10:服務(wù)端已連接成功(IP:127.0.0.1,PORT:9091)
00:28:10.829 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoClient - 11:客戶端獲取輸出流粥血,向服務(wù)端發(fā)送信息:
00:28:10.829 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoClient - 12:請(qǐng)輸入姓名:
這是原生 Socket 實(shí)現(xiàn)的 sayHi柏锄,非常簡(jiǎn)單,100多行代碼而已复亏。
雙端通信和服務(wù)規(guī)則如下:
- Server 僅提供 sayHi 功能趾娃。
- sayHi 功能僅一個(gè)姓名參數(shù)。
- 客戶端->服務(wù)端 逐行寫(xiě)入?yún)?shù)缔御,行數(shù)據(jù)即姓名抬闷。
- 服務(wù)端->客戶端 逐行回寫(xiě)返回值,行數(shù)據(jù)即 sayHi 消息。
3.2 sayHi 迭代升級(jí)
接下來(lái)笤成,我們對(duì) sayHi 功能升級(jí)一下:
- 加“性別”參數(shù)评架,男性稱呼為“xx先生”,女性稱呼為“xx女士”炕泳。
- 加“體重”參數(shù)纵诞,超過(guò) 200 斤則提醒:“你該減肥啦”。
到這兒培遵,前面的一部分規(guī)則就要被打破了浙芙,一次請(qǐng)求不能僅僅帶一個(gè)“姓名”參數(shù),還要帶上性別和體重籽腕,重新制定規(guī)則如下:
- Server 僅提供 sayHi 功能茁裙。
- sayHi 功能三個(gè)參數(shù):姓名、性別节仿、體重晤锥。
- 客戶端->服務(wù)端 逐行寫(xiě)入?yún)?shù),行數(shù)據(jù)按姓名廊宪、性別矾瘾、體重順序帶三個(gè)參數(shù),參數(shù)用“,”分隔箭启。
- 服務(wù)端->客戶端 逐行回寫(xiě)返回值壕翩,行數(shù)據(jù)即 sayHi 消息。
在原來(lái)的基礎(chǔ)上傅寡,做個(gè) V2 版本:
- V2SayHiService.java放妈,sayHi 服務(wù)類。
- V2SayHiParam.java荐操,sayHi 參數(shù)芜抒。
- V2SocketDemoServer.java,服務(wù)端托启。
- V2SocketDemoClient.java宅倒,客戶端。
// V2SayHiService.java
package com.ann.javas.projects.javacores.socket.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Created by liyanling on 2018/9/9.
*/
public class V2SayHiService {
private static Logger logger = LoggerFactory.getLogger(V1SocketDemoServer.class);
private final static String SAY_HI_TEMPLATE = "Hi,%s%s!%s";
public static String sayHi(String params) {
try {
return sayHi(V2SayHiParam.decode(params));
} catch (Throwable t) {
logger.error("caught exception,t=", t);
return "sayHi參數(shù)格式錯(cuò)誤:" + params;
}
}
private static String sayHi(V2SayHiParam v2SayHiParam) {
String name = v2SayHiParam.getName();
String nameSuffix = V2SayHiParam.Gender.BOY == v2SayHiParam.getGender() ? "先生" : "女士";
String weightNotice = v2SayHiParam.getWeight() >= 200 ? "你該減肥啦~" : "";
return String.format(SAY_HI_TEMPLATE, name, nameSuffix, weightNotice);
}
}
// V2SayHiParam.java
package com.ann.javas.projects.javacores.socket.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class V2SayHiParam {
private static Logger logger = LoggerFactory.getLogger(V2SayHiParam.class);
public String name;
public Gender gender;
public int weight;
public enum Gender {
GIRL, BOY
}
public V2SayHiParam() {
}
public V2SayHiParam(String name, Gender gender, int weight) {
this.name = name;
this.gender = gender;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "V2SayHiParam{" + "name='" + name + '\'' + ", gender=" + gender + ", weight=" + weight + '}';
}
// String -> V2SayHiParam
public static V2SayHiParam decode(String params) {
int separatorLocate1 = params.indexOf(',');
int separatorLocate2 = params.indexOf(',', separatorLocate1 + 1);
int length = params.length();
String name = params.substring(0, separatorLocate1);
String gender = params.substring(separatorLocate1 + 1, separatorLocate2);
int weight = Integer.valueOf(params.substring(separatorLocate2 + 1, length));
return new V2SayHiParam(name, Gender.valueOf(gender), weight);
}
// V2SayHiParam -> String
public static String encode(V2SayHiParam v2SayHiParam) {
return v2SayHiParam.getName() + "," + v2SayHiParam.getGender() + "," + v2SayHiParam.getWeight();
}
}
// V2SocketDemoServer.java
package com.ann.javas.projects.javacores.socket.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by liyanling on 2018/6/27.
*/
public class V2SocketDemoServer {
private static Logger logger = LoggerFactory.getLogger(V2SocketDemoServer.class);
public static final int PORT = 9092;
private static int count = 1;
public static void main(String[] args) throws Exception {
startServer();
}
private static void startServer() throws Exception {
try {
ServerSocket serverSocket = new ServerSocket(PORT);
logger.info("{}:服務(wù)端(端口:{})啟動(dòng)監(jiān)聽(tīng)屯耸,等待客戶端連接", count++, PORT);
while (true) {
Socket socket = serverSocket.accept();
logger.info("{}:服務(wù)端已與客戶端建立連接拐迁,開(kāi)始接收客戶端消息...", count++);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String params = reader.readLine();
logger.info("{}:服務(wù)端獲取輸入流,讀取客戶端信息:{}", count++, params);
String sayHiResult = V2SayHiService.sayHi(params);
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
writer.write(sayHiResult + "\n");
writer.flush();
logger.info("{}:服務(wù)端獲取輸出流疗绣,響應(yīng)客戶端請(qǐng)求线召,回寫(xiě):{}", count++, sayHiResult);
writer.close();
reader.close();
socket.close();
// serverSocket.close();// 這里不要把 serverSocket 關(guān)了。還得繼續(xù)監(jiān)聽(tīng)呢
logger.info("{}:服務(wù)端關(guān)閉資源", count++);
logger.info("{}:等待下一個(gè)客戶端連接", count++);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
// V2SocketDemoClient.java
package com.ann.javas.projects.javacores.socket.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* Created by liyanling on 2018/6/27.
*/
public class V2SocketDemoClient {
private static Logger logger = LoggerFactory.getLogger(V2SocketDemoClient.class);
public static final String IP = "127.0.0.1";
public static Scanner scanner;
public static int count = 1;
public static void main(String[] args) throws Exception {
scanner = new Scanner(System.in);
logger.info("{}:demo1-----------",count++);
startClient(IP, V2SocketDemoServer.PORT);
Thread.sleep(1000);
logger.info("{}:demo2-----------",count++);
startClient(IP, V2SocketDemoServer.PORT);
scanner.close();
}
public static void startClient(String IP, int PORT) throws Exception {
try {
logger.info("{}:準(zhǔn)備連接服務(wù)端(IP:{},PORT:{})", count++, IP, PORT);
Socket socket = new Socket(IP, PORT);
logger.info("{}:服務(wù)端已連接成功(IP:{},PORT:{})", count++, IP, PORT);
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
logger.info("{}:客戶端獲取輸出流多矮,向服務(wù)端發(fā)送信息:", count++);
logger.info("{}:請(qǐng)輸入姓名:", count++);
String name = scanner.nextLine();
logger.info("{}:請(qǐng)輸入性別(GIRL / BOY):", count++);
String gender = scanner.nextLine();
logger.info("{}:請(qǐng)輸入體重(單位:斤):", count++);
Integer weight = Integer.valueOf(scanner.nextLine());
V2SayHiParam v2SayHiParam = new V2SayHiParam(name, V2SayHiParam.Gender.valueOf(gender), weight);
writer.write(V2SayHiParam.encode(v2SayHiParam) + "\n");
writer.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
logger.info("{}:客戶端獲取輸入流缓淹,讀取服務(wù)端返回信息:{}", count++, reader.readLine());
reader.close();
writer.close();
socket.close();
logger.info("{}:關(guān)閉資源", count++);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
仍舊是先啟動(dòng)服務(wù)端,再啟動(dòng)客戶端,在客戶端終端輸入:大熊割卖、BOY前酿、220,分別是姓名鹏溯、性別罢维、體重。輸出日志如下:
// V2SocketDemoServer.java 日志
00:30:02.588 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoServer - 1:服務(wù)端(端口:9092)啟動(dòng)監(jiān)聽(tīng)丙挽,等待客戶端連接
00:30:05.646 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoServer - 2:服務(wù)端已與客戶端建立連接肺孵,開(kāi)始接收客戶端消息...
00:30:17.821 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoServer - 3:服務(wù)端獲取輸入流,讀取客戶端信息:大熊,BOY,220
00:30:17.829 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoServer - 4:服務(wù)端獲取輸出流颜阐,響應(yīng)客戶端請(qǐng)求平窘,回寫(xiě):Hi,大熊先生!你該減肥啦~
00:30:17.830 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoServer - 5:服務(wù)端關(guān)閉資源
00:30:17.830 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoServer - 6:等待下一個(gè)客戶端連接
00:30:18.835 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoServer - 7:服務(wù)端已與客戶端建立連接,開(kāi)始接收客戶端消息...
// V2SocketDemoClient.java 日志
00:30:05.639 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 1:demo1-----------
00:30:05.641 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 2:準(zhǔn)備連接服務(wù)端(IP:127.0.0.1,PORT:9092)
00:30:05.646 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 3:服務(wù)端已連接成功(IP:127.0.0.1,PORT:9092)
00:30:05.648 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 4:客戶端獲取輸出流凳怨,向服務(wù)端發(fā)送信息:
00:30:05.648 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 5:請(qǐng)輸入姓名:
大熊
00:30:12.951 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 6:請(qǐng)輸入性別(GIRL / BOY):
BOY
00:30:15.267 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 7:請(qǐng)輸入體重(單位:斤):
220
00:30:17.829 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 8:客戶端獲取輸入流瑰艘,讀取服務(wù)端返回信息:Hi,大熊先生!你該減肥啦~
00:30:17.830 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 9:關(guān)閉資源
00:30:18.834 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 10:demo2-----------
00:30:18.834 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 11:準(zhǔn)備連接服務(wù)端(IP:127.0.0.1,PORT:9092)
00:30:18.835 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 12:服務(wù)端已連接成功(IP:127.0.0.1,PORT:9092)
00:30:18.835 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 13:客戶端獲取輸出流,向服務(wù)端發(fā)送信息:
00:30:18.835 [main] INFO com.ann.javas.projects.javacores.socket.demo.V2SocketDemoClient - 14:請(qǐng)輸入姓名:
3.3 能力擴(kuò)展
再來(lái)個(gè)升級(jí)肤舞,除 sayHi 功能外紫新,還需要:
- 新增 locate 功能,用于定位用戶位置李剖。
- 新增 adsRecommendation 功能芒率,用于推送廣告。
前面的實(shí)現(xiàn)默認(rèn)只提供 sayHi 功能篙顺,要新增 locate 和 adsRecommendation 支持偶芍,再次修改規(guī)則為:
- Server 僅提供 sayHi、locate德玫、adsRecommendation功能匪蟀。
- sayHi 功能三個(gè)參數(shù):姓名、性別化焕、體重萄窜。
- locate 和 adsRecommendation 無(wú)參數(shù)铃剔。
- 客戶端->服務(wù)端 逐行寫(xiě)入功能名和參數(shù)撒桨,參數(shù)仍舊按順序“,”分隔,功能名和參數(shù)之間用“|”分隔键兜。
- 服務(wù)端->客戶端 逐行回寫(xiě)返回值凤类。
類定義和源碼如下:
- V3DemoService.java,demo 服務(wù)類普气,提供 sayHi谜疤、locate、adsRecommendation 功能。
- V3SayHiParam.java夷磕,sayHi 參數(shù)履肃。
- V3ocketDemoServer.java,服務(wù)端坐桩。
- V3SocketDemoClient.java尺棋,客戶端。
// V3DemoService.java
package com.ann.javas.projects.javacores.socket.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
/**
* Created by liyanling on 2018/9/9.
*/
public class V3DemoService {
private static Logger logger = LoggerFactory.getLogger(V1SocketDemoServer.class);
private final static String SAY_HI_TEMPLATE = "Hi,%s%s!%s";
private final static List<String> CITIES = Arrays.asList("上海", "北京", "廣州", "成都", "內(nèi)蒙古", "香港", "河北", "云南");
private final static List<String> ADS = Arrays.asList("坐紅旗車绵跷,走中國(guó)路", "要想皮膚好膘螟,早晚用大寶", "喝匯源果汁,走健康之路", "送禮就送腦白金",
"飄柔碾局,就是這么自信");
public static String locate() {
int citySize = CITIES.size();
int randomIndex = Math.abs((new Random(System.currentTimeMillis())).nextInt()) % citySize;
return CITIES.get(randomIndex);
}
public static String adsRecommendation() {
int adsSize = ADS.size();
int randomIndex = Math.abs((new Random(System.currentTimeMillis())).nextInt()) % adsSize;
return ADS.get(randomIndex);
}
public static String sayHi(String params) {
try {
return sayHi(V3SayHiParam.decode(params));
} catch (Throwable t) {
logger.error("caught exception,t=", t);
return "sayHi參數(shù)格式錯(cuò)誤:" + params;
}
}
private static String sayHi(V3SayHiParam v3SayHiParam) {
String name = v3SayHiParam.getName();
String nameSuffix = V3SayHiParam.Gender.BOY == v3SayHiParam.getGender() ? "先生" : "女士";
String weightNotice = v3SayHiParam.getWeight() >= 200 ? "你該減肥啦~" : "";
return String.format(SAY_HI_TEMPLATE, name, nameSuffix, weightNotice);
}
public static String doSomething(String params) {
try {
int separatorLocate = params.indexOf('|');
int length = params.length();
String function = params.substring(0, separatorLocate);
String functionParams = params.substring(separatorLocate + 1, length);
logger.info("demoService function:{} functionParams:{}",function,functionParams);
if ("sayHi".equals(function)) {
return sayHi(functionParams);
} else if ("locate".equals(function)) {
return locate();
} else if ("adsRecommendation".equals(function)) {
return adsRecommendation();
} else {
return "不支持" + function + "功能";
}
} catch (Throwable t) {
logger.error("caught error,t=", t);
return "參數(shù)格式錯(cuò)誤:" + params;
}
}
}
// V3SayHiParam.java
package com.ann.javas.projects.javacores.socket.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class V3SayHiParam {
private static Logger logger = LoggerFactory.getLogger(V3SayHiParam.class);
public String name;
public Gender gender;
public int weight;
public enum Gender {
GIRL, BOY
}
public V3SayHiParam() {
}
public V3SayHiParam(String name, Gender gender, int weight) {
this.name = name;
this.gender = gender;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "V3SayHiParam{" + "name='" + name + '\'' + ", gender=" + gender + ", weight=" + weight + '}';
}
// String -> V3SayHiParam
public static V3SayHiParam decode(String params) {
int separatorLocate1 = params.indexOf(',');
int separatorLocate2 = params.indexOf(',', separatorLocate1 + 1);
int length = params.length();
String name = params.substring(0, separatorLocate1);
String gender = params.substring(separatorLocate1 + 1, separatorLocate2);
int weight = Integer.valueOf(params.substring(separatorLocate2 + 1, length));
return new V3SayHiParam(name, Gender.valueOf(gender), weight);
}
// V3SayHiParam -> String
public static String encode(V3SayHiParam v3SayHiParam) {
return v3SayHiParam.getName() + "," + v3SayHiParam.getGender() + "," + v3SayHiParam.getWeight();
}
}
// V3ocketDemoServer.java
package com.ann.javas.projects.javacores.socket.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by liyanling on 2018/6/27.
*/
public class V3SocketDemoServer {
private static Logger logger = LoggerFactory.getLogger(V3SocketDemoServer.class);
public static final int PORT = 9093;
private static int count = 1;
public static void main(String[] args) throws Exception {
startServer();
}
private static void startServer() throws Exception {
try {
ServerSocket serverSocket = new ServerSocket(PORT);
logger.info("{}:服務(wù)端(端口:{})啟動(dòng)監(jiān)聽(tīng)荆残,等待客戶端連接", count++, PORT);
while (true) {
Socket socket = serverSocket.accept();
logger.info("{}:服務(wù)端已與客戶端建立連接,開(kāi)始接收客戶端消息...", count++);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String params = reader.readLine();
logger.info("{}:服務(wù)端獲取輸入流净当,讀取客戶端信息:{}", count++, params);
String sayHiResult = V3DemoService.doSomething(params);
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
writer.write(sayHiResult + "\n");
writer.flush();
logger.info("{}:服務(wù)端獲取輸出流内斯,響應(yīng)客戶端請(qǐng)求,回寫(xiě):{}", count++, sayHiResult);
writer.close();
reader.close();
socket.close();
// serverSocket.close();// 這里不要把 serverSocket 關(guān)了像啼。還得繼續(xù)監(jiān)聽(tīng)呢
logger.info("{}:服務(wù)端關(guān)閉資源", count++);
logger.info("{}:等待下一個(gè)客戶端連接", count++);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
// V3SocketDemoClient.java
package com.ann.javas.projects.javacores.socket.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* Created by liyanling on 2018/6/27.
*/
public class V3SocketDemoClient {
private static Logger logger = LoggerFactory.getLogger(V3SocketDemoClient.class);
public static final String IP = "127.0.0.1";
public static Scanner scanner;
public static int count = 1;
public static void main(String[] args) throws Exception {
scanner = new Scanner(System.in);
logger.info("{}:demo1-----------", count++);
startClient(IP, V3SocketDemoServer.PORT);
Thread.sleep(1000);
logger.info("{}:demo2-----------", count++);
startClient(IP, V3SocketDemoServer.PORT);
Thread.sleep(1000);
logger.info("{}:demo3-----------", count++);
startClient(IP, V3SocketDemoServer.PORT);
scanner.close();
}
public static void startClient(String IP, int PORT) throws Exception {
try {
logger.info("{}:準(zhǔn)備連接服務(wù)端(IP:{},PORT:{})", count++, IP, PORT);
Socket socket = new Socket(IP, PORT);
logger.info("{}:服務(wù)端已連接成功(IP:{},PORT:{})", count++, IP, PORT);
PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
logger.info("{}:客戶端獲取輸出流嘿期,向服務(wù)端發(fā)送信息:", count++);
logger.info("{}:請(qǐng)輸入功能名(sayHi / locate / adsRecommendation):", count++);
String function = scanner.nextLine();
String line = "";
if ("sayHi".equals(function)) {
logger.info("{}:請(qǐng)輸入姓名:", count++);
String name = scanner.nextLine();
logger.info("{}:請(qǐng)輸入性別(GIRL / BOY):", count++);
String gender = scanner.nextLine();
logger.info("{}:請(qǐng)輸入體重(單位:斤):", count++);
Integer weight = Integer.valueOf(scanner.nextLine());
V3SayHiParam v3SayHiParam = new V3SayHiParam(name, V3SayHiParam.Gender.valueOf(gender), weight);
line = function + "|" + V3SayHiParam.encode(v3SayHiParam);
} else {
line = function + "|";
}
writer.write(line + "\n");
writer.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
logger.info("{}:客戶端獲取輸入流,讀取服務(wù)端返回信息:{}", count++, reader.readLine());
reader.close();
writer.close();
socket.close();
logger.info("{}:關(guān)閉資源", count++);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
分別測(cè)試 locate埋合、adsRecommendation备徐、sayHi 功能,輸出日志如下:
// V3SocketDemoServer.java 日志
00:31:37.510 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 1:服務(wù)端(端口:9093)啟動(dòng)監(jiān)聽(tīng)甚颂,等待客戶端連接
00:31:51.403 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 2:服務(wù)端已與客戶端建立連接蜜猾,開(kāi)始接收客戶端消息...
00:32:00.525 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 3:服務(wù)端獲取輸入流,讀取客戶端信息:locate|
00:32:00.532 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoServer - demoService function:locate functionParams:
00:32:00.534 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 4:服務(wù)端獲取輸出流振诬,響應(yīng)客戶端請(qǐng)求蹭睡,回寫(xiě):上海
00:32:00.535 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 5:服務(wù)端關(guān)閉資源
00:32:00.535 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 6:等待下一個(gè)客戶端連接
00:32:01.539 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 7:服務(wù)端已與客戶端建立連接,開(kāi)始接收客戶端消息...
00:32:08.982 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 8:服務(wù)端獲取輸入流赶么,讀取客戶端信息:adsRecommendation|
00:32:08.982 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoServer - demoService function:adsRecommendation functionParams:
00:32:08.983 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 9:服務(wù)端獲取輸出流肩豁,響應(yīng)客戶端請(qǐng)求,回寫(xiě):飄柔辫呻,就是這么自信
00:32:08.983 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 10:服務(wù)端關(guān)閉資源
00:32:08.983 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 11:等待下一個(gè)客戶端連接
00:32:09.989 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 12:服務(wù)端已與客戶端建立連接清钥,開(kāi)始接收客戶端消息...
00:32:25.251 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 13:服務(wù)端獲取輸入流,讀取客戶端信息:sayHi|小明,GIRL,210
00:32:25.252 [main] INFO com.ann.javas.projects.javacores.socket.demo.V1SocketDemoServer - demoService function:sayHi functionParams:小明,GIRL,210
00:32:25.254 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 14:服務(wù)端獲取輸出流放闺,響應(yīng)客戶端請(qǐng)求祟昭,回寫(xiě):Hi,小明女士!你該減肥啦~
00:32:25.254 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 15:服務(wù)端關(guān)閉資源
00:32:25.255 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoServer - 16:等待下一個(gè)客戶端連接
// V3SocketDemoClient.java 日志
00:31:51.398 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 1:demo1-----------
00:31:51.399 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 2:準(zhǔn)備連接服務(wù)端(IP:127.0.0.1,PORT:9093)
00:31:51.404 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 3:服務(wù)端已連接成功(IP:127.0.0.1,PORT:9093)
00:31:51.406 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 4:客戶端獲取輸出流,向服務(wù)端發(fā)送信息:
00:31:51.407 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 5:請(qǐng)輸入功能名(sayHi / locate / adsRecommendation):
locate
00:32:00.535 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 6:客戶端獲取輸入流怖侦,讀取服務(wù)端返回信息:上海
00:32:00.535 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 7:關(guān)閉資源
00:32:01.538 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 8:demo2-----------
00:32:01.538 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 9:準(zhǔn)備連接服務(wù)端(IP:127.0.0.1,PORT:9093)
00:32:01.539 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 10:服務(wù)端已連接成功(IP:127.0.0.1,PORT:9093)
00:32:01.539 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 11:客戶端獲取輸出流篡悟,向服務(wù)端發(fā)送信息:
00:32:01.539 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 12:請(qǐng)輸入功能名(sayHi / locate / adsRecommendation):
adsRecommendation
00:32:08.983 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 13:客戶端獲取輸入流谜叹,讀取服務(wù)端返回信息:飄柔,就是這么自信
00:32:08.983 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 14:關(guān)閉資源
00:32:09.988 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 15:demo3-----------
00:32:09.988 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 16:準(zhǔn)備連接服務(wù)端(IP:127.0.0.1,PORT:9093)
00:32:09.991 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 17:服務(wù)端已連接成功(IP:127.0.0.1,PORT:9093)
00:32:09.991 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 18:客戶端獲取輸出流搬葬,向服務(wù)端發(fā)送信息:
00:32:09.991 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 19:請(qǐng)輸入功能名(sayHi / locate / adsRecommendation):
sayHi
00:32:12.661 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 20:請(qǐng)輸入姓名:
小明
00:32:16.647 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 21:請(qǐng)輸入性別(GIRL / BOY):
GIRL
00:32:22.979 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 22:請(qǐng)輸入體重(單位:斤):
210
00:32:25.254 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 23:客戶端獲取輸入流荷腊,讀取服務(wù)端返回信息:Hi,小明女士!你該減肥啦~
00:32:25.254 [main] INFO com.ann.javas.projects.javacores.socket.demo.V3SocketDemoClient - 24:關(guān)閉資源
3.4 小結(jié)
從 V1 到 V2 再到 V3,你會(huì)發(fā)現(xiàn)一個(gè)很痛的點(diǎn)是:隨便來(lái)個(gè)新需求急凰,都會(huì)導(dǎo)致客戶端與服務(wù)端的“大換血”停局,可擴(kuò)展性非常差,說(shuō)到底香府,還是“序列化與反序列化規(guī)則”做的不夠好董栽,單單行數(shù)據(jù)、","分隔企孩、"|"分隔 這種“口頭”約定怔蚌,是遠(yuǎn)遠(yuǎn)不夠的爹土。
從這個(gè)角度入手,我們來(lái)看 thrift 是怎么做的。
下接:thrift 入門(mén)(2/2)
PS:我也不想拆饱溢,但是放一起文章太長(zhǎng)無(wú)法發(fā)布蝶俱。瘦馍。碳褒。