TCP通信
快速入門(mén)(一發(fā)一收)
TCP協(xié)議回顧:
TCP是一種面向連接,安全、可靠的傳輸數(shù)據(jù)的協(xié)議
傳輸前,采用“三次握手”方式,點(diǎn)對(duì)點(diǎn)通信,是可靠的
在連接中可進(jìn)行大數(shù)據(jù)量的傳輸
TCP通信模式:
在java中只要是使用java.net.Socket類(lèi)實(shí)現(xiàn)通信,底層即是使用了TCP協(xié)議
編寫(xiě)客戶(hù)端代碼
Socket(客戶(hù)端):
構(gòu)造器 | 說(shuō)明 |
---|---|
Socket(String host , int port) | 創(chuàng)建發(fā)送端的Socket對(duì)象與服務(wù)端連接,參數(shù)為服務(wù)端程序的ip和端口。 |
Socket類(lèi)成員方法:
方法 | 說(shuō)明 |
---|---|
OutputStream getOutputStream() | 獲得字節(jié)輸出流對(duì)象 |
InputStream getInputStream() | 獲得字節(jié)輸入流對(duì)象 |
客戶(hù)端實(shí)現(xiàn)步驟:
- 創(chuàng)建客戶(hù)端的Socket對(duì)象,請(qǐng)求與服務(wù)端的連接。
- 使用socket對(duì)象調(diào)用getOutputStream()方法得到字節(jié)輸出流。
- 使用字節(jié)輸出流完成數(shù)據(jù)的發(fā)送。
- 不建議直接關(guān)閉socket管道釋放資源, 一般用戶(hù)退出時(shí)才會(huì)關(guān)閉。
/**
客戶(hù)端
*/
public class ClientDemo {
public static void main(String[] args) {
try {
// 1. 創(chuàng)建socket通信管道請(qǐng)求與服務(wù)端進(jìn)行連接
/**
參數(shù)一: 服務(wù)器IP地址
參數(shù)二: 服務(wù)器端口號(hào)
*/
Socket socket = new Socket("127.0.0.1", 7777);
// 2. 從socket通信管道中獲取到字節(jié)輸出流
OutputStream os = socket.getOutputStream();
// 包裹低級(jí)字節(jié)輸出流為字節(jié)打印流
PrintStream ps = new PrintStream(os);
// 3. 打印流發(fā)送消息
ps.println("我是TCP的客戶(hù)端");
ps.flush(); // 刷新
} catch (Exception e) {
e.printStackTrace();
}
}
}
編寫(xiě)服務(wù)器代碼
ServerSocket(服務(wù)端):
構(gòu)造器 | 說(shuō)明 |
---|---|
ServerSocket(int port) | 注冊(cè)服務(wù)端端口 |
ServerSocket類(lèi)成員方法:
方法 | 說(shuō)明 |
---|---|
Socket accept() | 等待接收客戶(hù)端的Socket通信連接 連接成功返回Socket對(duì)象與客戶(hù)端建立端到端通信 |
服務(wù)端實(shí)現(xiàn)步驟:
- 創(chuàng)建ServerSocket對(duì)象,注冊(cè)服務(wù)端端口。
- 調(diào)用ServerSocket對(duì)象的accept()方法,等待客戶(hù)端的連接,并得到Socket管道對(duì)象。
- 通過(guò)Socket對(duì)象調(diào)用getInputStream()方法得到字節(jié)輸入流、完成數(shù)據(jù)的接收。
- 不建議直接關(guān)閉socket管道釋放資源, 一般用戶(hù)退出時(shí)才會(huì)關(guān)閉。
/**
服務(wù)器
*/
public class ServerDemo {
public static void main(String[] args) {
try {
// 1. 創(chuàng)建ServerSocket對(duì)象注冊(cè)服務(wù)器端口
ServerSocket serverSocket = new ServerSocket(7777);
// 2. 調(diào)用accept方法, 等待客戶(hù)端連接, 連接成功返回socket管道對(duì)象
Socket socket = serverSocket.accept();
// 3. 從socket管道中獲取字節(jié)輸入流, 完成數(shù)據(jù)接受
InputStream is = socket.getInputStream();
// 把字節(jié)輸入流包裝為緩沖字符輸入流進(jìn)行消息接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 按照行讀取
String message;
if ((message = br.readLine()) != null) {
System.out.println(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
多發(fā)多收
需求:
- 使用TCP通信方式實(shí)現(xiàn):多發(fā)多收消息。
具體要求:
- 可以使用死循環(huán)控制服務(wù)端收完消息繼續(xù)等待接收下一個(gè)消息。
- 客戶(hù)端也可以使用死循環(huán)等待用戶(hù)不斷輸入消息。
- 客戶(hù)端一旦輸入了exit,則關(guān)閉客戶(hù)端程序,并釋放資源。
客戶(hù)端
/**
客戶(hù)端
*/
public class ClientDemo {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 7777);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
// 客戶(hù)端使用死循環(huán)等待用戶(hù)不斷地輸入消息
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("發(fā)送消息: ");
String inp = scanner.nextLine();
// 一旦輸入了exit,則關(guān)閉客戶(hù)端程序,并釋放資源
if (inp.equals("exit")) {
System.out.println("下線成功");
ps.close();
break;
}
ps.println(inp);
ps.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
服務(wù)端
/**
服務(wù)器
*/
public class ServerDemo {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(7777);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String message;
// 死循環(huán)控制服務(wù)端收完消息繼續(xù)等待接收下一個(gè)消息
while ((message = br.readLine()) != null) {
System.out.println("收到消息: " + message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
多發(fā)多收(同時(shí)接受多個(gè)客戶(hù)端)
思考: 案例實(shí)現(xiàn)了多發(fā)多收,那么是否可以同時(shí)接收多個(gè)客戶(hù)端的消息?
不可以的。
因?yàn)榉?wù)端現(xiàn)在只有一個(gè)線程,只能與一個(gè)客戶(hù)端進(jìn)行通信; 并且上面代碼中, 我們只連接了一個(gè)客戶(hù)端然后就在死循環(huán)接受消息。
那么如何才可以讓服務(wù)端可以處理多個(gè)客戶(hù)端的通信需求?
引入多線程。
同時(shí)處理多個(gè)客戶(hù)端消息實(shí)現(xiàn)架構(gòu)如下:
主線程死循環(huán)不斷地接收socket鏈接, 每成功鏈接一個(gè)socket, 就交給子線程處理
實(shí)現(xiàn)步驟如下:
優(yōu)化服務(wù)器代碼即可
創(chuàng)建一個(gè)線程類(lèi), 用來(lái)處理接收消息
public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String message;
// 死循環(huán)控制服務(wù)端收完消息繼續(xù)等待接收下一個(gè)消息
while ((message = br.readLine()) != null) {
System.out.println("收到消息: " + message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
在服務(wù)器主線程中, 每鏈接到一個(gè)socket都要?jiǎng)?chuàng)建一個(gè)線程類(lèi)交給子線程處理
/**
服務(wù)器
*/
public class ServerDemo {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(7777);
// 1. 主線程中定義一個(gè)死循環(huán)由主線程不斷地接收客戶(hù)端socket管道連接
while (true) {
// 2. 每接收到一個(gè)socket管道, 都交給一個(gè)獨(dú)立的子線程負(fù)責(zé)讀取消息
Socket socket = serverSocket.accept();
// 交給子線程處理, 并啟動(dòng)子線程
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
線程池優(yōu)化
目前的通信架構(gòu)存在什么問(wèn)題?
客戶(hù)端與服務(wù)端的線程模型是: 1-1的關(guān)系, 有多少客戶(hù)端就會(huì)創(chuàng)建多少線程。
客戶(hù)端并發(fā)越多,系統(tǒng)癱瘓的越快。
引入線程池處理多個(gè)客戶(hù)端消息的架構(gòu)如下:
線程池優(yōu)化多發(fā)多收, 我們只需要優(yōu)化服務(wù)器的代碼即可:
創(chuàng)建一個(gè)Runnable任務(wù)類(lèi)
public class ServerReaderRunnable implements Runnable {
private Socket socket;
public ServerReaderRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String message;
// 死循環(huán)控制服務(wù)端收完消息繼續(xù)等待接收下一個(gè)消息
while ((message = br.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress() + "收到消息: " + message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
優(yōu)化服務(wù)器端代碼
/**
服務(wù)器
*/
public class ServerDemo {
// 使用靜態(tài)變量記錄一個(gè)線程池對(duì)象
private static ExecutorService pool = new ThreadPoolExecutor(3, 5, 6,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(7777);
while (true) {
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "上線了");
// 創(chuàng)建Runnable任務(wù)交給線程池處理
pool.execute(new ServerReaderRunnable(socket));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
線程池優(yōu)勢(shì)是什么?
服務(wù)端可以復(fù)用線程處理多個(gè)客戶(hù)端,可以避免系統(tǒng)癱瘓。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-804939.html
適合客戶(hù)端通信時(shí)長(zhǎng)較短的場(chǎng)景。
計(jì)思想, 客戶(hù)端將消息發(fā)送給服務(wù)器, 再由服務(wù)器進(jìn)行轉(zhuǎn)發(fā)給其他客戶(hù)端。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-804939.html
到了這里,關(guān)于Java網(wǎng)絡(luò)編程 - TCP通信的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!