活動發起人@小虛竹 想對你說:
這是一個以寫作博客為目的的創作活動,旨在鼓勵大學生博主們挖掘自己的創作潛能,展現自己的寫作才華。如果你是一位熱愛寫作的、想要展現自己創作才華的小伙伴,那么,快來參加吧!我們一起發掘寫作的魅力,書寫出屬于我們的故事。我們誠摯邀請你參加為期14天的創作挑戰賽!
提醒:在發布作品前,請將不需要的內容刪除。
?各位看官,大家早安午安晚安呀~~~
如果您覺得這篇文章對您有幫助的話
歡迎您一鍵三連,小編盡全力做到更好
歡迎您分享給更多人哦
今天我們來學習【Java】TCP網絡編程:從可靠傳輸到Socket實戰
目錄
1.首先我們再說一下TCP和UDP的區別和相同點
2.連接:通信雙方都會記錄對方的信息
3.主要是兩個api ServerSocket和Socket
4.TCP服務端實戰代碼演示
5.TCP客戶端實戰代碼演示
TCP的socket的api的差異很大,但是和前面的IO有很大的關聯
1.首先我們再說一下TCP和UDP的區別和相同點
1.TCP是有連接的,UDP無連接(這一點可以在代碼中體現)
2.TCP是面向字節流流的,UDP是面向數據報的
3.TCP是可靠傳輸的,UDP是不可靠傳輸的(這一點在代碼中體現不出來)
4.TCP和UDP都是全雙工的
2.連接:通信雙方都會記錄對方的信息
UDP:每次發送數據報都要指定對方的地址(UDP沒有存儲這個信息)
一張圖
TCP:不需要(不過需要內核自動和客戶端建連接(TCP的三次握手,后面我會進行講解)這個過程是系統內核自動完成的)
對于應用程序來說,客戶端是發起“建立連接”
服務器這邊:把內核中建立好的連接拿到應用程序里面()
這個ServerSocket只負責綁定端口號,然后通過accept方法 把建立好的連接拿過來(但是一瞬間有很多連接的話,就像生產者消費者模型里面只能進行阻塞等待)
3.主要是兩個api ServerSocket和Socket
ServerSocket是給服務器用的類,使用這個類用來綁定端口號(這個類負責把系統內核里面已經建立好的連接從隊列里面拿過來,)
Socket:既會給服務器使用的類,也會給客戶端使用,通過socket這個對象和客戶端進行交互
(這兩個類都是用來表示socket文件的,抽象了網卡這樣的硬件設備)
4.TCP服務端實戰代碼演示
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;public class TcpEchoServer { // 讀取用Scanner ,發送用PrintWriter// 1.首先serverSocket調用系統API把連接拿過來,然后交給socket// 我們就有了socket這個對象,這個時候我們就可以宣布這個客戶端成功和我們服務器建立了聯系并且我們拿到了// 2.然后我們通過字節流把數據從socket抽象的文件里面讀取到,用try包起來,用scanner從流里面讀取// 3. 我們拿到字符串進行響應// 4. 把返回的字符串通過字節流(我們的字符串通過字符流轉換成字節流)寫回去// 5. 把信息打印出來private ServerSocket serverSocket = null;public TcpEchoServer(int serverPort) throws IOException {// 利用這個系統API從內核中取到已經建立好的連接// 這個和客戶端構造的socket完全不一樣,客戶端的那個是我們已經拿到的連接serverSocket = new ServerSocket(serverPort); // 服務器自己分配端口號,}public void start() throws IOException {System.out.println("服務器啟動");// 把隊列里面建立好的連接拿過來while(true){ // 要一直不斷地從那個內核里面不斷地拿我們已經建立好的連接!!!Socket Clientsocket = serverSocket.accept();/* //創建一個新的線程把這個請求進行響應Thread t = new Thread(() ->{possessCollection(Clientsocket);});t.start();*/// 但是線程池是更好一點點的選擇ExecutorService service = Executors.newCachedThreadPool();service.submit(() ->{possessCollection(Clientsocket);});}}public void possessCollection(Socket Clientsocket){ //System.out.printf("[%s,%d] 客戶端上線\n" , Clientsocket.getInetAddress(),Clientsocket.getPort());// 把端口號和IP拿到try( InputStream inputStream = Clientsocket.getInputStream(); // 這里要用 " ; "OutputStream outputStream = Clientsocket.getOutputStream()){// 客戶可能等會還會繼續發送請求(我們循環處理)Scanner scanner = new Scanner(inputStream);//每一次讀一次緩沖區,緩沖區里面的東西就少一次,都被我讀出來了嘛while(true){if(!scanner.hasNext()){// 用戶不再輸入的時候,就直接跳出循環!!!,一直等待用戶輸入System.out.printf("[%s,%d] 客戶端下線\n" , Clientsocket.getInetAddress(),Clientsocket.getPort());break;}String request = scanner.next();// 拿到字符串進行響應String response = process(request);// 拿到響應//4. 拿到字符串的響應,然后我們通過字符流轉字節流傳遞出去PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);// 此處的println不是寫到控制臺了,而是寫到outputStream的流對象了,也就是寫入到ClientSocket里面了// 自然這個數據也是通過網卡發出去了printWriter.flush();// 再刷新一下緩存,防止沒有發出去,// 總結: 發送和接收數據都是通過socket文件的字節流輸入輸出來實現,用scanner讀字節流,用printWriter寫字符串// 5.打印一下交互過程System.out.printf("[%s,%d] req=%s resp=%s\n",Clientsocket.getInetAddress(),Clientsocket.getPort(),request,response);}}catch(IOException e){e.printStackTrace();}finally{try {Clientsocket.close();} catch (IOException e) {throw new RuntimeException(e);}}}private String process(String request) { // 回顯服務器return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);tcpEchoServer.start();}}
但是這里面會出現兩個問題
問題一:為什么我們的ServerSocket對象沒有進行close操作,但是Socket對象卻需要close操作呢?這樣不會出現文件資源泄露嗎?
首先我們需要知道什么時候回造成文件資源泄露?
一直頻繁申請但是一直不釋放就會(什么文件的表項啥的)
但是ServerSocket對象從頭到尾只創建過一次對象,而且一直在把內核中建立的連接拿到。所以說ServerSocket這個對象的生命周期是是伴隨著進程消失的(因此不需要特地的進行回收,等到進程解釋JVM會把這個進程里面的東西一起回收了)
但是?
問題二:等待我們寫完客戶端的代碼之后進行講述
如果啟動多個客戶端和服務器進行連接(就不行了,這個服務器一直在等待客戶端進行輸入,我們就需要多個線程并發拿到這個連接)
但是頻繁地創建和銷毀線程也會有很大開銷,線程池是更好一點點選擇
5.TCP客戶端實戰代碼演示
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;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 {/*** 1.隨機分配一個端口的發出信息(我們要把服務器的IP地址和端口號給搞進去)* 2.我們循環輸入一個字符串,把這個字符串用字符流轉換成字節流寫到socket抽象的文件里面* 3.然后我們接收響應(不像服務器,我們又不需要進行處理)* 4.我們直接通過Scanner把字節流里面的內容讀出來就好了*/private Socket socket = null;public TcpEchoClient(String serverIP,int serverPort) throws IOException {socket = new Socket(serverIP,serverPort);}public void start(){System.out.println(" -> ");Scanner scanner = new Scanner(System.in);try(OutputStream outputStream = socket.getOutputStream();InputStream inputStream = socket.getInputStream()){while(true){/* if(!scanner.hasNext()){ // 用戶不想輸入了,就直接退出了break; 不需要這個}*/// * 2.我們循環輸入一個字符串,把這個字符串用字符流轉換成字節流寫到socket抽象的文件里面String request = scanner.next();PrintWriter printWriter = new PrintWriter(outputStream); // 這些流盡量都放到try()里面printWriter.println(request);printWriter.flush();// 一定不要忘記刷新緩存區'// * 4.我們直接通過Scanner把字節流里面的內容讀出來就好了Scanner scannerNetwork = new Scanner(inputStream); // 這個Scanner盡量也是放到try()里面String response = scannerNetwork.next();System.out.println(response);}}catch(IOException e){e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);tcpEchoClient.start();}
}
上述就是【Java】TCp網絡編程:TCP網絡編程:從可靠傳輸到Socket實戰的全部內容啦
能看到這里相信您一定對小編的文章有了一定的認可。
有什么問題歡迎各位大佬指出
歡迎各位大佬評論區留言修正~~
