網(wǎng)上哪里可以免費(fèi)學(xué)編程百度搜索關(guān)鍵詞排名優(yōu)化技術(shù)
????????回顯服務(wù)器表示客戶端傳來(lái)的請(qǐng)求是什么,服務(wù)器就回應(yīng)什么,客戶端不用對(duì)傳來(lái)的數(shù)據(jù)進(jìn)行處理,主要是為了熟悉TCP協(xié)議提供的API的使用
對(duì)于代碼的解釋全作為注釋寫(xiě)在了代碼上,推薦復(fù)制到編程軟件中查看
UDP協(xié)議實(shí)現(xiàn)回顯服務(wù)器可以看UDP數(shù)據(jù)報(bào)網(wǎng)絡(luò)編程(實(shí)現(xiàn)簡(jiǎn)單的回顯服務(wù)器,客戶端)
其中涉及到的線程池的內(nèi)容可以看通過(guò)標(biāo)準(zhǔn)庫(kù)創(chuàng)建線程池
idea開(kāi)啟多個(gè)客戶端的方法可以看idea如何開(kāi)啟多個(gè)客戶端(一個(gè)代碼開(kāi)啟多個(gè)客戶端運(yùn)行)
服務(wù)器代碼
package TcpEcho;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** Created with IntelliJ IDEA.* Description:* User: wuyulin* Date: 2023-08-10* Time: 16:25*/
//TCP協(xié)議 回顯服務(wù)器(客戶端傳來(lái)的請(qǐng)求是什么,服務(wù)器就回應(yīng)什么)
//TCP協(xié)議網(wǎng)絡(luò)編程兩個(gè)最主要的類是ServerSocket和Socket
public class TcpEchoServer {//線程池對(duì)象ExecutorService executorService= null;//ServerSocket類內(nèi)置了一個(gè)隊(duì)列,在內(nèi)核中客戶端和服務(wù)器嘗試建立連接,連接建立完了以后得到的連接對(duì)象就存入了隊(duì)列中//客戶端和服務(wù)器嘗試建立連接,進(jìn)行一系列的數(shù)據(jù)交互稱為“握手”,這個(gè)過(guò)程建立完了以后,連接就建立好了private ServerSocket serverSocket=null;//在構(gòu)造方法中實(shí)例化serverSocket對(duì)象,port參數(shù)表示在創(chuàng)建服務(wù)器時(shí)要指定服務(wù)器的ip地址TcpEchoServer(int port) throws IOException {serverSocket=new ServerSocket(port);executorService=Executors.newCachedThreadPool(); //實(shí)例化一個(gè)線程數(shù)目動(dòng)態(tài)變化的線程池}public void start() throws IOException {//寫(xiě)日志,方便觀察當(dāng)前運(yùn)行狀態(tài)System.out.println("服務(wù)器啟動(dòng)");//服務(wù)器需要不停的去接收客戶端傳來(lái)的請(qǐng)求并做出回應(yīng),所以需要一個(gè)死循環(huán)while (true){//TCP是有連接的,需要處理服務(wù)器與客戶端之間的連接,而UDP是無(wú)連接的,不需要處理//處理服務(wù)器與客戶端之間的連接//通過(guò)調(diào)用accept方法,取出serverSocket對(duì)象內(nèi)置的隊(duì)列中的連接對(duì)象(Socket對(duì)象)//當(dāng)隊(duì)列中沒(méi)有Socket連接對(duì)象時(shí),就會(huì)在accept方法這里進(jìn)入阻塞等待,直到取得連接對(duì)象為止Socket socket=serverSocket.accept();//對(duì)獲得的服務(wù)器與客戶端之間的連接對(duì)象進(jìn)行處理//采用當(dāng)前的編寫(xiě)方式會(huì)發(fā)現(xiàn)當(dāng)有多個(gè)客戶端來(lái)連接服務(wù)器并發(fā)出請(qǐng)求時(shí),服務(wù)器不能同時(shí)處理//因?yàn)樵趆andleConnection方法中會(huì)一直循環(huán)處理客戶端發(fā)出的請(qǐng)求//而當(dāng)前編寫(xiě)方式要等handleConnection方法執(zhí)行完畢才能去取出下一個(gè)連接對(duì)象,才能處理下一個(gè)客戶端發(fā)出的請(qǐng)求// handleConnection(socket);//解決辦法:用主線程去調(diào)用accept方法取出連接對(duì)象,每取出一個(gè)連接對(duì)象就創(chuàng)建一個(gè)線程去處理連接對(duì)象對(duì)應(yīng)的客戶端發(fā)出的請(qǐng)求//但下面這個(gè)創(chuàng)建線程來(lái)處理socket連接對(duì)象的代碼也注釋掉了,這是因?yàn)橐怯泻芏嗫蛻舳藖?lái)對(duì)服務(wù)器發(fā)出請(qǐng)求,就會(huì)涉及到大量//線程的創(chuàng)建和銷(xiāo)毀,這是很消耗資源的,所以應(yīng)該采用線程池來(lái)處理socket連接對(duì)象
// Thread t=new Thread(()->{
// try {
// handleConnection(socket);
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// });
//
// t.start();//在該服務(wù)器類的成員屬性中添加上線程池//將處理連接對(duì)象的任務(wù)通過(guò)submit添加到線程池的阻塞隊(duì)列中(線程池中的線程會(huì)將添加到阻塞隊(duì)列中的任務(wù)進(jìn)行處理)executorService.submit(new Runnable() {@Overridepublic void run() {try {handleConnection(socket);} catch (IOException e) {throw new RuntimeException(e);}}});}}//處理客戶端和服務(wù)器之間的連接,并且完成數(shù)據(jù)的接收,處理,回應(yīng)public void handleConnection(Socket socket) throws IOException {System.out.printf("客戶端上線 %s %d\n",socket.getInetAddress().toString(),socket.getPort());//由于TCP協(xié)議的網(wǎng)絡(luò)編程,進(jìn)行數(shù)據(jù)傳輸?shù)幕締挝皇亲止?jié),所以需要inputstream和outputstream類型的對(duì)象來(lái)處理字節(jié)流的數(shù)據(jù)//socket網(wǎng)絡(luò)連接對(duì)象可以直接實(shí)例化InputStream和OutputStream對(duì)象來(lái)對(duì)網(wǎng)絡(luò)要傳輸?shù)淖止?jié)數(shù)據(jù)進(jìn)行處理//采用try(){}的結(jié)構(gòu),在()中實(shí)例化inputStream和outputStream對(duì)象,可以在{}中的程序執(zhí)行結(jié)束了以后自動(dòng)關(guān)閉這兩個(gè)對(duì)象(防止內(nèi)存泄漏)try(InputStream inputStream=socket.getInputStream();OutputStream outputStream=socket.getOutputStream()){//處理客戶端傳來(lái)的請(qǐng)求//客戶端傳來(lái)的請(qǐng)求可能不止一個(gè),所以需要一直死循環(huán)去一直讀取客戶端傳來(lái)的請(qǐng)求while (true){//InputStream直接使用不是很方便,包裝一層Scanner使用Scanner scanner=new Scanner(inputStream);//當(dāng)沒(méi)有讀取到請(qǐng)求了(客戶端傳來(lái)的請(qǐng)求讀取完了)就跳出循環(huán),結(jié)束方法,去拿下一個(gè)網(wǎng)絡(luò)連接對(duì)象進(jìn)行處理//當(dāng)客戶端還沒(méi)有傳入請(qǐng)求時(shí)就會(huì)進(jìn)入阻塞等待,直到客戶端傳入請(qǐng)求或客戶端下線才恢復(fù)if(!scanner.hasNext()){//只有當(dāng)客戶端下線才會(huì)執(zhí)行這段代碼System.out.printf("%s %d客戶端下線",socket.getInetAddress().toString(),socket.getPort());break;}//客戶端中還有請(qǐng)求,讀取客服端的請(qǐng)求到request字符串中//這里默認(rèn)了客戶端傳來(lái)的請(qǐng)求是字符串,按照字符串的形式來(lái)處理請(qǐng)求String request=scanner.next();//對(duì)客戶端傳來(lái)的請(qǐng)求進(jìn)行處理并作出響應(yīng)String response=handle(request);//將響應(yīng)傳遞給客戶端//直接使用outputStream比較麻煩,包裝一層PrintWriter進(jìn)行使用PrintWriter printWriter=new PrintWriter(outputStream);//要調(diào)用println方法將回應(yīng)傳遞給客戶端才行,因?yàn)閜rintln方法在傳遞了一個(gè)回應(yīng)后會(huì)換行,而在客戶端中就剛好可以用next方法讀取//(next方法不會(huì)讀取空白符,而換行屬于空白符)printWriter.println(request);//由于IO操作很消耗資源,所以在調(diào)用println方法向客戶端發(fā)送數(shù)據(jù)時(shí)會(huì)先將數(shù)據(jù)放到一個(gè)內(nèi)存緩沖區(qū)中,等到有一定的內(nèi)容再一起發(fā)送//所以為了保證回應(yīng)被及時(shí)的發(fā)到客戶端,就要對(duì)內(nèi)存緩沖區(qū)進(jìn)行刷新printWriter.flush();//寫(xiě)日志,方便觀察當(dāng)前運(yùn)行狀態(tài)System.out.printf("[%s,%d] request:%s response:%s\n",socket.getInetAddress().toString(),socket.getPort(),request,response);}} catch (IOException e) {throw new RuntimeException(e);}finally { //Socket連接對(duì)象會(huì)被不停的從serverSocket對(duì)象中取出,所以在使用完畢Socket連接對(duì)象以后應(yīng)該調(diào)用close方法關(guān)閉,否則會(huì)出現(xiàn)內(nèi)存泄漏socket.close();}}//對(duì)客戶端傳來(lái)的請(qǐng)求進(jìn)行處理public String handle(String request){return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer=new TcpEchoServer(1010);tcpEchoServer.start();}
}
客戶端代碼
package TcpEcho;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;/*** Created with IntelliJ IDEA.* Description:* User: wuyulin* Date: 2023-08-10* Time: 19:26*/
//TCP回顯客戶端
public class TcpEchoClient {//客戶端需要實(shí)例化一個(gè)連接對(duì)象Socket來(lái)實(shí)現(xiàn)客戶端與服務(wù)器之間的數(shù)據(jù)交互Socket socket=null;//在實(shí)例化連接對(duì)象Socket時(shí)需要服務(wù)器的ip地址和端口號(hào)TcpEchoClient(String serverIp,int serverPort) throws IOException {socket=new Socket(serverIp,serverPort);}public void start(){//寫(xiě)日志,方便觀察當(dāng)前運(yùn)行狀態(tài)System.out.println("客戶端啟動(dòng)");//TCP協(xié)議進(jìn)行網(wǎng)絡(luò)編程傳遞數(shù)據(jù)的基本單位是字節(jié),所以需要用到InputStream和OutputStream的對(duì)象來(lái)進(jìn)行數(shù)據(jù)的傳輸//通過(guò)連接對(duì)象即可實(shí)例化InputStream和OutputStream的對(duì)象try(InputStream inputStream=socket.getInputStream();OutputStream outputStream=socket.getOutputStream();){//客戶端需要不停的讀取客戶輸入的請(qǐng)求,所以要用循環(huán)while (true){Scanner scanner=new Scanner(System.in);//讀取用戶的請(qǐng)求System.out.println("->");String request=scanner.next();//將用戶的請(qǐng)求發(fā)送給服務(wù)器//直接使用outputStream不方便,包裝一層PrintWriter使用PrintWriter printWriter=new PrintWriter(outputStream);printWriter.println(request);//由于IO操作很消耗資源,所以在調(diào)用println方法向客戶端發(fā)送數(shù)據(jù)時(shí)會(huì)先將數(shù)據(jù)放到一個(gè)內(nèi)存緩沖區(qū)中,等到有一定的內(nèi)容再一起發(fā)送//所以為了保證請(qǐng)求被及時(shí)的發(fā)到服務(wù)器,就要對(duì)內(nèi)存緩沖區(qū)進(jìn)行刷新printWriter.flush();//接收服務(wù)器處理后的回應(yīng)//直接使用inputStream不方便,包裝一層Scanner使用Scanner inputScanner=new Scanner(inputStream);String response=inputScanner.next();//將回應(yīng)打印到控制臺(tái)System.out.println(response);}} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) throws IOException {TcpEchoClient tcpEchoClient=new TcpEchoClient("127.0.0.1",1010);tcpEchoClient.start();}
}