Java 網絡編程(二)—— TCP流套接字編程

TCP 和 UDP 的區別

在傳輸層,TCP 協議是有連接的,可靠傳輸,面向字節流,全雙工
而UDP 協議是無連接的,不可靠傳輸,面向數據報,全雙工

有連接和無連接的區別是在進行網絡通信的時候,通信雙方有沒有保存對端的地址信息,即假設 A 和 B 進行通信,A 保存了 B 的地址信息,B 也保存了 A 的地址信息,此時雙方都知道和誰建立了連接,這就是有連接的通信,在之前的 UDP 數據報套接字編程中就提到過 UDP 是無連接的,所以在發送數據報的時候要加上對端的信息,防止丟包。

可靠傳輸是通過各種手段來防止丟包的出現,而不可靠傳輸則沒有做任何處理直接把數據報傳輸過去,但是可靠傳輸不意味著能 100% 把數據報完整無誤地傳輸給對方,只是盡可能降低丟包發生的概率,并且可靠傳輸是要使用很多手段來保持的,所以付出的代價相比于不可靠傳輸要大。

面向字節流就是以字節為單位來進行數據的傳輸,面向數據報就是以數據報為單位進行數據的傳輸。

全雙工就是通信的雙發可以同時給對方發送數據,但是半雙工是指雙方只有一方可以發送數據。

TCP流套接字 API 介紹

ServerSocket

ServerSocket 是TCP服務端Socket 的API

構造方法:

方法名說明
ServerSocket(int port)創建一個TCP服務端流套接字Socket,并綁定端口號

ServerSocket 方法:

方法名返回值說明
accept()Socket開始監聽指定端口(創建時綁定的端口),有客戶端連接后,返回一個服務端Socket 對象,并基于該Socket 建立于客戶端的連接,否則阻塞等待
close()void關閉此套接字

Socket

Socket 是客戶端Socket 或者是 服務端那邊收到客戶端建立連接的請求(通過 accept() 方法)返回的Socket 對象。

不管是客戶端還是服務端的Socket 對象,他們都保留了對端的地址信息,這也是TCP協議有連接的體現。

Socket 構造方法:

方法名說明
Socket(String host, int port)創建一個客戶端流套接字Socket,并于對應IP 的主機對應的端口的進程建立連接

Socket 方法:

方法名返回值說明
getInetAddress()InetAddress返回套接字所連接的地址
getInputStream()InputStream返回此套接字的輸入流
getOutputStream()OutputStream返回此套接字的輸出流

回顯服務器

首先在回顯服務器的構造方法里初始化我們的ServerSocket

    public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}

然后就是服務器啟動運行的代碼了:在面對多個客戶端的時候,我們可以使用線程池來進行處理。
這里使用Executors.newCachedThreadPool()是不固定線程的個數的線程池,這樣可以靈活地處理多個客戶端的請求。

    public void start() throws IOException {System.out.println("服務器啟動...");ExecutorService executorService = Executors.newCachedThreadPool();while(true) {//與客戶端建立連接Socket clientSocket = serverSocket.accept();//處理客戶端發出的多個請求executorService.submit(() -> {try {processClient(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});}}

處理請求

我們通過了一個方法processClient來封裝了處理請求的邏輯

如何進行數據的獲取和寫入操作?
可以通過輸入流和輸出流來處理getInputStreamgetOutputStream

try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) 

為了更加方便地使用這兩個流對象,我們進行了進一步的封裝:

//對輸入流和輸出流進行進一步的封裝,方便我們的使用
Scanner scanner = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);

由于客戶端可能發來的不止一個請求,我們可以使用循環來處理一下,在循環體中,我們處理請求有三個步驟,首先獲取請求解析請求,然后計算響應,最后發送響應

            while(true) {if(!scanner.hasNext()) {System.out.printf("[%s:%d] 客戶端下線\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}//解析請求String request = scanner.next();//計算響應String response = process(request);//發送響應writer.println(response);//因為此時的響應數據還在緩存區里,所以需要使用 flush 來將內存的數據發送出去writer.flush();System.out.printf("[%s:%d] request:%s response:%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);}

由于這里是回顯服務器,所以計算響應的代碼是直接返回字符串就可以了

    private String process(String request) {return request;}

最后當客戶端沒有請求的時候,我們需要斷開此次連接,釋放資源,避免資源的泄漏

 finally {//當請求處理完的時候記得關閉服務器與客戶端的連接,防止資源泄漏clientSocket.close();}

最終代碼

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.ExecutorService;
import java.util.concurrent.Executors;public class TcpEchoServer {private ServerSocket serverSocket;public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服務器啟動...");ExecutorService executorService = Executors.newCachedThreadPool();while(true) {//與客戶端建立連接Socket clientSocket = serverSocket.accept();//處理客戶端發出的多個請求executorService.submit(() -> {try {processClient(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});}}private void processClient(Socket clientSocket) throws IOException {//獲取輸入流和輸出流try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {System.out.printf("[%s:%d] 客戶端上線\n",clientSocket.getInetAddress(),clientSocket.getPort());//對輸入流和輸出流進行進一步的封裝,方便我們的使用Scanner scanner = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);while(true) {if(!scanner.hasNext()) {System.out.printf("[%s:%d] 客戶端下線\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}//解析請求String request = scanner.next();//計算響應String response = process(request);//發送響應writer.println(response);//因為此時的響應數據還在緩存區里,所以需要使用 flush 來將內存的數據發送出去writer.flush();System.out.printf("[%s:%d] request:%s response:%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);}} catch (IOException e) {throw new RuntimeException(e);} finally {//當請求處理完的時候記得關閉服務器與客戶端的連接,防止資源泄漏clientSocket.close();}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
}

客戶端

首先在客戶端構造方法建立于服務器的連接:

    public TcpEchoClient(String serverIP, int port) throws IOException {//與服務器建立連接socket = new Socket(serverIP,port);}

運行邏輯

首先用戶從控制臺輸入數據,然后發送請求,接著等待服務器的響應并接收響應然后打印響應的內容即可。

    public void start() {try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {//對輸入流和輸出流進行進一步的封裝Scanner scanner = new Scanner(System.in);Scanner scanner2 = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);while(true) {//發送多個請求和接收多個響應if(!scanner.hasNext()) {break;}//發送請求String request = scanner.next();writer.println(request);writer.flush();//接收響應String response = scanner2.next();System.out.println(response);}} catch (IOException e) {throw new RuntimeException(e);}}

這里要注意用戶通過控制臺輸入數據,我們要使用的是Scanner(System.in)
當我們要發送數據的時候是使用 Socket 的 getOutputStream 方法來獲取對應的輸出流對象,為了便于使用所以我們又使用 PrintWriter 來進一步封裝輸出流,來打印響應
在發送請求的時候我們需要使用 Socket 的 getInputStream 方法來獲得輸入流對象,為了方便使用,所以使用Scanner(inputStream)進一步封裝。

最終代碼

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;public TcpEchoClient(String serverIP, int port) throws IOException {//與服務器建立連接socket = new Socket(serverIP,port);}public void start() {try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {//對輸入流和輸出流進行進一步的封裝Scanner scanner = new Scanner(System.in);Scanner scanner2 = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);while(true) {//發送多個請求和接收多個響應if(!scanner.hasNext()) {break;}//發送請求String request = scanner.next();writer.println(request);writer.flush();//接收響應String response = scanner2.next();System.out.println(response);}} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);client.start();}
}

細節說明

在我們使用PrintWriter 的 writer.println(xxx)之后,我們的數據其實還保留在緩存區中,也就是還沒發出去,我們需要通過flush() 方法來刷新緩存區的數據,才能將數據真正發送到對端去。


我們不可以使用writer.print這種沒有自動添加換行符的方法,因為我們在接收數據的時候,使用的是Scanner 的 next()方法,next() 是要接收到空白符(包括換行符,制表符,翻頁符…)才停止接收的,如果你使用 print 來發送數據,這時候的數據是沒有帶任何空白符的,那么就不會停止接收數據而是繼續等待空白符的到來,這時候服務器就無法處理客戶端的請求:如下圖:

服務器就阻塞在 下圖標紅的代碼里:
在這里插入圖片描述

客戶端被阻塞在接收響應的代碼里:
在這里插入圖片描述


你在客戶端的控制臺輸入的回車不算進數據的換行符里,控制臺輸入的回車時,只是將數據交給了客戶端程序,并不會自動將這些數據轉換為網絡流中的換行符。

換一句話說,控制臺的回車只是結束你在控制臺的輸入,并不會自動在數據末尾加上換行符

效果展示

在這里插入圖片描述
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/71998.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/71998.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/71998.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MySQL 事務筆記

MySQL 事務筆記 目錄 事務簡介事務操作事務四大特性并發事務問題事務隔離級別總結 事務簡介 事務(Transaction)是數據庫操作的邏輯單元,由一組不可分割的SQL操作組成。主要用于保證: 多個操作的原子性(要么全部成功…

GPT1 與 GPT2 的異同

1.什么是GPT1: GPT1介紹了一種通過生成式預訓練(Generative Pre-Training)來提升語言理解能力的方法。這種方法首先在一個大型的未標注文本語料庫上進行語言模型的預訓練,然后針對具體的任務進行判別式微調(discrimin…

Android Audio其他——數字音頻接口(附)

數字音頻接口 DAI,即 Digital Audio Interfaces,顧名思義,DAI 表示在板級或板間傳輸數字音頻信號的方式。相比于模擬接口,數字音頻接口抗干擾能力更強,硬件設計簡單,DAI 在音頻電路設計中得到越來越廣泛的應用。 一、音頻鏈路 1、模擬音頻信號 可以看到在傳統的…

kafka-leader -1問題解決

一. 問題: 在 Kafka 中,leader -1 通常表示分區的領導者副本尚未被選舉出來,或者在獲取領導者信息時出現了問題。以下是可能導致出現 kafka leader -1 的一些常見原因及相關分析: 1. 副本同步問題: 在 Kafka 集群中&…

DeepSeek基礎之機器學習

文章目錄 一、核心概念總結(一)機器學習基本定義(二)基本術語(三)假設空間(四)歸納偏好(五)“沒有免費的午餐”定理(NFL 定理) 二、重…

【jira】用到幾張表

jira用到的幾張表 測試計劃,測試周期,測試用例,問題記錄 1. 測試計劃 # 記錄表,查計劃詳情 SELECT ID,issuenum,SUMMARY FROM jiraissue where issuenum 22871# 測試計劃下,測試周期,查測試周期id&…

Mysql 死鎖場景及解決方案

一、常見死鎖場景 1. 不同順序的鎖獲取 場景:事務A按順序更新 行1 → 行2,事務B按 行2 → 行1 順序更新。 原因:雙方各持有一把鎖,同時請求對方持有的鎖,形成循環等待。 2. 索引缺失導致鎖升級 場景:更…

Spring Boot從入門到精通:一站式掌握企業級開發

前言 Spring Boot作為Java領域最流行的微服務框架,憑借其約定優于配置的理念和快速啟動的特性,極大簡化了Spring應用的初始搭建和開發過程。本文將帶你從零開始系統學習Spring Boot,最終實現精通級應用開發,涵蓋核心原理、實戰技…

【Go】十六、protobuf構建基礎服務信息、grpc服務啟動的基礎信息

商品服務 服務結構 創建 goods 服務,將之前 user 服務的基本結構遷移到 goods 服務上,完整目錄是: mxshop_srvs user_srv … tmp … goods_srv config config.go 配置的讀取表 global global.go 數據庫、日志初始化、全局變量定義 handler …

Redis 持久化方式:RDB(Redis Database)和 AOF(Append Only File)

本部分內容是關于博主在學習 Redis 時關于持久化部分的記錄,介紹了 RDB 和 AOF 兩種持久化方式,詳細介紹了持久化的原理、配置、使用方式、優缺點和使用場景。并對兩種持久化方式做了對比。文章最后介紹了 Redis 持久化的意義并與其他常見的緩存技術做了…

Linux中lshw相關的命令

? lshw(List Hardware)是一個在 Linux 系統中用于顯示硬件詳細信息的強大工具。以下是一些常見的 lshw 相關命令及其用法: 1. 安裝 lshw 在使用 lshw 之前,你可能需要先安裝它。不同的 Linux 發行版安裝方式有所不同&#xff1…

爬蟲第九篇-結束爬蟲循環

最近在學習Python爬蟲的過程中,遇到了一個很有趣的問題:如何優雅地結束爬蟲循環?今天,我想和大家分享一下我的發現和心得。 一、爬蟲循環結束的常見問題 在寫爬蟲時,我們經常會遇到這樣的情況:當爬取到的…

Vue3狀態管理新選擇:Pinia使用完全指南

一、為什么需要狀態管理? 在Vue應用開發中,當我們的組件樹變得復雜時,組件間的數據傳遞會成為棘手的問題。傳統方案(如props/$emit)在多層嵌套組件中會變得笨拙,這時狀態管理工具應運而生。Vue3帶來了全新…

一文掌握模擬登錄的基本原理和實戰

文章目錄 1. 模擬登錄的基本原理1.1 登錄流程1.2 關鍵技術2. 模擬登錄的實戰步驟2.1 分析登錄頁面2.2 使用 Requests 實現模擬登錄2.3 處理驗證碼2.4 使用 Selenium 實現模擬登錄3. 實戰案例:模擬登錄豆瓣3.1 分析豆瓣登錄頁面3.2 實現代碼4. 注意事項5. 總結模擬登錄是爬蟲開…

推薦算法工程師的技術圖譜和學習路徑

推薦算法工程師的技術圖譜和學習路徑可以從多個維度進行概述,可以總結如下: 一、技術圖譜 推薦算法工程師需要掌握的技術棧主要分為以下幾個方面: 數學基礎: 微積分、線性代數、概率論與統計學是推薦算法的基礎,用于理解模型的數學原理和優化算法。高等數學、最優化理論…

ONNX轉RKNN的環境搭建

將ONNX模型轉換為RKNN模型的過程記錄 工具準備 rknn-toolkit:https://github.com/rockchip-linux/rknn-toolkit rknn-toolkit2:https://github.com/airockchip/rknn-toolkit2 rknn_model_zoo:https://github.com/airockchip/rknn_model_zoo ultralytics_yolov8:https://github…

華為認證考試證書下載步驟(紙質+電子版)

華為考試證書可以通過官方渠道下載相應的電子證書,部分高級認證如HCIE還支持申請紙質證書。 一、華為電子版證書申請步驟如下: ①訪問華為培訓與認證網站 打開瀏覽器,登錄華為培訓與認證官方網站 ②登錄個人賬號 在網站首頁,點…

面試八股文--數據庫基礎知識總結(2) MySQL

本文介紹關于MySQL的相關面試知識 一、關系型數據庫 1、定義 關系型數據庫(Relational Database)是一種基于關系模型的數據庫管理系統(DBMS),它將數據存儲在表格(表)中,并通過表格…

介紹下pdf打印工具類 JasperPrint

JasperPrint 工具類深度解析 JasperPrint 是 JasperReports 框架中實現 PDF 打印的核心載體類,其本質是 填充數據后的可打印報表對象,承擔著從模板編譯、數據填充到格式輸出的全流程控制。以下從 7 個維度展開深度解析: 一、核心定位與生命周…

LVS+Keepalived 高可用集群搭建

一、高可用集群: 1.什么是高可用集群: 高可用集群(High Availability Cluster)是以減少服務中斷時間為目地的服務器集群技術它通過保護用戶的業務程序對外不間斷提供的服務,把因軟件、硬件、人為造成的故障對業務的影響…