?
??????點(diǎn)進(jìn)來你就是我的人了
博主主頁:??????戳一戳,歡迎大佬指點(diǎn)!
人生格言:當(dāng)你的才華撐不起你的野心的時(shí)候,你就應(yīng)該靜下心來學(xué)習(xí)!歡迎志同道合的朋友一起加油喔??????
目標(biāo)夢(mèng)想:進(jìn)大廠,立志成為一個(gè)牛掰的Java程序猿,雖然現(xiàn)在還是一個(gè)??嘿嘿
謝謝你這么帥氣美麗還給我點(diǎn)贊!比個(gè)心
目錄
一.Socket概述
??Socket通信是有兩種方式的:TCP和UDP
TCP與UDP區(qū)別
socket之send和recv原理剖析
二. TCP通信客戶端Socket
三. TCP通信服務(wù)器端ServerSocket
四.基于TCP的Socket通信
五.UDP相關(guān)類DatagramPacket類和DatagramSocket類
數(shù)據(jù)包類DatagramPacket
發(fā)送數(shù)據(jù)包類DatagramSocket
InetAddress類(無構(gòu)造方法)
六.基于UDP的Socket通信
七. TCP和UCP的緩沖區(qū)
1.TCP的緩沖區(qū)
2.UDP的緩沖區(qū)
一.Socket概述
? Socket(套接字),是網(wǎng)絡(luò)上兩個(gè)程序之間實(shí)現(xiàn)數(shù)據(jù)交換的一端,它既可以發(fā)送請(qǐng)求,也可以接受請(qǐng)求,一個(gè)Socket由一個(gè)IP地址和一個(gè)端口號(hào)唯一確定,利用Socket能比較方便的實(shí)現(xiàn)兩端(服務(wù)端和客戶端)的網(wǎng)絡(luò)通信。
? 在Java中,有專門的Socket類來處理用戶請(qǐng)求和響應(yīng),學(xué)習(xí)使用Socket類方法,就可以實(shí)現(xiàn)兩臺(tái)機(jī)器之間通信。
??Socket通信是有兩種方式的:TCP和UDP
? TCP通信:客戶端提供了java.net.Socket
類,服務(wù)器端提供了java.net.ServerSocket
類。
? UDP通信:UDP通信不建立邏輯連接,使用DatagramPacket
類打包數(shù)據(jù)包,使用DatagramSocket
類發(fā)送數(shù)據(jù)包。
TCP與UDP區(qū)別
- TCP面向連接;UDP是無連接的,即發(fā)送數(shù)據(jù)之前不需要建立連接。
- TCP提供可靠的服務(wù)。也就是說,通過TCP連接傳送的數(shù)據(jù),無差錯(cuò),不丟失,不重復(fù),且按序到達(dá);UDP盡最大努力交付,即不保證可靠交付。
- UDP具有較好的實(shí)時(shí)性,工作效率比TCP高,適用于對(duì)高速傳輸和實(shí)時(shí)性有較高的通信或廣播通信。
- TCP對(duì)系統(tǒng)資源要求較多,UDP對(duì)系統(tǒng)資源要求較少。
- TCP面向字節(jié)流;UDP面向數(shù)據(jù)報(bào),一次發(fā)送/接收都必須是完整的一個(gè)數(shù)據(jù)報(bào)或者多個(gè)數(shù)據(jù)報(bào),不能是半個(gè)數(shù)據(jù)報(bào),兩者都是全雙工,支持雙向通信
Socket通信模型如下圖:
?
socket之send和recv原理剖析
當(dāng)創(chuàng)建一個(gè)TCP socket對(duì)象的時(shí)候會(huì)有一個(gè)發(fā)送緩沖區(qū)和一個(gè)接收緩沖區(qū),這個(gè)發(fā)送和接收緩沖區(qū)指的就是內(nèi)存中的一片空間。
send原理剖析send發(fā)數(shù)據(jù),必須得通過網(wǎng)卡發(fā)送數(shù)據(jù),應(yīng)用程序是無法直接通過網(wǎng)卡發(fā)送數(shù)據(jù)的,它需要調(diào)用操作系統(tǒng)接口,也就是說,應(yīng)用程序把發(fā)送的數(shù)據(jù)先寫入到發(fā)送緩沖區(qū)(內(nèi)存中的一片空間),再由操作系統(tǒng)控制網(wǎng)卡把發(fā)送緩沖區(qū)的數(shù)據(jù)發(fā)送給服務(wù)端網(wǎng)卡。
recv原理剖析應(yīng)用軟件是無法直接通過網(wǎng)卡接收數(shù)據(jù)的,它需要調(diào)用操作系統(tǒng)接口,由操作系統(tǒng)通過網(wǎng)卡接收數(shù)據(jù),把接收的數(shù)據(jù)寫入到接收緩沖區(qū)(內(nèi)存中的一片空間),應(yīng)用程序再從接收緩存區(qū)獲取客戶端發(fā)送的數(shù)據(jù)。
二. TCP通信客戶端Socket
? Java中專門用來實(shí)現(xiàn)Socket客戶端的類就叫Socket
,這個(gè)類實(shí)現(xiàn)了客戶端套接字,用于向服務(wù)器發(fā)出連接請(qǐng)求等。
-
構(gòu)造方法:
Socket(String host, int port)
:創(chuàng)建一個(gè)流套接字并將其連接到指定IP地址的指定端口號(hào)。如果host為null,則相當(dāng)于指定地址為回送地址。
?127.x.x.x是本機(jī)的回送地址,即主機(jī)IP堆棧內(nèi)部的IP地址,主要用于網(wǎng)絡(luò)軟件測(cè)試以及本地機(jī)進(jìn)程間通信,無論什么程序,一旦使用回送地址發(fā)送數(shù)據(jù),協(xié)議軟件立即返回之,不進(jìn)行任何網(wǎng)絡(luò)傳輸。
-
主要方法:
-
InputStream getInputStream()
:返回此套接字的輸入流。關(guān)閉生成的InputStream也將關(guān)閉相關(guān)的Socket。
-
OutputStream getOutputStream()
:返回此套接字的輸出流。關(guān)閉生成的OutputStream也將關(guān)閉相關(guān)的Socket。
-
void close()
:關(guān)閉此套接字
-
三. TCP通信服務(wù)器端ServerSocket
? Java中專門用來建立Socket服務(wù)器的類叫ServerSocket
,這個(gè)類實(shí)現(xiàn)了服務(wù)器套接字,該對(duì)象等待通過網(wǎng)絡(luò)的請(qǐng)求。
-
構(gòu)造方法:
ServerSocket(int port)
:創(chuàng)建綁定到特定端口的服務(wù)器套接字。 -
主要方法:
-
Socket accept()
:監(jiān)聽并接受連接,返回一個(gè)新的Socket對(duì)象,用于和客戶端通信,該方法會(huì)一直阻塞直到建立連接。 -
void close()
:關(guān)閉此套接字。
-
四.基于TCP的Socket通信
-
步驟分析:
- 服務(wù)端先啟動(dòng),創(chuàng)建ServerSocket對(duì)象,等待連接。
- 客戶端啟動(dòng),創(chuàng)建Socket對(duì)象,請(qǐng)求連接。
- 服務(wù)器端接收請(qǐng)求,調(diào)用accept方法,并返回一個(gè)Socket對(duì)象,連接成功
- 客戶端的Socket對(duì)象通過調(diào)用
getOutputStream()
方法獲取OutputStream
對(duì)象,并使用write()
方法將數(shù)據(jù)寫入到發(fā)送緩沖區(qū)。隨后,通過調(diào)用flush()
方法確保數(shù)據(jù)已被發(fā)送出去 - 服務(wù)器端Socket對(duì)象通過調(diào)用
getInputStream()
方法獲取與該socket關(guān)聯(lián)的InputStream
實(shí)例,然后使用read()
方法從接收緩沖區(qū)中讀取數(shù)據(jù)。 - 客戶端釋放資源,斷開連接。
服務(wù)器端:
public class TcpEchoServer {
// serverSocket 就是外場(chǎng)拉客的小哥
// clientSocket 就是內(nèi)場(chǎng)服務(wù)的小姐姐.
// serverSocket 只有一個(gè). clientSocket 會(huì)給每個(gè)客戶端都分配一個(gè)~
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
ExecutorService executorService = Executors.newCachedThreadPool();
System.out.println("服務(wù)器啟動(dòng)!");
while (true) {
Socket clientSocket = serverSocket.accept();
//服務(wù)器的主線程(main 線程)負(fù)責(zé)運(yùn)行 while 循環(huán),用于接收客戶端的連接請(qǐng)求。
// 與此同時(shí),針對(duì)每個(gè)接收到的連接請(qǐng)求,都會(huì)創(chuàng)建一個(gè)新線程處理與該客戶端的數(shù)據(jù)通信。這些新線程與主線程是并發(fā)執(zhí)行的。
//由于主線程和新創(chuàng)建的線程并發(fā)執(zhí)行,服務(wù)器可以在處理一個(gè)客戶端連接的同時(shí),繼續(xù)接收其他客戶端的連接請(qǐng)求。
// 這使得服務(wù)器可以并發(fā)處理多個(gè)客戶端連接,提高了服務(wù)器的處理能力。
// 創(chuàng)建新的線程, 用新線程來調(diào)用 processConnection
// Thread t = new Thread(() -> {
// try {
// processConnection(clientSocket);
// } catch (IOException e) {
// e.printStackTrace();
// }
// });
// t.start();
//使用線程池
executorService.submit(new Runnable() {
@Override
public void run() {
try {
processConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
// 通過這個(gè)方法來處理一個(gè)連接.
// 讀取請(qǐng)求
// 根據(jù)請(qǐng)求計(jì)算響應(yīng)
// 把響應(yīng)返回給客戶端
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 客戶端上線!\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
// 沒有這個(gè) scanner 和 printWriter, 完全可以!! 但是代價(jià)就是得一個(gè)字節(jié)一個(gè)字節(jié)扣, 找到哪個(gè)是請(qǐng)求的結(jié)束標(biāo)記 \n
// 不是不能做, 而是代碼比較麻煩.
// 為了簡單, 把字節(jié)流包包裝成了更方便的字符流~~
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while (true) {
// 1. 讀取請(qǐng)求
if (!scanner.hasNext()) {
// 讀取的流到了結(jié)尾了 (對(duì)端關(guān)閉了)
System.out.printf("[%s:%d] 客戶端下線!\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
// 直接使用 scanner 讀取一段字符串.
String request = scanner.next();
// 2. 根據(jù)請(qǐng)求計(jì)算響應(yīng)
String response = process(request);
// 3. 把響應(yīng)寫回給客戶端. 不要忘了, 響應(yīng)里也是要帶上換行的.
printWriter.println(response);
//數(shù)據(jù)此時(shí)還在緩存區(qū),使用 flush() 方法,可以確保數(shù)據(jù)立即發(fā)送
printWriter.flush();
//clientSocket.getInetAddress().toString() 返回客戶端的 IP 地址,clientSocket.getPort() 返回客戶端的端口號(hào),
// request 是客戶端發(fā)送的請(qǐng)求,response 是服務(wù)器響應(yīng)的數(shù)據(jù)。
// 通過格式化輸出的方式將這些信息打印出來,方便程序員進(jìn)行調(diào)試和查看。
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();
}
}
在服務(wù)器端我們使用了
try-with-resources
語句,確保在代碼塊執(zhí)行完畢后自動(dòng)關(guān)閉資源,無論代碼執(zhí)行過程中是否發(fā)生異常。當(dāng)程序執(zhí)行到
try
語句塊結(jié)束時(shí),如果resource
實(shí)現(xiàn)了AutoCloseable
或Closeable
接口,那么close()
方法將被自動(dòng)調(diào)用。同時(shí)在服務(wù)器端引入了多線程的寫法,保證服務(wù)器能連接多個(gè)客戶端,同時(shí)與多個(gè)客戶端保持通信,具體實(shí)現(xiàn)邏輯如下:
服務(wù)器的主線程(main 線程)負(fù)責(zé)運(yùn)行 while 循環(huán),用于接收客戶端的連接請(qǐng)求。
與此同時(shí),針對(duì)每個(gè)接收到的連接請(qǐng)求,都會(huì)創(chuàng)建一個(gè)新線程處理與該客戶端的數(shù)據(jù)通信。這些新線程與主線程是并發(fā)執(zhí)行的。由于主線程和新創(chuàng)建的線程并發(fā)執(zhí)行,服務(wù)器可以在處理一個(gè)客戶端連接的同時(shí),繼續(xù)接收其他客戶端的連接請(qǐng)求。?這使得服務(wù)器可以并發(fā)處理多個(gè)客戶端連接,提高了服務(wù)器的處理能力。?當(dāng)然我們也可以引入線程池來優(yōu)化這段代碼:
- 線程池中的線程數(shù)量是動(dòng)態(tài)調(diào)整的。
- 當(dāng)有新任務(wù)提交時(shí),如果線程池中有空閑線程,那么會(huì)復(fù)用空閑線程來執(zhí)行新任務(wù);如果沒有空閑線程,則會(huì)創(chuàng)建一個(gè)新線程來執(zhí)行新任務(wù)。
- 當(dāng)線程池中的線程空閑時(shí)間超過一定時(shí)間(默認(rèn)為 60 秒)時(shí),線程池會(huì)回收這個(gè)空閑線程。
好處:線程池可以復(fù)用已經(jīng)創(chuàng)建的線程,避免了頻繁地創(chuàng)建和銷毀線程所帶來的性能開銷。當(dāng)有新任務(wù)到來時(shí),線程池會(huì)優(yōu)先使用空閑的線程,從而提高系統(tǒng)資源的利用率。
?客戶端:
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient(String serverIp, int port) throws IOException {
// 這個(gè)操作相當(dāng)于讓客戶端和服務(wù)器建立 tcp 連接.
// 這里的連接連上了, 服務(wù)器的 accept 就會(huì)返回.
socket = new Socket(serverIp, port);
}
public void start() {
Scanner scanner = new Scanner(System.in);
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
//outputStream 是一個(gè)字節(jié)輸出流,通過 PrintWriter 對(duì)象的構(gòu)造方法將其包裝成字符輸出流,以便能夠方便地寫入字符數(shù)據(jù)。
PrintWriter printWriter = new PrintWriter(outputStream);
//scannerFromSocket 對(duì)象是將輸入流對(duì)象包裝成一個(gè) Scanner 對(duì)象,以便能夠方便地讀取輸入流中的數(shù)據(jù)。
// Scanner 對(duì)象會(huì)自動(dòng)解析和分隔輸入流中的數(shù)據(jù),并將其轉(zhuǎn)換為相應(yīng)的數(shù)據(jù)類型或字符串。
Scanner scannerFromSocket = new Scanner(inputStream);
while (true) {
// 1. 從鍵盤上讀取用戶輸入的內(nèi)容.
System.out.print("-> ");
String request = scanner.next();
// 2. 把讀取的內(nèi)容構(gòu)造成請(qǐng)求, 發(fā)送給服務(wù)器.
//在 Java 中,可以通過 PrintWriter 對(duì)象的 println() 方法發(fā)送帶有換行符的數(shù)據(jù)包。
// 該方法會(huì)將指定的字符串添加一個(gè)換行符,并將其發(fā)送到輸出流中。
printWriter.println(request);
//數(shù)據(jù)此時(shí)還在緩存區(qū),使用 flush() 方法,可以確保數(shù)據(jù)立即發(fā)送
printWriter.flush();
// 3. 從服務(wù)器讀取響應(yīng)內(nèi)容
//next() 方法會(huì)讀取下一個(gè)標(biāo)記,而不是一行數(shù)據(jù)。標(biāo)記通常是以空格、制表符或換行符為分隔符的單詞或符號(hào)。
// 因此,在讀取數(shù)據(jù)時(shí),如果數(shù)據(jù)包中只有一個(gè)標(biāo)記,則可以使用 next() 方法讀取該標(biāo)記。
String response = scannerFromSocket.next();
// 4. 把響應(yīng)結(jié)果顯示到控制臺(tái)上.
// request 是客戶端發(fā)送的請(qǐng)求,response 是服務(wù)器響應(yīng)的數(shù)據(jù)。
// 通過格式化輸出的方式將這些信息打印出來,方便程序員進(jìn)行調(diào)試和查看。
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();
}
}
?PrintWriter printWriter = new PrintWriter(outputStream);? ? ? ? ? ?
outputStream 是一個(gè)字節(jié)輸出流,通過 PrintWriter 對(duì)象的構(gòu)造方法將其包裝成字符輸出流,以便能夠方便地寫入字符數(shù)據(jù)。
Scanner scannerFromSocket = new Scanner(inputStream);
scannerFromSocket 對(duì)象是將輸入流對(duì)象包裝成一個(gè) Scanner 對(duì)象,以便能夠方便地讀取輸入流中的數(shù)據(jù)。
Scanner 對(duì)象會(huì)自動(dòng)解析和分隔輸入流中的數(shù)據(jù),并將其轉(zhuǎn)換為相應(yīng)的數(shù)據(jù)類型或字符串。
五.UDP相關(guān)類DatagramPacket類和DatagramSocket類
-
數(shù)據(jù)包類DatagramPacket
- 作用:用來封裝發(fā)送端或接收端要發(fā)送或接收的數(shù)據(jù)。
- 構(gòu)造方法
-
DatagramPacket(byte[] buf, int length)
:構(gòu)造DatagramPacket,用來接收長度為length的數(shù)據(jù)包。 -
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
:構(gòu)造數(shù)據(jù)報(bào)包,用來將長度為length的包發(fā)送到指定主機(jī)上的指定端口號(hào)。
-
- 常用方法
-
public int getLength()
:獲得發(fā)送端實(shí)際發(fā)送的字節(jié)數(shù)或接收端世界接收的字節(jié)數(shù) -
public int getPort()
:獲得發(fā)送端或接收端端口號(hào)
-
-
發(fā)送數(shù)據(jù)包類DatagramSocket
- 作用:用來發(fā)送和接收數(shù)據(jù)包對(duì)象
- 構(gòu)造方法
-
DatagramSocket()
:構(gòu)造數(shù)據(jù)報(bào)套接字并將其綁定到本地主機(jī)上任何可用的端口。 -
DatagramSocket(int port)
:創(chuàng)建數(shù)據(jù)包套接字并將其綁定到本地主機(jī)上指定端口。
-
- 常用方法
-
public void send(DatagramPacket p)
:從此套接字發(fā)送數(shù)據(jù)報(bào)包 -
public void receive(DatagramPacket p)
:從此套接字接收數(shù)據(jù)報(bào)包 -
public void close()
:關(guān)閉此數(shù)據(jù)報(bào)套接字
-
-
InetAddress類(無構(gòu)造方法)
- 作用:代表一個(gè)IP地址
- 靜態(tài)方法
-
public static InetAddress getLocalHost()
:返回本地主機(jī) -
public static InetAddress getByName()
:在給定主機(jī)名的情況下確定主機(jī)的 IP 地址。
-
- 普通方法
-
public String getHostName()
: 獲取此 IP 地址的主機(jī)名。 -
public String getHostAddress()
:返回 IP 地址字符串(以文本表現(xiàn)形式)
-
六.基于UDP的Socket通信
-
步驟分析
- 服務(wù)器端先啟動(dòng),創(chuàng)建DatagramSocket對(duì)象,監(jiān)聽端口,用于接收
- 服務(wù)器端創(chuàng)建DatagramPacket對(duì)象,打包用于接收的數(shù)據(jù)包
- 服務(wù)器阻塞等待接收
- 客戶端啟動(dòng),創(chuàng)建DatagramSocket對(duì)象,監(jiān)聽端口,用于接收
- 客戶端創(chuàng)建DatagramPacket對(duì)象,打包用于發(fā)送的數(shù)據(jù)包
- 客戶端發(fā)送數(shù)據(jù),服務(wù)端接收
- 服務(wù)端接收數(shù)據(jù)后,創(chuàng)建DatagramPacket對(duì)象,打包用于發(fā)送的數(shù)據(jù)包,發(fā)送數(shù)據(jù)
- 客戶端創(chuàng)建DatagramPacket對(duì)象,打包用于接收的數(shù)據(jù)包,阻塞等待接收
- 客戶端接收服務(wù)端數(shù)據(jù),斷開連接,釋放資源
服務(wù)器端:
public class UdpEchoServer {
// 需要先定義一個(gè) socket 對(duì)象.
// 通過網(wǎng)絡(luò)通信, 必須要使用 socket 對(duì)象.
private DatagramSocket socket = null;
// 綁定一個(gè)端口, 不一定能成功!!
// 如果某個(gè)端口已經(jīng)被別的進(jìn)程占用了, 此時(shí)這里的綁定操作就會(huì)出錯(cuò).
// 同一個(gè)主機(jī)上, 一個(gè)端口, 同一時(shí)刻, 只能被一個(gè)進(jìn)程綁定.
public UdpEchoServer(int port) throws SocketException {
// 構(gòu)造 socket 的同時(shí), 指定要關(guān)聯(lián)/綁定的端口.
socket = new DatagramSocket(port);
}
// 啟動(dòng)服務(wù)器的主邏輯.
public void start() throws IOException {
System.out.println("服務(wù)器啟動(dòng)!");
while (true) {
// 每次循環(huán), 要做三件事情:
// 1. 讀取請(qǐng)求并解析
// 構(gòu)造空飯盒
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
// 食堂大媽給飯盒里盛飯. (飯從網(wǎng)卡上來的)
socket.receive(requestPacket);
// 為了方便處理這個(gè)請(qǐng)求, 把數(shù)據(jù)包轉(zhuǎn)成 String
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
// 2. 根據(jù)請(qǐng)求計(jì)算響應(yīng)(此處省略這個(gè)步驟)
String response = process(request);
// 3. 把響應(yīng)結(jié)果寫回到客戶端
// 根據(jù) response 字符串, 構(gòu)造一個(gè) DatagramPacket .
// 和請(qǐng)求 packet 不同, 此處構(gòu)造響應(yīng)的時(shí)候, 需要指定這個(gè)包要發(fā)給誰.
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
// requestPacket 是從客戶端這里收來的. getSocketAddress 就會(huì)得到客戶端的 ip 和 端口
requestPacket.getSocketAddress());
socket.send(responsePacket);
System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
requestPacket.getPort(), request, response);
}
}
// 這個(gè)方法希望是根據(jù)請(qǐng)求計(jì)算響應(yīng).
// 由于咱們寫的是個(gè) 回顯 程序. 請(qǐng)求是啥, 響應(yīng)就是啥!!
// 如果后續(xù)寫個(gè)別的服務(wù)器, 不再回顯了, 而是有具體的業(yè)務(wù)了, 就可以修改 process 方法,
// 根據(jù)需要來重新構(gòu)造響應(yīng).
// 之所以單獨(dú)列成一個(gè)方法, 就是想讓同學(xué)們知道, 這是一個(gè)服務(wù)器中的關(guān)鍵環(huán)節(jié)!!!
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
udpEchoServer.start();
}
}
UdpEchoServer(UDP 服務(wù)器):
a. 首先,創(chuàng)建一個(gè)
DatagramSocket
對(duì)象,并綁定到指定的端口。這個(gè)端口用于服務(wù)器與客戶端之間的通信。b. 在服務(wù)器的主循環(huán)中,首先創(chuàng)建一個(gè)空的
DatagramPacket
對(duì)象,用于接收客戶端發(fā)來的請(qǐng)求數(shù)據(jù)。c. 調(diào)用
socket.receive(requestPacket)
方法接收客戶端發(fā)來的數(shù)據(jù)包。d. 將收到的數(shù)據(jù)包中的數(shù)據(jù)轉(zhuǎn)換成字符串形式,并調(diào)用
process()
方法生成響應(yīng)。在這個(gè)例子中,響應(yīng)就是原請(qǐng)求。e. 創(chuàng)建一個(gè)新的
DatagramPacket
對(duì)象,包含響應(yīng)數(shù)據(jù)和客戶端的地址信息。f. 使用
socket.send(responsePacket)
方法將響應(yīng)數(shù)據(jù)包發(fā)送回客戶端。g. 打印請(qǐng)求和響應(yīng)信息。
?客戶端:
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIP;
private int serverPort;
// 客戶端啟動(dòng), 需要知道服務(wù)器在哪里!!
public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
// 對(duì)于客戶端來說, 不需要顯示關(guān)聯(lián)端口.
// 不代表沒有端口, 而是系統(tǒng)自動(dòng)分配了個(gè)空閑的端口.
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
public void start() throws IOException {
// 通過這個(gè)客戶端可以多次和服務(wù)器進(jìn)行交互.
Scanner scanner = new Scanner(System.in);
while (true) {
// 1. 先從控制臺(tái), 讀取一個(gè)字符串過來
// 先打印一個(gè)提示符, 提示用戶要輸入內(nèi)容
System.out.print("-> ");
String request = scanner.next();
// 2. 把字符串構(gòu)造成 UDP packet, 并進(jìn)行發(fā)送.
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
InetAddress.getByName(serverIP), serverPort);
socket.send(requestPacket);
// 3. 客戶端嘗試讀取服務(wù)器返回的響應(yīng)
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket);
// 4. 把響應(yīng)數(shù)據(jù)轉(zhuǎn)換成 String 顯示出來.
//這行代碼將接收到的字節(jié)流數(shù)據(jù)按照指定的編碼格式轉(zhuǎn)換成字符串,方便我們查看和處理數(shù)據(jù)。
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
System.out.printf("req: %s, resp: %s\n", request, response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);
udpEchoClient.start();
}
}
UdpEchoClient(UDP 客戶端):
a. 創(chuàng)建一個(gè)
DatagramSocket
對(duì)象,不需要顯式地關(guān)聯(lián)端口,系統(tǒng)會(huì)自動(dòng)分配一個(gè)空閑的端口。b. 在客戶端的主循環(huán)中,從控制臺(tái)讀取用戶輸入的字符串作為請(qǐng)求。
c. 創(chuàng)建一個(gè)
DatagramPacket
對(duì)象,包含請(qǐng)求數(shù)據(jù)、服務(wù)器的 IP 地址和端口信息。d. 調(diào)用
socket.send(requestPacket)
方法將請(qǐng)求數(shù)據(jù)包發(fā)送給服務(wù)器。e. 創(chuàng)建一個(gè)空的
DatagramPacket
對(duì)象,用于接收服務(wù)器返回的響應(yīng)數(shù)據(jù)。f. 調(diào)用
socket.receive(responsePacket)
方法接收服務(wù)器發(fā)來的響應(yīng)數(shù)據(jù)包。g. 將收到的響應(yīng)數(shù)據(jù)包中的數(shù)據(jù)轉(zhuǎn)換成字符串形式,并打印請(qǐng)求和響應(yīng)信息。
總結(jié):這兩段代碼實(shí)現(xiàn)了一個(gè)簡單的 UDP 回顯服務(wù)器和客戶端??蛻舳藢⒂脩糨斎氲恼?qǐng)求數(shù)據(jù)通過 UDP 協(xié)議發(fā)送給服務(wù)器,服務(wù)器接收到請(qǐng)求后原樣返回響應(yīng),客戶端接收響應(yīng)并打印信息。整個(gè)過程使用無連接的 UDP 協(xié)議進(jìn)行通信。
七. TCP和UCP的緩沖區(qū)
1.TCP的緩沖區(qū)
創(chuàng)建一個(gè)TCP的socket,同時(shí)在內(nèi)核中創(chuàng)建一個(gè) 發(fā)送緩沖區(qū) 和一個(gè) 接收緩沖區(qū);
調(diào)用write時(shí),數(shù)據(jù)會(huì)先寫入發(fā)送緩沖區(qū)中;
如果發(fā)送的字節(jié)數(shù)太長,會(huì)被拆分成多個(gè)TCP的數(shù)據(jù)包發(fā)出;
如果發(fā)送的字節(jié)數(shù)太短,就會(huì)先在緩沖區(qū)里等待,等到緩沖區(qū)長度差不多了,或者其他合適
的時(shí)機(jī)發(fā)送出去;
接收數(shù)據(jù)的時(shí)候,數(shù)據(jù)也是從網(wǎng)卡驅(qū)動(dòng)程序到達(dá)內(nèi)核的接收緩沖區(qū);
然后應(yīng)用程序可以調(diào)用read從接收緩沖區(qū)拿數(shù)據(jù);
另一方面,TCP的另一個(gè)連接,既有發(fā)送緩沖區(qū),也有接收緩沖區(qū),那么對(duì)于這一個(gè)連接,既
可以讀數(shù)據(jù),也可以寫數(shù)據(jù)。這個(gè)概念叫做 全雙工
2.UDP的緩沖區(qū)
UDP只有接收緩沖區(qū),沒有發(fā)送緩沖區(qū):
UDP沒有真正意義上的 發(fā)送緩沖區(qū)。發(fā)送的數(shù)據(jù)會(huì)直接交給內(nèi)核,由內(nèi)核將數(shù)據(jù)傳給網(wǎng)絡(luò)層協(xié)議
進(jìn)行后續(xù)的傳輸動(dòng)作;
UDP具有接收緩沖區(qū),但是這個(gè)接收緩沖區(qū)不能保證收到的UDP報(bào)的順序和發(fā)送UDP報(bào)的順序一
致;如果緩沖區(qū)滿了,再到達(dá)的UDP數(shù)據(jù)就會(huì)被丟棄;文章來源:http://www.zghlxwxcb.cn/news/detail-437173.html
?文章來源地址http://www.zghlxwxcb.cn/news/detail-437173.html
到了這里,關(guān)于Socket套接字編程(實(shí)現(xiàn)TCP和UDP的通信)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!