- 禁止白嫖 T > T
- 點(diǎn)點(diǎn)贊唄
JavaEE & UDP簡易翻譯服務(wù)器 & 網(wǎng)絡(luò)編程示例2 & TCP回顯服務(wù)器,回顯客戶端
1. UDP簡易翻譯服務(wù)器
- 這個(gè)翻譯器主要是在上一章的回顯服務(wù)器和回顯客戶端上進(jìn)行修改
- 修改了計(jì)算響應(yīng)的過程,即process方法
1.1 重寫方法
- 重寫方法是Java中的一種重要手段
- 指在一個(gè)類的子類里,對父類的一個(gè)方法進(jìn)行重新定義!
- 而父類的權(quán)限級別要大于等于子類~ 【除了private】
- 向上轉(zhuǎn)型,父類對象調(diào)用的是子類重寫的方法
- 動態(tài)綁定,子類對象調(diào)用父類的方法,但是該方法里嵌套調(diào)用了重寫方法
- 可以理解為super幫助構(gòu)造父類時(shí),實(shí)現(xiàn)動態(tài)綁定重寫方法~
- 而他的"super"跟子類實(shí)例化父類對象別無二致
- 也可以看成向上轉(zhuǎn)型 + 動態(tài)綁定
- 而構(gòu)造子類必然要以子類構(gòu)造父類,這個(gè)過程才是重寫方法的本質(zhì)
子類繼承父類,并為幫助父類構(gòu)造:
Ctrl + O 重寫process方法:
- 注意:父類的process權(quán)限要足夠高才行~
- 注解的作用就是警示你寫對代碼!
- 不至于寫了半天出錯(cuò)了,找不到代碼錯(cuò)誤的地方~
1.2 設(shè)計(jì)翻譯服務(wù)器
- 對于這個(gè)簡單的翻譯器,我只需要將一個(gè)個(gè)單詞和一個(gè)個(gè)含義對應(yīng)好,并且保存起來,每次查詢的時(shí)候就在里面找對應(yīng)關(guān)系~
- 而這也是一些翻譯器的基本原理,一個(gè)單詞字符串對象 + 映射對象占內(nèi)存也不過1KB,100w個(gè)單詞 也約等于 10億字節(jié),也就是1GB
快速記憶小技巧:
- Thousand ==> 1KB
- Million ==> 1MB
- Billion ==> 1GB
而符合這種需求的數(shù)據(jù)結(jié)構(gòu)就是:哈希表
- 快速查詢
- 映射關(guān)系 — Map
- 找不到的時(shí)候get返回null~
1.3 書寫main方法啟動服務(wù)器測試
public static void main(String[] args) throws IOException {
UDPDictServer udpDictServer = new UDPDictServer(9090);
udpDictServer.start();
}
- 測試正常,如果你要做大這個(gè)翻譯器,可以多點(diǎn)單詞~
2. TCP網(wǎng)絡(luò)編程示例
- 這個(gè)示例跟UDP的作用是一模一樣的~
- 只不過這次用到的協(xié)議是TCP
如果沒有學(xué)UDP建議先學(xué)UDP!
傳送門:JavaEE & 網(wǎng)絡(luò)編程示例1 & UDP套接字?jǐn)?shù)據(jù)報(bào)編程 == 一發(fā)一收
2.1 ServerSocket 與 Socket
- ServerSocket : 服務(wù)器使用的socket對象
- Socket : 既會給客戶端使用,也會給服務(wù)器使用的socket對象
2.1.1 ServerSocket的構(gòu)造方法
方法名 | 方法說明 |
---|---|
ServerSocket(int port) | 服務(wù)器socket對象構(gòu)造方法【指定端口號】 |
- 服務(wù)器一定要綁定具體的端口號~
2.1.2 ServerSocket的核心方法
方法名 | 方法說明 |
---|---|
Socket accept() | 建立連接 |
void close() | 關(guān)閉socket【文件】對象 |
-
socket對象如果周期很長,一般是不需要關(guān)閉的,因?yàn)殛P(guān)閉時(shí)一般意味著程序終結(jié),資源自動回收
-
accept方法是跟客戶端建立連接的方法,后面寫代碼時(shí)重點(diǎn)說明
- TCP有連接的特點(diǎn)就在這~
- 連接一旦建立,發(fā)送與接受都是能確定是否成功的~
-
返回的Socket對象,即可以代表服務(wù)器與客戶端的連接,也可以代表客戶端與服務(wù)器的連接~
2.1.3 Socket的構(gòu)造方法
方法名 | 方法說明 |
---|---|
Socket(String host, int port) | 指定IP與端口號 |
2.1.4 Socket的核心方法
方法名 | 方法說明 |
---|---|
InetAddress getInetAddress() | 返回Socket對象的IP地址 |
InputStreamgetInputStream() | 返回控制這個(gè)socket文件的輸入流 |
OutputStreamgetOutputStream() | 返回控制這個(gè)socket文件的輸出流 |
-
輸入輸出流特別重要
- 作用于他們就相當(dāng)于作用于網(wǎng)卡
- 有了他們,我們就可以進(jìn)行數(shù)據(jù)的溝通了!
而他們對應(yīng)的是字節(jié)流 ==> TCP面向字節(jié)流
2.1.5 長連接與短連接
- 服務(wù)器收到一個(gè)請求,就返回一個(gè)響應(yīng),然后斷開連接
這就是短連接
- 服務(wù)器收到一個(gè)客戶端的多條請求,再一起返回響應(yīng),然后斷開連接
這就是長連接
- 而長短連接都是可能出現(xiàn)的!
對于接下來的代碼,考慮到多條請求,我做出如下規(guī)定:
- 多條請求之間以空白符分割~
- 每一個(gè)請求都是字符串
對于后面的代碼設(shè)計(jì),是按長連接來設(shè)計(jì)的!
2.2 基于TCP 的 Socket 寫一個(gè)簡單的客戶端服務(wù)器程序
同樣的,分為服務(wù)器與客戶端:
- 同樣也可以做成翻譯器~
2.2.1 TCP回顯服務(wù)器
- 可能在前面會很懵,但是后面的圖解會串聯(lián)起來,可能會讓你理解起來更加容易!
2.2.1.1 ServerScoket對象與構(gòu)造方法
- 這個(gè)對象是專門給服務(wù)器用的,通過這個(gè)對象來獲得客戶端【發(fā)來請求的機(jī)器】的【socket管理員】
- 即連接
- 而這個(gè)socket并不是用于操縱socket文件~
public class TCPEchoServer {
private ServerSocket serverSocket = null;
public TCPEchoServer(int port) throws IOException {
this.serverSocket = new ServerSocket(port);
}
}
2.2.1.2 start啟動方法
- Socket對象被動構(gòu)造
- accept的話,則是接受客戶端的輸出
public void start() throws IOException {
System.out.println("服務(wù)器啟動!");
while(true) {
Socket clientSocket = serverSocket.accept();//建立連接
processConnect(clientSocket); // 連接成功后進(jìn)行一些操作~
}
}
你可能有一個(gè)疑惑:
- 發(fā)送信息要用到客戶端socket對象這沒問題
- 但是,接受到的信息為什么也用到客戶端的socket對象?
- 服務(wù)器收到的信息可能來自很多地方,并且都是字節(jié)流,不像數(shù)據(jù)報(bào)一塊一塊的
-
服務(wù)器的輸出流數(shù)據(jù)來源來自客戶端的輸入流
- 輸入:我輸入給別人
- 輸出:別人輸出給我
如以下關(guān)系:
-
socket文件內(nèi)有兩塊緩沖區(qū)
- 接受緩沖區(qū)
- 發(fā)送緩沖區(qū)
- 即,只需要知道一方,就可以完成讀寫~
- 而那一方應(yīng)該是“對方”
2.2.1.3 processConnect細(xì)節(jié)
public void processConnect(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 客戶端上線\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort());
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
Scanner scanner = new Scanner(inputStream);
//這個(gè)對象將自動讀取【字節(jié)流/其他流】為【字符流】
PrintWriter printWriter = new PrintWriter(outputStream);
//這個(gè)對象將自動寫入【字符流】化為【字節(jié)流/其他流】
while(scanner.hasNext()) {//死循環(huán)了,除非客戶端關(guān)閉~
String request = scanner.next();//nextLine有坑很煩
String response = process(request);
printWriter.println(response);//換行記得加~
printWriter.flush();
System.out.printf("[%s : %d] 發(fā)來請求:%s, 將對其返回響應(yīng):%s\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
System.out.printf("[%s:%d] 客戶端下線\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort());
} catch (IOException e) {
e.printStackTrace();
} finally {
clientSocket.close();
}
}
講解:
- 拋異常操作自己去拋
- 客戶端上線打印日志
- 打開輸入輸出流
-
字節(jié)流轉(zhuǎn)換器
- 通過這兩個(gè)對象,實(shí)現(xiàn)傳輸過程
- 如何理解輸入與輸出流?
- 建立長連接
-
獲得請求并計(jì)算
-
回顯計(jì)算:
-
public String process(String request) { return request; }
-
這里的flush特別重要,當(dāng)你flush后,才能算真正輸出了~
- 即,對方就能接受你的輸出,并且用你的輸入流去獲得~
- scanner在等待輸入呢,flush就相當(dāng)于踢了它一腳~
-
打印日志~
-
2.2.2 TCP回顯客戶端
2.2.2.1 指定服務(wù)器Socket對象與構(gòu)造方法
- 會用這個(gè)對象就行了,至于這么通過端口獲得對應(yīng)的輸入輸出流的不得而知~
public class TCPEchoClient {
Socket socket = null;//此socket對象代表了服務(wù)器的socket文件~
public TCPEchoClient(String host, int port) throws IOException {
this.socket = new Socket(host, port);
//這個(gè)過程客戶端主動連接了服務(wù)器,這樣就可以被服務(wù)器的accept接受~
//而服務(wù)器的accept返回的對象,默認(rèn)填上了客戶端的IP與端口號
}
}
2.2.2.2 start啟動方法
public void start() {
Scanner scanner = new Scanner(System.in);
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
PrintWriter printWriter = new PrintWriter(outputStream);
Scanner scannerFromServer = new Scanner(inputStream);
while(true) {
System.out.print("-> ");
String request = scanner.nextLine();
printWriter.println(request);
printWriter.flush();
String response = scannerFromServer.next();//如果為空,則需要等待對方flush
System.out.printf("[%s : %d] 收到請求:%s, 返回響應(yīng):%s\n", socket.getInetAddress().toString(),
socket.getPort(), request, response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
講解:
-
獲得服務(wù)器的輸入輸出流
- 同樣的方式~
- 同樣的方式~
-
字節(jié)流轉(zhuǎn)換器
-
多次請求響應(yīng)
- “寫”的時(shí)候,記得帶上“回車空白符”,分割每個(gè)響應(yīng)
- 并打印日志
- “寫”的時(shí)候,記得帶上“回車空白符”,分割每個(gè)響應(yīng)
可能會更加難理解,但是也簡化了UDP反復(fù)制作“數(shù)據(jù)報(bào)”的過程,并且是有連接的~
2.2.2.3 補(bǔ)充說明
長連接是指一個(gè)服務(wù)器一段時(shí)間內(nèi)反復(fù)服務(wù)一個(gè)客戶端
- 客戶端socket這段時(shí)間內(nèi)一直處于打開狀態(tài),并不斷發(fā)送請求得到響應(yīng)~
并不是一次性發(fā)送多條請求,得到多條響應(yīng)~
- 錯(cuò)誤代碼:
- 這段代碼確實(shí)可以完成一次上面的流程
- 但是有不可修復(fù)的bug!
2.2.3 測試與總結(jié)
-
服務(wù)器進(jìn)程:
- 端口號隨便寫的(空余的)
public static void main(String[] args) throws IOException {
TCPEchoServer tcpEchoServer = new TCPEchoServer(9090);
tcpEchoServer.start();
}
-
客戶端進(jìn)程:
- IP自己去查~
public static void main(String[] args) throws IOException {
TCPEchoClient tcpEchoClient = new TCPEchoClient("10.61.10.239", 9090);
tcpEchoClient.start();
}
運(yùn)行測試(“死鎖”版):
運(yùn)行測試(正常版):
- 對于一次性輸入多條請求,獲得多個(gè)響應(yīng)也只能這樣~
總結(jié)圖示:
- 下一次控制臺輸入,又是一次輪回~
2.2.4 修復(fù)缺陷
- 運(yùn)行兩個(gè)客戶端:
你可能已經(jīng)發(fā)現(xiàn)了,這個(gè)代碼還存在一個(gè)缺陷
- 就是這個(gè)服務(wù)器,在一段時(shí)間內(nèi),“搶到服務(wù)器”的客戶端不關(guān)閉,服務(wù)器就無法服務(wù)其他客戶端。
- 那怎么辦!
其實(shí),很簡單
- 線程池 / 循環(huán)建立多線程
- 只要讓服務(wù)器并發(fā)的去執(zhí)行任務(wù)就ok了!
但是,客戶端達(dá)到一定數(shù)量會導(dǎo)致系統(tǒng)崩了或者客戶端未能“同時(shí)”受到服務(wù)
- 即,肉眼可見的不并發(fā)
代碼改進(jìn):
- 運(yùn)行結(jié)果:
線程池法:
文章到此結(jié)束!謝謝觀看
可以叫我 小馬,我可能寫的不好或者有錯(cuò)誤,但是一起加油鴨??!文章來源:http://www.zghlxwxcb.cn/news/detail-415719.html后續(xù)網(wǎng)絡(luò)編程的博客會陸續(xù)更新,敬請期待!文章來源地址http://www.zghlxwxcb.cn/news/detail-415719.html
到了這里,關(guān)于JavaEE & UDP簡易翻譯服務(wù)器 & 網(wǎng)絡(luò)編程示例2 & TCP回顯服務(wù)器,回顯客戶端的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!