目錄
TCP流套接字編程
1.ServerSocket API
2.Socket API
3.TCP中的長短連接
4.回顯程序(短連接)
5.服務(wù)器和客戶端它們的交互過程
6.運行結(jié)果及修改代碼
?文章來源地址http://www.zghlxwxcb.cn/news/detail-411177.html
TCP流套接字編程
??兩個核心:ServerSocket? ? ?Socket
1.ServerSocket API
?ServerSocket 是創(chuàng)建?TCP服務(wù)端Socket的API
ServerSocket 構(gòu)造方法:
ServerSocket(int port) 創(chuàng)建一個服務(wù)端流套接字Socket,并綁定到指定端口
ServerSocket 方法:
Socket accept() 開始監(jiān)聽指定端口(創(chuàng)建時綁定的端口)有客戶端連接后,返回一個服務(wù)端Socket對象,并基于該Socket建立與客戶端的連接,否則阻塞等待
void close() 關(guān)閉此套接字
2.Socket API
?Socket 是 客戶端Socket,或服務(wù)端中接收到客戶端建立連接(accept方法)的請求后,返回的服務(wù)端 Socket。
Socket 構(gòu)造方法:
Socket(String host, int port) 創(chuàng)建一個客戶端流套接字Socket,并與對應(yīng)IP的主機上,對應(yīng)端口的進程建立連接
Socket 方法:
InetAddress getInetAddress() 返回套接字所連接的地址
InputStream getInputStream() 返回此套接字的輸入流
OutputStream getOutputStream() 返回此套接字的輸出流
3.TCP中的長短連接
??TCP發(fā)送數(shù)據(jù)時,需要先建立連接,什么時候關(guān)閉連接就決定是短連接還是長連接:
短連接:每次接收到數(shù)據(jù)并返回響應(yīng)后,都關(guān)閉連接,即是短連接。也就是說,短連接只能一次收發(fā)數(shù)據(jù)。
4.回顯程序(短連接)
1??TCP 客戶端
package io;
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 socket = null;
public TcpEchoClient(String serverIp, int port) throws IOException {
// 這個操作相當于讓客戶端和服務(wù)器建立 tcp 連接.
// 這里的連接連上了, 服務(wù)器的 accept 就會返回.
socket = new Socket(serverIp, port);
}
//啟動客戶端程序
public void start() {
Scanner scanner = new Scanner(System.in);
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
PrintWriter printWriter = new PrintWriter(outputStream);
Scanner scannerFromSocket = new Scanner(inputStream);
while (true) {
// 1. 從鍵盤上讀取用戶輸入的內(nèi)容.
System.out.print("-> ");
String request = scanner.next();
// 2. 把讀取的內(nèi)容構(gòu)造成請求, 發(fā)送給服務(wù)器.
// 注意, 這里的發(fā)送, 是帶有換行的!!
printWriter.println(request);//這里只是把數(shù)據(jù)寫入內(nèi)存的緩沖區(qū)中,等待緩沖區(qū)滿了,才會真正寫網(wǎng)卡
// 3. 從服務(wù)器讀取響應(yīng)內(nèi)容
String response = scannerFromSocket.next();
// 4. 把響應(yīng)結(jié)果顯示到控制臺上.
System.out.printf("req: %s; resp: %s\n", request, response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
client.start();
}
}
2??TCP 服務(wù)端
package io;
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;
public class TcpEchoServer {
// serverSocket 就是外場拉客的小哥
// clientSocket 就是內(nèi)場服務(wù)的小姐姐.
// serverSocket 只有一個. clientSocket 會給每個客戶端都分配一個~
private ServerSocket serverSocket = null;
//端口號綁定
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服務(wù)器啟動!");
while (true) {
Socket clientSocket = serverSocket.accept();
processConnection(clientSocket);
}
}
// 通過這個方法來處理一個連接.
// 讀取請求
// 根據(jù)請求計算響應(yīng)
// 把響應(yīng)返回給客戶端
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 客戶端上線!\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort());
// try () 這種寫法, ( ) 中允許寫多個流對象. 使用 ; 來分割
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
// 沒有這個 scanner 和 printWriter, 完全可以!! 但是代價就是得一個字節(jié)一個字節(jié)扣, 找到哪個是請求的結(jié)束標記 \n
// 不是不能做, 而是代碼比較麻煩.為了簡單, 把字節(jié)流包裝秤了更方便的字符流~~
Scanner scanner = new Scanner(inputStream);
//Scanner 相當于 字符流,上述約定了請求是字符串,所以就可以使用字符流來處理
PrintWriter printWriter = new PrintWriter(outputStream);
while (true) {
// 1. 讀取請求:
//hasNext 判定接下來還有沒有數(shù)據(jù)了,如果對端關(guān)閉連接(客戶端關(guān)閉連接),此時 hasNext 就會返回 false,循環(huán)就讓它結(jié)束
//如果對端有數(shù)據(jù),hasNxet 返回 true,進一步就可以使用 next 方法來讀出這一段字符串的內(nèi)容了
if (!scanner.hasNext()) {
// 讀取的流到了結(jié)尾了 (對端關(guān)閉了)
System.out.printf("[%s:%d] 客戶端下線!\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
// 直接使用 scanner 讀取一段字符串.
String request = scanner.next();
//next:一直往后讀,讀到空白符結(jié)束——空格,換行,制表符,翻頁符..都算空白符
//nextLine 只是讀到換行符結(jié)束
//這里不要用nextLine
// 2. 根據(jù)請求計算響應(yīng)
String response = process(request);
// 3. 把響應(yīng)寫回給客戶端. 不要忘了, 響應(yīng)里也是要帶上換行的.
printWriter.println(response);
System.out.printf("[%s:%d] req: %s; resp: %s\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
clientSocket.close();//clientSocket 只是給一個連接提供服務(wù)的,還是要能夠進行關(guān)閉
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
5.服務(wù)器和客戶端它們的交互過程
1??啟動服務(wù)器
?2??客戶端啟動
當客戶端和服務(wù)器連接建立好之后,服務(wù)器這邊的 accept 就返回了
3??服務(wù)器就進入 processConnection 了,嘗試從客戶端讀取請(由于此時客戶端還沒有發(fā)送請求,此時讀取操作也會阻塞)
?與此同時:
用戶端往下執(zhí)行到,從控制臺讀取用戶輸入——也會阻塞
?4??當用戶真的輸入內(nèi)容,客戶端真正發(fā)送了請求出去;同時往下執(zhí)行到,讀取服務(wù)器響應(yīng),再次阻塞
?5??服務(wù)器收到客戶端的請求之后,從 next 這里返回,執(zhí)行 process 執(zhí)行 println ,把響應(yīng)寫回到客戶端
?6??服務(wù)器重新回到上述開頭位置,繼續(xù)嘗試讀取請求,并阻塞
? ? ? 客戶端收到服務(wù)器的響應(yīng)就可以把結(jié)果顯示出來了;同時進入下次循環(huán),再次等待用戶的輸入了
6.運行結(jié)果及修改代碼
?
?
????當我們輸入 hello 之后,發(fā)現(xiàn)客戶端沒有任何響應(yīng)——因為數(shù)據(jù)還在緩沖區(qū)內(nèi)
// 2. 把讀取的內(nèi)容構(gòu)造成請求, 發(fā)送給服務(wù)器.
// 注意, 這里的發(fā)送, 是帶有換行的!!
printWriter.println(request);
這里只是把數(shù)據(jù)寫入內(nèi)存的緩沖區(qū)中,等待緩沖區(qū)滿了,才會真正寫網(wǎng)卡
1??此時我們需要沖刷
?
?此時就可以響應(yīng)了;當前程序已經(jīng)跑起來了,已經(jīng)可以正常通信了
??但是還存在一個嚴重的 bug!!服務(wù)器需要同時能夠給多個客戶端提供服務(wù)的
2??在 idea 中啟動多個客戶端,需要配置,默認一個程序只能啟動一個
???此時就可以啟動多個程序
3??但是我們看到的是在第一個客戶端輸入 hello,運行是正常的;但是我們在第二個客戶端輸入 hello2的時候沒有任何反應(yīng)
?這就是當前服務(wù)器無法同時服務(wù)多個客戶端;為什么會造成這種原因呢?
?6??解決方法:希望同時能夠給客戶端1提供服務(wù),又能夠循環(huán)的調(diào)用 accept——多線程
?如果直接調(diào)用,該方法回影響這個循環(huán)的二次執(zhí)行,導(dǎo)致 accept 不及時了 ;這個時候就需要 創(chuàng)建新的線程,用新線程來調(diào)用 processConnection;每次來一個新的客戶端都搞一個新的線程即可!!!
public void start() throws IOException {
System.out.println("服務(wù)器啟動!");
while (true) {
Socket clientSocket = serverSocket.accept();
//如果直接調(diào)用,該方法回影響這個循環(huán)的二次執(zhí)行,導(dǎo)致 accept 不及時了
//這個時候就需要 創(chuàng)建新的線程,用新線程來調(diào)用 processConnection;每次來一個新的客戶端都搞一個新的線程即可??!!
Thread t = new Thread(() -> {
try {
processConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();
}
}
?此時運行結(jié)果:
7??解決方案2:使用 線程池?修改上述代碼
? ?一個連接的所有請求處理完,這個程序不會銷毀,而且還到池子里,下次直接使用
?文章來源:http://www.zghlxwxcb.cn/news/detail-411177.html
?
?
?
?
到了這里,關(guān)于網(wǎng)絡(luò)編程【TCP流套接字編程】的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!