目錄
1. ServerSocket API(給服務(wù)器端使用的類)
2. Socket API(既給服務(wù)器使用,也給客戶端使用)
3. 寫(xiě)TCP回顯—服務(wù)器
4. 使用線程池后的TCP服務(wù)器代碼(最終)
5. 寫(xiě)回顯-客戶端
6. TCP回顯—客戶端代碼
7. 運(yùn)行回顯服務(wù)器和客戶端
TCP流套接字編程
1. ServerSocket API(給服務(wù)器端使用的類)
?ServerSocket 是創(chuàng)建TCP服務(wù)端Socket的API。
?構(gòu)造方法
方法簽名 | 說(shuō)明 |
---|---|
ServerSocket(int port) | 創(chuàng)建一個(gè)服務(wù)端流套接字Socket,并綁定到指定端口 |
方法
方法簽名 | 說(shuō)明 |
---|---|
Socket accept() | 開(kāi)始監(jiān)聽(tīng)指定端口(創(chuàng)建時(shí)綁定的端口),有客戶端連接后,返回一個(gè)服務(wù)端Socket對(duì)象,并基于改Socket建立與客戶端的連接,否則阻塞等待(接受客戶端的連接) |
void close() | 關(guān)閉此套接字 |
2. Socket API(既給服務(wù)器使用,也給客戶端使用)
Socket 是客戶端Socket,或服務(wù)端中接收到客戶端建立連接(accept方法)的請(qǐng)求后,返回的服務(wù)端Socket。
不管是客戶端還是服務(wù)端Socket,都是雙方建立連接以后,保存的對(duì)端信息,及用來(lái)與對(duì)方收發(fā)數(shù)據(jù)的
構(gòu)造方法
方法簽名 | 說(shuō)明 |
---|---|
Socket(String host, int port) | 創(chuàng)建一個(gè)客戶端流套接字Socket,并與對(duì)應(yīng)IP的主機(jī)上,對(duì)應(yīng)端口的 進(jìn)程建立連接(嘗試和指定的服務(wù)器建立連接) |
方法
方法簽名 | 說(shuō)明 |
---|---|
InetAddress getInetAddress() | 返回套接字所連接的地址(返回套接字獲取到對(duì)方的IP地址和端口) |
InputStream getInputStream() | 返回此套接字的輸入流(通過(guò)Socket可以獲取到兩個(gè)流對(duì)象,分別用來(lái)讀和寫(xiě)) |
OutputStream getOutputStream() | 返回此套接字的輸出流 |
3. 寫(xiě)TCP回顯—服務(wù)器
1.先寫(xiě)一個(gè)ServerSocket對(duì)象
private ServerSocket listenSocket = null;
2.下面寫(xiě)Tcp服務(wù)器構(gòu)造方法
public TcpEchoServer(int port) throws IOException { listenSocket = new ServerSocket(port); }
3.寫(xiě)一個(gè)啟動(dòng)服務(wù)器的方法start(),在start中寫(xiě)上while循環(huán)來(lái)執(zhí)行
? ?a)調(diào)用accept來(lái)接收客戶端的連接
? ?b)再處理這個(gè)連接,這里寫(xiě)一個(gè)processConnection()方法來(lái)處理
public void start() throws IOException { System.out.println("服務(wù)器啟動(dòng)!"); while(true) { //1. 先調(diào)用 accept 來(lái)接受客戶端的連接 Socket clientSocket = listenSocket.accept(); //2. 再處理這個(gè)連接 processConnection(clientSocket); } }
4.下面來(lái)寫(xiě)這個(gè)processConnection()方法,處理連接客戶端連接
? ?方法中寫(xiě)try(這里寫(xiě)上InputStream(讀)和OutPutStream(寫(xiě))對(duì)象,寫(xiě)在try中幫助資源回收) {這里寫(xiě)上寫(xiě)具體處理邏輯步驟}
注意最后必須要寫(xiě)上finally來(lái)close關(guān)閉clientSocket
private void processConnection(Socket clientSocket) throws IOException { System.out.printf("[%s:%d] 客戶端上線!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort()); //接下來(lái)處理客戶端請(qǐng)求 try(InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) { while(true) { } }finally { //這里要關(guān)閉socket,是因?yàn)? //socket也是一個(gè)文件,一個(gè)進(jìn)程能夠同時(shí)打開(kāi)的文件個(gè)數(shù)有上限(PCB文件描述符表,不是無(wú)限的 clientSocket.close(); } }
? ?a)讀取請(qǐng)求并解析
Scanner scanner = new Scanner(inputStream); if(!scanner.hasNext()) { //讀完了,連接可以斷開(kāi)了 System.out.printf("[%s:%d] 客戶端下線!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort()); break; } String request = scanner.next();
? ?b)根據(jù)請(qǐng)求計(jì)算響應(yīng)(這里寫(xiě)一個(gè)process方法)
String response = process(request);
private String process(String request) { return request; }
?c)響應(yīng)寫(xiě)回到客戶端
PrintWriter printWriter = new PrintWriter(outputStream); printWriter.println(response); //刷新緩沖區(qū)確保數(shù)據(jù)確實(shí)通過(guò)網(wǎng)卡發(fā)送出去了 printWriter.flush();
? ?d)將發(fā)送的信息顯示到服務(wù)器界面上
System.out.printf("[%s:%d] req: %s; resp: %s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(), request,response);
5.最后再寫(xiě)上mian方法來(lái)執(zhí)行服務(wù)器
public static void main(String[] args) throws IOException { TcpEchoServer tcpEchoServer = new TcpEchoServer(9090); tcpEchoServer.start(); }
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.Semaphore;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 28463
* Date: 2022—10—14
* Time: 17:05
*/
public class TcpEchoServer {
//代碼中會(huì)設(shè)計(jì)到多個(gè) socket 對(duì)象,使用不同的名字來(lái)區(qū)分
private ServerSocket listenSocket = null;
public TcpEchoServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服務(wù)器啟動(dòng)!");
while(true) {
//1. 先調(diào)用 accept 來(lái)接受客戶端的連接
Socket clientSocket = listenSocket.accept();
//2. 再處理這個(gè)連接
processConnection(clientSocket);
}
}
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 客戶端上線!\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
//接下來(lái)處理客戶端請(qǐng)求
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
while(true) {
//1.讀取請(qǐng)求并解析
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()) {
//讀完了,連接可以斷開(kāi)了
System.out.printf("[%s:%d] 客戶端下線!\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
String request = scanner.next();
//2.根據(jù)請(qǐng)求計(jì)算響應(yīng)
String response = process(request);
//3.響應(yīng)寫(xiě)回到客戶端
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//刷新緩沖區(qū)確保數(shù)據(jù)確實(shí)通過(guò)網(wǎng)卡發(fā)送出去了
printWriter.flush();
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();
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
?下面思考為啥代碼最后finally要執(zhí)行clientSocket的close,而前面的listenSocket以及UDP程序中的socekt為啥就沒(méi)close?
但是上面的代碼有個(gè)問(wèn)題,只能處理一個(gè)客戶端的請(qǐng)求,(本質(zhì)上第二個(gè)客戶端的消息是發(fā)出去了,但服務(wù)器此時(shí)還在執(zhí)行第一個(gè)客戶端的請(qǐng)求,只要從第一個(gè)客戶端這里出來(lái),服務(wù)器就會(huì)立刻執(zhí)行第二個(gè)客戶端的消息)
我們希望的是既能夠快速重復(fù)的調(diào)用到accept(也就是連接多個(gè)客戶端),又能夠循環(huán)的處理客戶端的請(qǐng)求。所以就需要使用到多線程了
那么為什么前面UDP就不需要考慮這個(gè)問(wèn)題,而TCP需要考慮?
UDP是無(wú)連接,客戶端直接發(fā)消息就行(不必專注于處理某一個(gè)客戶端)
TCP建立連接之后,要處理客戶端的多次請(qǐng)求,才導(dǎo)致無(wú)法快速的調(diào)用到accept(長(zhǎng)連接)(主要原因)
如果TCP每個(gè)連接只處理一個(gè)客戶端的請(qǐng)求,也能夠保證快速調(diào)用到accept(短連接)
?下面使用多線程,給每個(gè)客戶端連上來(lái)的都分配一個(gè)新的線程負(fù)責(zé)處理請(qǐng)求
?但是直接這樣使用多線程,如果循環(huán)多次,對(duì)應(yīng)就會(huì)創(chuàng)建很多線程,等線程執(zhí)行完,又會(huì)消毀很多的線程,所以更好的方法就是使用線程池
4. 使用線程池后的TCP服務(wù)器代碼(最終)
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.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 28463
* Date: 2022—10—14
* Time: 17:05
*/
public class TcpEchoServer {
//代碼中會(huì)設(shè)計(jì)到多個(gè) socket 對(duì)象,使用不同的名字來(lái)區(qū)分
private ServerSocket listenSocket = null;
public TcpEchoServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服務(wù)器啟動(dòng)!");
ExecutorService service = Executors.newCachedThreadPool();
while(true) {
//1. 先調(diào)用 accept 來(lái)接受客戶端的連接
Socket clientSocket = listenSocket.accept();
//2. 再處理這個(gè)連接,這里應(yīng)該要使用多線程,每個(gè)客戶端連上來(lái)都分配一個(gè)新的線程負(fù)責(zé)處理
service.submit(new Runnable() {
@Override
public void run() {
try {
processConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 客戶端上線!\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
//接下來(lái)處理客戶端請(qǐng)求
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
while(true) {
//1.讀取請(qǐng)求并解析
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()) {
//讀完了,連接可以斷開(kāi)了
System.out.printf("[%s:%d] 客戶端下線!\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
String request = scanner.next();
//2.根據(jù)請(qǐng)求計(jì)算響應(yīng)
String response = process(request);
//3.響應(yīng)寫(xiě)回到客戶端
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//刷新緩沖區(qū)確保數(shù)據(jù)確實(shí)通過(guò)網(wǎng)卡發(fā)送出去了
printWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort(),
request,response);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//這里要關(guān)閉socket,是因?yàn)? //socket也是一個(gè)文件,一個(gè)進(jìn)程能夠同時(shí)打開(kāi)的文件個(gè)數(shù)有上限(PCB文件描述符表,不是無(wú)限的
clientSocket.close();
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
5. 寫(xiě)回顯-客戶端
?1. 先寫(xiě)一個(gè)Socket對(duì)象,客戶端用Socket來(lái)建立連接
private Socket socket = null;
2.下面寫(xiě)Tcp客戶端構(gòu)造(和Udp區(qū)別較大,有連接和無(wú)連接的區(qū)別)
public TcpEchoClient(String serverIP, int serverPort) throws IOException { //和服務(wù)器建立連接,就需要知道服務(wù)器在哪 socket = new Socket(serverIP,serverPort); }
3.寫(xiě)客戶端執(zhí)行方法start(),給start里面放try,try中執(zhí)行的就是while,來(lái)讓客戶端循環(huán)輸入,還要在try后面括號(hào)中寫(xiě)上InputStream(讀)和OutputStream(寫(xiě))的對(duì)象(寫(xiě)在括號(hào)中中try會(huì)自動(dòng)幫助,關(guān)掉資源)
public void start() throws IOException { Scanner scan = new Scanner(System.in); try(InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) { while(true) { } } }
while中寫(xiě)? a)從控制臺(tái)讀取數(shù)據(jù),構(gòu)造一個(gè)請(qǐng)求
System.out.println("-> "); String request = scan.next();
? ? ? ? ? ? ? ? ?b)發(fā)送請(qǐng)求給服務(wù)器(PrintWriter)
PrintWriter printWriter = new PrintWriter(outputStream); printWriter.println(request); //這個(gè) flush 不要忘記,否則可能導(dǎo)致請(qǐng)求沒(méi)有真發(fā)出去 printWriter.flush();
? ? ? ? ? ? ? ? ?c)從服務(wù)器讀取響應(yīng)
Scanner respScanner = new Scanner(inputStream); String response = respScanner.next();
? ? ? ? ? ? ? ? ?d)把響應(yīng)顯示到界面上
System.out.println(response);
4.最后再寫(xiě)上mian方法來(lái)執(zhí)行客戶端
public static void main(String[] args) throws IOException { TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090); tcpEchoClient.start();
?Udp和Tcp構(gòu)造的區(qū)別,有連接和無(wú)連接?
6. TCP回顯—客戶端代碼
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 28463
* Date: 2022—10—14
* Time: 17:06
*/
public class TcpEchoClient {
//客戶端需要使用這個(gè) Socket 對(duì)象來(lái)建立連接
private Socket socket = null;
public TcpEchoClient(String serverIP, int serverPort) throws IOException {
//和服務(wù)器建立連接,就需要知道服務(wù)器在哪
socket = new Socket(serverIP,serverPort);
}
public void start() throws IOException {
Scanner scan = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
while(true) {
//1.從控制臺(tái)讀取數(shù)據(jù),構(gòu)造成一個(gè)請(qǐng)求
System.out.println("-> ");
String request = scan.next();
//2.發(fā)送請(qǐng)求給服務(wù)器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
//這個(gè) flush 不要忘記,否則可能導(dǎo)致請(qǐng)求沒(méi)有真發(fā)出去
printWriter.flush();
//3.從服務(wù)器讀取響應(yīng)
Scanner respScanner = new Scanner(inputStream);
String response = respScanner.next();
//4.把響應(yīng)顯示到界面上
System.out.println(response);
}
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
tcpEchoClient.start();
}
}
7. 運(yùn)行回顯服務(wù)器和客戶端
?文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-805067.html
?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-805067.html
到了這里,關(guān)于網(wǎng)絡(luò)編程套接字之三【TCP】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!