一.TCP流套字節(jié)相關(guān)API.
Socket(既能給客戶端使用,也能給服務(wù)器使用)
構(gòu)造方法
基本方法:
ServerSocket(只能給服務(wù)器使用)
構(gòu)造方法:
基本方法:
二.TCP實現(xiàn)回顯服務(wù)器.
客戶端代碼示例:
package Demo2;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
private Socket clientSocket =null;
public TcpEchoClient(String serverIp,int serverPort) throws IOException {
//此處可以把這里的IP和port直接傳給socket對象.
//由于TCP是有連接的,所以socket中就會保存好這兩個信息.
clientSocket = new Socket(serverIp,serverPort);
}
public void start(){
System.out.println("客戶端啟動~~");
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()
) {
Scanner scannerConsole = new Scanner(System.in);
//從控制臺讀取數(shù)據(jù)
Scanner scannerNetWork = new Scanner(inputStream);
//
while(true){
//1.從控制臺讀取數(shù)據(jù).
System.out.println("->");
if(!scannerConsole.hasNext()){
break;
}
String request = scannerConsole.next();
PrintWriter printWriter = new PrintWriter(outputStream);
//2.把請求發(fā)送給服務(wù)器. 這里要使用println來發(fā)送.為了讓發(fā)送的請求末尾帶有一個換行.
printWriter.println(request);
//通過flush來主動刷新緩沖區(qū),來確保數(shù)據(jù)發(fā)送到服務(wù)器了.
printWriter.flush();
//3.從服務(wù)器讀取響應(yīng).這里也是和服務(wù)器返回響應(yīng)的邏輯想對應(yīng)
String response = scannerNetWork.next();
//4.把響應(yīng)打印到控制臺.
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
client.start();
}
}
服務(wù)器代碼示例:文章來源:http://www.zghlxwxcb.cn/news/detail-857365.html
package Demo2;
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;
public class TcpEchoServer {
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服務(wù)器啟動~~");
ExecutorService pool = Executors.newCachedThreadPool();
while(true) {
//通過accept方法來接聽電話,然后才能進行通信.
Socket clientSocket = serverSocket.accept();
// Thread thread = new Thread(()->{
// processConnection(clientSocket);
// });
// thread.start();
pool.submit(new Runnable() {
@Override
public void run() {
processConnection(clientSocket);
}
});
}
}
//通過這個方法來處理一次連接,連接過程中就會涉及請求響應(yīng)交互
public void processConnection(Socket clientSocket){
System.out.printf("[%s:%d] 客戶端上線!\n",clientSocket.getInetAddress(),clientSocket.getPort());
//循環(huán)讀取客戶端的請求并返回響應(yīng)
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()
) {
Scanner scanner = new Scanner(inputStream);
while(true){
//可以通過inputStream來讀取數(shù)據(jù)了.
//byte[] buffer = new byte[4096];
//int n = inputStream.read(buffer);
//此處讀操作完全可以用read來完成,但是read是把讀取到的數(shù)據(jù)放到一個byte數(shù)組之中
//后續(xù)根據(jù)請求處理響應(yīng),還需要把數(shù)組轉(zhuǎn)化成字符串.
//此時就可以使用Scanner來簡化這個過程.
if(!scanner.hasNext()){
//讀取完畢,例如客戶端斷開鏈接.
System.out.printf("[%s %d] 客戶端下線!\n",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
//1.讀取請求并解析,此時有一個隱藏的約定,next讀的時候要讀到空白符才會結(jié)束
// 因此就要求客戶端發(fā)來的請求必須帶有空白符結(jié)尾.比如帶有/n或" ".
String request = scanner.next();
//2.根據(jù)請求計算響應(yīng).
String response = process(request);
//3.把相應(yīng)給客戶端.
//outputStream.write(response.getBytes(),0,response.getBytes().length);
// 通過這種方式可以返回,但是這種方式不方便給返回的響應(yīng)中添加換行
// 此時就可以給outputStream套一層來完成更方便的寫入.
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s %d] request : %s ;response : %s ",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);
System.out.println();
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
運行結(jié)果:文章來源地址http://www.zghlxwxcb.cn/news/detail-857365.html
代碼執(zhí)行流程:
- 服務(wù)器啟動,阻塞在accept,等待客戶端建立連接.
- 客戶端啟動.這里的new操作會觸發(fā)和服務(wù)器之間建立連接的操作.此時服務(wù)器就會從accept中返回.
- 服務(wù)器解除阻塞,繼續(xù)向下執(zhí)行,執(zhí)行processConnection方法
執(zhí)行這個方法,執(zhí)行到hasNext就會阻塞,此時雖然建立了連接,但是客戶端還沒有發(fā)來任何請求.hasNext阻塞等待到請求到達.
- 客戶端繼續(xù)執(zhí)行到hasNext,等待用戶向客戶端寫入內(nèi)容.
- 如果用戶真的輸入了,就會繼續(xù)向下執(zhí)行發(fā)送請求等待返回的邏輯.
這里就會把請求真的發(fā)出去,同時客戶端等待服務(wù)器返回響應(yīng),此時next就會阻塞等待.
- 服務(wù)器從hasNext 返回讀取到的請求,構(gòu)造響應(yīng),并把響應(yīng)返回給客戶端.
此時服務(wù)器結(jié)束此次循環(huán),開啟下一次循環(huán),繼續(xù)阻塞在hasNext等待下一個請求
- 客戶端讀取到響應(yīng),并顯示出來.
此時客戶端就會結(jié)束此次循環(huán),開啟下一次循環(huán),繼續(xù)阻塞在hasNext等待用戶輸入下一個請求.
代碼注意事項:
-
- flush()方法存在一個內(nèi)存緩沖區(qū).由于文件IO的操作比較低效,因此就希望IO的次數(shù)少一些,等攢到一定程度再進行IO操作.(相當(dāng)于多次IO合并成一次了). 因此就引入了緩沖區(qū),此時就會出現(xiàn)問題,你輸入的數(shù)據(jù)比較少,數(shù)據(jù)被存在內(nèi)存緩沖區(qū)了,所以需要我們手動刷新緩沖區(qū).
-
- 如果客戶端非常的多,就需要創(chuàng)建多個Socket對象,此時就可能導(dǎo)致系統(tǒng)的資源使用完了,因此需要在Socket執(zhí)行完畢之后關(guān)閉資源.
-
- 引入線程池來解決頻繁的創(chuàng)建銷毀線程.
-
- 如果有多個客戶端建立請求,并且長時間不銷毀
- 解決方案一:引入?yún)f(xié)程===>輕量級線程,用戶態(tài)可以通過手動調(diào)度的方式讓一個線程并發(fā)的做多個任務(wù).
- 解決方案二:IO多路復(fù)用===>這是一個系統(tǒng)內(nèi)核級別的機制,本質(zhì)上是讓一個線程去處理多個Socket對象 (這些Socket數(shù)據(jù)并非是同一時刻都需要處理).
到了這里,關(guān)于【網(wǎng)絡(luò)編程】TCP流套接字編程(TCP實現(xiàn)回顯服務(wù)器)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!