Socket網絡編程(四)——點對點傳輸場景方案

目錄

  • 場景
  • 如何去獲取到TCP的IP和Port?
  • UDP的搜索IP地址、端口號方案
  • UDP搜索取消實現
    • 相關的流程:
    • 代碼實現邏輯
      • 服務端實現
      • 客戶端實現
      • UDP搜索代碼執行結果
  • TCP點對點傳輸實現
    • 代碼實現步驟
    • 點對點傳輸測試結果
  • 源碼下載

場景

在一個局域網當中,不知道服務器的IP地址,僅僅知道服務器公共的UDP的端口,在這種情況下,想要實現TCP的連接。TCP是點對點的連接,所以需要知道TCP的連接IP地址和端口Port。

如何去獲取到TCP的IP和Port?

可以通過UDP的搜索實現,

  1. 當我們的服務器與我們所有的客戶端之間約定了搜索的格式之后,我們可以在客戶端發起廣播
  2. 然后服務器在收到廣播之后判斷一下這些收到的廣播是否是需要處理的。那么服務器就會回送這些廣播到對應的端口(地址)上去。
  3. 客戶端就能收到服務器回送過來的UDP的包。收到的這些數據包,里面就包含了端口號、IP地址等。
  4. 根據以上的流程就能夠UDP的搜索得到TCP服務器的IP地址和TCP的端口,然后使用這些信息來實現TCP的連接。

UDP的搜索IP地址、端口號方案

  1. 構建基礎口令消息
    原理:如果要實現UDP的交互,就要約定一組公共的數據格式,也就是基礎的口令頭。如果沒有約定口令消息,那么別人發送的消息到達我們的服務器后就會去回送,這就會導致我們自己的基本信息(比如IP\Port)的暴露。
  2. 局域網廣播口令消息(指定端口)
  3. 接收指定端口回送消息(得到客戶端IP、Port,這里的客戶端IP指的是server端)

20240228-154508-t3.png
如上圖,BroadCast發出廣播,如果有設備(服務器)感興趣就會回送到BroadCast。如果三臺(服務器)都感興趣,就都會回送到BroadCast。

UDP搜索取消實現

相關的流程:

  1. 異步線程接收回送消息
  2. 異步線程等待完成(定時)
  3. 關閉等待-終止線程等待

代碼實現邏輯

服務端實現

  1. TCP/UDP基礎信息字段
    TCPConstants.java
public class TCPConstants {// 服務器固化UDP接收端口public static int PORT_SERVER = 30401;
}
  1. UDP基礎信息
    UDPConstants.java
public class UDPConstants {// 公用頭部(8個字節都是7,就是可回復的)public static byte[] HEADER = new byte[]{7,7,7,7,7,7,7,7};// 服務器固化UDP接收端口public static int PORT_SERVER = 30201;// 客戶端回送端口public static int PORT_CLIENT_RESPONSE = 30202;
}
  1. 工具類ByteUtils
    用于校驗是否為正確的口令。即對HEADER進行校驗。
public class ByteUtils {/*** Does this byte array begin with match array content?** @param source Byte array to examine* @param match  Byte array to locate in <code>source</code>* @return true If the starting bytes are equal*/public static boolean startsWith(byte[] source, byte[] match) {return startsWith(source, 0, match);}/*** Does this byte array begin with match array content?** @param source Byte array to examine* @param offset An offset into the <code>source</code> array* @param match  Byte array to locate in <code>source</code>* @return true If the starting bytes are equal*/public static boolean startsWith(byte[] source, int offset, byte[] match) {if (match.length > (source.length - offset)) {return false;}for (int i = 0; i < match.length; i++) {if (source[offset + i] != match[i]) {return false;}}return true;}/*** Does the source array equal the match array?** @param source Byte array to examine* @param match  Byte array to locate in <code>source</code>* @return true If the two arrays are equal*/public static boolean equals(byte[] source, byte[] match) {if (match.length != source.length) {return false;}return startsWith(source, 0, match);}/*** Copies bytes from the source byte array to the destination array** @param source      The source array* @param srcBegin    Index of the first source byte to copy* @param srcEnd      Index after the last source byte to copy* @param destination The destination array* @param dstBegin    The starting offset in the destination array*/public static void getBytes(byte[] source, int srcBegin, int srcEnd, byte[] destination,int dstBegin) {System.arraycopy(source, srcBegin, destination, dstBegin, srcEnd - srcBegin);}/*** Return a new byte array containing a sub-portion of the source array** @param srcBegin The beginning index (inclusive)* @param srcEnd   The ending index (exclusive)* @return The new, populated byte array*/public static byte[] subbytes(byte[] source, int srcBegin, int srcEnd) {byte destination[];destination = new byte[srcEnd - srcBegin];getBytes(source, srcBegin, srcEnd, destination, 0);return destination;}/*** Return a new byte array containing a sub-portion of the source array** @param srcBegin The beginning index (inclusive)* @return The new, populated byte array*/public static byte[] subbytes(byte[] source, int srcBegin) {return subbytes(source, srcBegin, source.length);}
}
  1. 服務器端接收約定數據包,解析成功并回送包的代碼
    ServerProvider
public class ServerProvider {private static Provider PROVIDER_INSTANCE;static void start(int port){stop();String sn = UUID.randomUUID().toString();Provider provider = new Provider(sn, port);provider.start();PROVIDER_INSTANCE = provider;}static void stop(){if(PROVIDER_INSTANCE != null){PROVIDER_INSTANCE.exit();PROVIDER_INSTANCE = null;}}private static class Provider extends Thread{private final byte[] sn;private final int port;private boolean done = false;private DatagramSocket ds = null;// 存儲消息的Bufferfinal byte[] buffer = new byte[128];public Provider(String sn, int port){super();this.sn = sn.getBytes();this.port = port;}@Overridepublic void run() {super.run();System.out.println("UDDProvider Started.");try {// 監聽20000 端口ds = new DatagramSocket(UDPConstants.PORT_SERVER);// 接收消息的PacketDatagramPacket receivePacket = new DatagramPacket(buffer,buffer.length);while(!done){// 接收ds.receive(receivePacket);// 打印接收到的信息與發送者的信息// 發送者的IP地址String clientIp = receivePacket.getAddress().getHostAddress();int clientPort = receivePacket.getPort();int clientDataLen = receivePacket.getLength();byte[] clientData = receivePacket.getData();boolean isValid = clientDataLen >= (UDPConstants.HEADER.length + 2 + 4) && ByteUtils.startsWith(clientData,UDPConstants.HEADER);System.out.println("ServerProvider receive from ip:" + clientIp + "\tport:" + clientIp +"\tport:"+clientPort+"\tdataValid:"+isValid);if(!isValid){//無效繼續continue;}// 解析命令與回送端口int index = UDPConstants.HEADER.length;short cmd = (short) ((clientData[index++] << 8) | (clientData[index++] & 0xff));int responsePort = (((clientData[index++]) << 24) |((clientData[index++] & 0xff) << 16) |((clientData[index++] & 0xff) << 8) |((clientData[index++] & 0xff)));// 判斷合法性if( cmd == 1 && responsePort > 0){// 構建一份回送數據ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);byteBuffer.put(UDPConstants.HEADER);byteBuffer.putShort((short)2);byteBuffer.putInt(port);byteBuffer.put(sn);int len = byteBuffer.position();// 直接根據發送者構建一份回送信息DatagramPacket responsePacket = new DatagramPacket(buffer,len,receivePacket.getAddress(),responsePort);ds.send(responsePacket);System.out.println("ServerProvider response to:" + clientIp + "\tport:"+responsePort + "\tdataLen: " + len);}else {System.out.println("ServerProvider receive cmd nonsupport; cmd:" + cmd + "\tport:" + port);}}} catch (SocketException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}private void close() {if( ds != null ){ds.close();ds = null;}}/*** 提供結束*/void exit(){done = true;close();}}
}
  1. main方法啟動類
public class Server {public static void main(String[] args) {ServerProvider.start(TCPConstants.PORT_SERVER);try{System.in.read();} catch (IOException e){e.printStackTrace();}ServerProvider.stop();}
}

客戶端實現

客戶端廣播發送消息包代碼

  1. 服務器端消息實體
    ServerInfo
public class ServerInfo {private String sn;private int port;private String address;public ServerInfo(int port, String address, String sn) {this.sn = sn;this.port = port;this.address = address;}省略set/get方法 ……}
  1. 客戶端啟動main方法類
public class Client {public static void main(String[] args) {// 定義10秒的搜索時間,如果超過10秒未搜索到,就認為服務器端沒有開機ServerInfo info = ClientSearcher.searchServer(10000);System.out.println("Server:" + info);}
}
  1. 客戶端接收服務器端回送與廣播發送的具體邏輯
    ClientSearcher
public class ClientSearcher {private static final int LISTENT_PORT = UDPConstants.PORT_CLIENT_RESPONSE;public static ServerInfo searchServer(int timeout){System.out.println("UDPSearcher Started.");//  成功收到回送的柵欄CountDownLatch receiveLatch = new CountDownLatch(1);Listener listener = null;try{// 監聽listener = listen(receiveLatch);// 發送廣播sendBroadCast();// 等待服務器返回,最長阻塞10秒receiveLatch.await(timeout, TimeUnit.MILLISECONDS);}catch (Exception e){e.printStackTrace();}// 完成System.out.println("UDPSearcher Finished.");if(listener == null){return null;}List<ServerInfo> devices = listener.getServerAndClose();if(devices.size() > 0){return devices.get(0);}return null;}/*** 監聽服務器端回送的消息* @param receiveLatch* @return* @throws InterruptedException*/private static Listener listen(CountDownLatch receiveLatch) throws InterruptedException {System.out.println("UDPSearcher start listen.");CountDownLatch startDownLatch = new CountDownLatch(1);Listener listener = new Listener(LISTENT_PORT, startDownLatch,receiveLatch);listener.start();   // 異步操作,開啟端口監聽startDownLatch.await();return listener;}/*** 發送廣播邏輯* @throws IOException*/private static void sendBroadCast() throws IOException {System.out.println("UDPSearcher sendBroadcast started.");// 作為搜索方,讓系統自動分配端口DatagramSocket ds = new DatagramSocket();// 構建一份請求數據ByteBuffer byteBuffer = ByteBuffer.allocate(128);// 頭部byteBuffer.put(UDPConstants.HEADER);// CMD命名byteBuffer.putShort((short)1);// 回送端口信息byteBuffer.putInt(LISTENT_PORT);// 直接構建PacketDatagramPacket requestPacket = new DatagramPacket(byteBuffer.array(), byteBuffer.position() + 1);// 廣播地址requestPacket.setAddress(InetAddress.getByName("255,255.255.255"));// 設置服務器端口requestPacket.setPort(UDPConstants.PORT_SERVER);// 發送ds.send(requestPacket);ds.close();// 完成System.out.println("UDPSearcher sendBroadcast finished.");}/*** 廣播消息的接收邏輯*/private static class Listener extends Thread {private final int listenPort;private final CountDownLatch startDownLatch;private final CountDownLatch receiveDownLatch;private final List<ServerInfo> serverInfoList = new ArrayList<>();private final byte[] buffer = new byte[128];private final int minLen = UDPConstants.HEADER.length + 2 + 4; // 2:CMD命令長度  4:TCP端口號長度private boolean done = false;private DatagramSocket ds = null;private Listener(int listenPort,CountDownLatch startDownLatch,CountDownLatch receiveDownLatch){super();this.listenPort = listenPort;this.startDownLatch = startDownLatch;this.receiveDownLatch = receiveDownLatch;}@Overridepublic void run(){super.run();// 通知已啟動startDownLatch.countDown();try{// 監聽回送端口ds = new DatagramSocket(listenPort);// 構建接收實體DatagramPacket receivePacket = new DatagramPacket(buffer,buffer.length);while( !done){// 接收ds.receive(receivePacket);// 打印接收到的信息與發送者的信息// 發送者的IP地址String ip = receivePacket.getAddress().getHostAddress();int port = receivePacket.getPort();int dataLen = receivePacket.getLength();byte[] data = receivePacket.getData();boolean isValid = dataLen >= minLen&& ByteUtils.startsWith(data, UDPConstants.HEADER);System.out.println("UDPSearch receive form ip:" + ip + "\tport:" + port + "\tdataValid:" + isValid);if( !isValid ) {// 無效繼續continue;}// 跳過口令字節,從具體數據開始ByteBuffer byteBuffer = ByteBuffer.wrap(buffer,UDPConstants.HEADER.length, dataLen);final short cmd = byteBuffer.getShort(); // 占據2個字節final int serverPort = byteBuffer.getInt(); // 占據4個字節if(cmd != 2 || serverPort <= 0){System.out.println("UDPSearcher receive cmd:" + cmd + "\tserverPort:" + serverPort);continue;}String sn = new String(buffer,minLen,dataLen - minLen);ServerInfo info = new ServerInfo(serverPort,ip,sn);serverInfoList.add(info);// 成功接收到一份receiveDownLatch.countDown();}}catch (Exception e){e.printStackTrace();}finally {close();}System.out.println("UDPSearcher listener finished.");}private void close(){if(ds != null){ds.close();ds = null;}}List<ServerInfo> getServerAndClose() {done = true;close();return serverInfoList;}}
}

UDP搜索代碼執行結果

服務端啟動接收的結果:

UDDProvider Started.
ServerProvider receive from ip:169.254.178.74	port:169.254.178.74	port:61968	dataValid:true
ServerProvider response to:169.254.178.74	port:30202	dataLen: 50

客戶端監聽并發起廣播的執行結果:

UDPSearcher Started.
UDPSearcher start listen.
UDPSearcher sendBroadcast started.
UDPSearcher sendBroadcast finished.
UDPSearch receive form ip:169.254.178.74	port:30201	dataValid:true
UDPSearcher Finished.
Server:ServerInfo{sn='ed4ab162-5d5c-49eb-b80e-6ddeb8b223e0', port=30401, address='169.254.178.74'}
UDPSearcher listener finished.Process finished with exit code 0

由以上結果可知,啟動服務端后,客戶端在啟動listen監聽后,向服務器端發送數據包,并獲得服務器端的回送,經解析后,該回送的數據包中可以獲得 ip/port,可用于TCP連接使用。在UDP解析數據包過程中,通過口令保證了客戶端與服務端對消息發送、接收、回送的有效,避免不必要的回應。

TCP點對點傳輸實現

基于前面UDP廣播-搜索的機制,Server-Client獲得了建立Socket鏈接的IP\Port信息。
可以接著使用該信息進行建立TCP的Socket連接,實現點對點的數據收發。

代碼實現步驟

  1. TCP服務端main啟動方法
public class Server {public static void main(String[] args) {TCPServer tcpServer = new TCPServer(TCPConstants.PORT_SERVER);boolean isSucceed = tcpServer.start();if(!isSucceed){System.out.println("Start TCP server failed.");}UDPProvider.start(TCPConstants.PORT_SERVER);try{System.in.read();} catch (IOException e){e.printStackTrace();}UDPProvider.stop();tcpServer.stop();}
}

在UDP搜索的基礎上,我們獲得了TCP的鏈接IP。創建tcpServer對相應的端口進行監聽客戶端鏈接請求。

  1. 服務端異步線程處理Socket
    TCPServer
public class TCPServer {private final int port;private ClientListener mListener;/*** 構造* @param port*/public TCPServer(int port){this.port = port;}/*** 開始* @return*/public boolean start(){try{ClientListener listener = new ClientListener(port);mListener = listener;listener.start();}catch (Exception e){e.printStackTrace();return false;}return true;}/*** 結束*/public void stop(){if(mListener != null){mListener.exit();}}/*** 監聽客戶端鏈接*/private static class ClientListener extends Thread {private ServerSocket server;private boolean done = false;private ClientListener(int port) throws IOException {server = new ServerSocket(port);System.out.println("服務器信息: " + server.getInetAddress() + "\tP:" + server.getLocalPort());}@Overridepublic void run(){super.run();System.out.println("服務器準備就緒~");// 等待客戶端連接do{// 得到客戶端Socket client = null;try {client = server.accept();}catch (Exception e){e.printStackTrace();}// 客戶端構建異步線程ClientHandler clientHandler = new ClientHandler(client);// 啟動線程clientHandler.start();}while (!done);System.out.println("服務器已關閉!");}void exit(){done = true;try {server.close();}catch (IOException e){e.printStackTrace();}}}/*** 客戶端消息處理*/private static class ClientHandler extends Thread{private Socket socket;private boolean flag = true;ClientHandler(Socket socket ){this.socket = socket;}@Overridepublic void run(){super.run();System.out.println("新客戶鏈接: " + socket.getInetAddress() + "\tP:" + socket.getPort());try {// 得到打印流,用于數據輸出;服務器回送數據使用PrintStream socketOutput = new PrintStream(socket.getOutputStream());// 得到輸入流,用于接收數據BufferedReader socketInput = new BufferedReader(new InputStreamReader(socket.getInputStream()));do {// 客戶端拿到一條數據String str = socketInput.readLine();if( "bye".equalsIgnoreCase(str)){flag = false;// 回送socketOutput.println("bye");}else {// 打印到屏幕,并回送數據長度System.out.println(str);socketOutput.println("回送: " + str.length());}}while (flag);socketInput.close();socketOutput.close();}catch (IOException e){System.out.println("連接異常斷開");}finally {// 連接關閉try {socket.close();}catch (IOException e){e.printStackTrace();}}System.out.println("客戶端已退出:" + socket.getInetAddress() + "\tP:" + socket.getPort());}}
}

accept() 監聽到客戶端的鏈接后,通過輸入流讀取客戶端數據,并通過輸出流回送數據長度。

  1. 基于UDP回送結果建立的TCP客戶端
    Client main方法
public class Client {public static void main(String[] args) {// 定義10秒的搜索時間,如果超過10秒未搜索到,就認為服務器端沒有開機ServerInfo info = UDPSearcher.searchServer(10000);System.out.println("Server:" + info);if( info != null){try {TCPClient.linkWith(info);}catch (IOException e){e.printStackTrace();}}}
}

獲得UDP的回送后,我們知道了建立TCP的ip、port。也就是serverInfo不為null,取出相關參數建立Socket 鏈接。

  1. 建立客戶端連接類
    TCPClient
public class TCPClient {public static void linkWith(ServerInfo info) throws IOException {Socket socket = new Socket();// 超時時間socket.setSoTimeout(3000);// 端口2000;超時時間300mssocket.connect(new InetSocketAddress(Inet4Address.getByName(info.getAddress()),info.getPort()));//System.out.println("已發起服務器連接,并進入后續流程~");System.out.println("客戶端信息: " + socket.getLocalAddress() + "\tP:" + socket.getLocalPort());System.out.println("服務器信息:" + socket.getInetAddress() + "\tP:" + socket.getPort());try {// 發送接收數據todo(socket);}catch (Exception e){System.out.println("異常關閉");}// 釋放資源socket.close();System.out.println("客戶端已退出~");}private static void todo(Socket client) throws IOException {// 構建鍵盤輸入流InputStream in = System.in;BufferedReader input = new BufferedReader(new InputStreamReader(in));// 得到Socket輸出流,并轉換為打印流OutputStream outputStream = client.getOutputStream();PrintStream socketPrintStream = new PrintStream(outputStream);// 得到Socket輸入流,并轉換為BufferedReaderInputStream inputStream = client.getInputStream();BufferedReader socketBufferedReader = new BufferedReader(new InputStreamReader(inputStream));boolean flag = true;do {// 鍵盤讀取一行String str = input.readLine();// 發送到服務器socketPrintStream.println(str);// 從服務器讀取一行String echo = socketBufferedReader.readLine();if("bye".equalsIgnoreCase(echo)){flag = false;}else {System.out.println(echo);}}while(flag);// 資源釋放socketPrintStream.close();socketBufferedReader.close();}
}

建立Socket鏈接,從鍵盤讀取一行發送到服務器;并從服務器讀取一行。以上就是基于UDP廣播-搜索實現TCP點對點傳輸的邏輯。

點對點傳輸測試結果

基于UDP實現的TCP服務端日志

服務器信息: 0.0.0.0/0.0.0.0	P:30401
服務器準備就緒~
UDDProvider Started.
ServerProvider receive from ip:169.254.178.74	port:169.254.178.74	port:51322	dataValid:true
ServerProvider response to:169.254.178.74	port:30202	dataLen: 50
新客戶鏈接: /169.254.178.74	P:57172
ping
pong

基于UDP實現的TCP客戶端日志:

UDPSearcher start listen.
UDPSearcher sendBroadcast started.
UDPSearcher sendBroadcast finished.
UDPSearch receive form ip:169.254.178.74	port:30201	dataValid:true
UDPSearcher Finished.
Server:ServerInfo{sn='10595790-14d1-44dc-a068-4c64c956a944', port=30401, address='169.254.178.74'}
UDPSearcher listener finished.
已發起服務器連接,并進入后續流程~
客戶端信息: /169.254.178.74	P:57172
服務器信息:/169.254.178.74	P:30401
ping
回送: 4
pong
回送: 4

源碼下載

下載地址:https://gitee.com/qkongtao/socket_study/tree/master/src/main/java/cn/kt/socket/SocketDemo_L5_UDP

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

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

相關文章

生成式人工智能治理:入門的基本技巧

GenAI 以前所未有的速度調解并擾亂了“一切照舊”&#xff0c;同時帶來了令人難以置信的力量&#xff0c;但也帶來了不可否認的責任。當然&#xff0c;現代企業非常熟悉技術進步。然而&#xff0c;人工智能的到來&#xff08;和實施&#xff09;無疑引起了相當大的沖擊&#xf…

C# 12 中的新增功能

本文內容 主構造函數集合表達式ref readonly 參數默認 Lambda 參數 顯示另外 5 個 C# 12 包括以下新增功能。 可以使用最新的 Visual Studio 2022 版本或 .NET 8 SDK 嘗試這些功能。 主構造函數 - 在 Visual Studio 2022 版本 17.6 預覽版 2 中引入。 集合表達式 - 在 Visu…

ThreeJs同一個場景多個相機的顯示

在threeJs開發數字孿生中&#xff0c;我們正常是需要使用一個相機&#xff0c;畫面顯示的內容也就是這個相機拍攝到的內容&#xff0c;但是是否可以添加多個相機&#xff0c;可以同時從不同角度觀察模型呢&#xff0c;實際上是可以的&#xff0c;不過多個相機的拍攝到的畫面肯定…

Linux uname命令教程:了解系統信息和配置(附實例詳解和注意事項)

Linux uname命令介紹 uname&#xff08;Unix Name&#xff09;命令用于顯示系統信息&#xff0c;包括內核名稱、網絡節點名稱、操作系統名稱、版本號、硬件名稱和處理器類型。它是一個基本的系統管理工具&#xff0c;通常用于識別系統配置。 Linux uname命令適用的Linux版本 …

Linux服務:Nginx反向代理與負載均衡

一、Nginx反向代理 1、什么是反向代理&#xff1f; 代理分為兩類&#xff0c;正向代理和反向代理。 ①正向代理&#xff1a;幫助用戶訪問服務器&#xff0c;緩存服務器內容。 ②反向代理&#xff1a;代理服務器處理用戶的請求&#xff0c;決定轉發請求給誰處理負載均衡的作…

Go 與 Rust:導航編程語言景觀

在當今構建軟件時&#xff0c;開發者在編程語言上有著豐富的選擇。兩種脫穎而出的語言是 Go 和 Rust - 都很強大但卻截然不同。本文將從各種因素比較這兩種語言&#xff0c;以幫助您確定哪種更適合您的需求。 我們將權衡它們在并發、安全性、速度、互操作性等方面的方法。我們將…

Ubuntu篇——crontab修改編輯器

輸入命令: crontab -e 如果你的系統是第一次使用crontab服務&#xff0c;會首先讓你選擇一個編輯器 如果已經選擇過編輯器&#xff0c;后續想要修改默認編輯器&#xff0c;可以輸入sudo select-editor進行修改。

【GPU驅動開發】-GPU架構簡介

前言 不必害怕未知&#xff0c;無需恐懼犯錯&#xff0c;做一個Creator&#xff01; GPU&#xff08;Graphics Processing Unit&#xff0c;圖形處理單元&#xff09;是一種專門用于處理圖形和并行計算的處理器。GPU系統架構通常包括硬件和軟件層面的組件。 一、總體流程 應…

Node.js基礎---Express中間件

1. 概念 1.什么是中間件 中間件(Middleware)&#xff0c;特指業務流程的中間處理環節 2. Express 中間件的調用流程 當一個請求到達 Express 的服務器后&#xff0c;可以連續調用多個中間件&#xff0c;從而對這次請求進行預處理 3. Express 中間件格式 Express 的中間件&…

每周一算法:雙端隊列廣搜

題目鏈接 電路維修 題目描述 達達是來自異世界的魔女&#xff0c;她在漫無目的地四處漂流的時候&#xff0c;遇到了善良的少女翰翰&#xff0c;從而被收留在地球上。翰翰的家里有一輛飛行車。有一天飛行車的電路板突然出現了故障&#xff0c;導致無法啟動。 電路板的整體結…

Java實戰:SpringBoot集成ZXing實現二維碼生成與解析

一、引言 在信息化社會&#xff0c;二維碼已經深入到生活的各個角落&#xff0c;無論是支付、營銷、信息傳遞&#xff0c;甚至防偽溯源&#xff0c;二維碼都發揮了至關重要的作用。作為Java開發者&#xff0c;我們如何在SpringBoot項目中便捷地實現二維碼的生成與解析呢&#…

4、Redis-Set【常用】

目錄 一、Redis-Set特點 二、常用命令與交并差 三、Redis中Set類型應用場景 一、Redis-Set特點 1、無序&#xff1a;添加的是A,B,C&#xff1b;取出的可能是B,A,C 2、唯一&#xff1a;不允許元素重復 二、常用命令與交并差 常用命令 格式含義例子sadd key members[...]往k…

吳恩達機器學習筆記十四 多輸出的分類 多類和多標簽的區別 梯度下降優化 卷積層

這里老師想講的是multiclass classification和multilable classification的區別&#xff0c;下面是我從其他地方找到的說法: Multiclass classification 多類分類 意味著一個分類任務需要對多于兩個類的數據進行分類。比如&#xff0c;對一系列的橘子&#xff0c;蘋果或者梨的…

Stable Diffusion生成式擴散模型代碼實現原理

Stable Diffusion可以使用PyTorch或TensorFlow等深度學習框架來實現。這些框架提供了一系列的工具和函數&#xff0c;使得開發者可以更方便地構建、訓練和部署深度學習模型。因此可以使用PyTorch或TensorFlow來實現Stable Diffusion模型。 安裝PyTorch&#xff1a;確保您已經安…

Linux命令行與shell腳本編程大全-2.2

第二部分 shell腳本編程基礎 第11章構建基礎腳本 第12章結構化命令 第13章更多的結構化命令 第14章處理用戶輸入 第15章呈現數據 第16章腳本控制 第15章 呈現數據 15.1 理解輸入和輸出 15.1.1 標準文件描述符 Linux 系統會將每個對象當作文件來處理&#xff0c;這包括輸入和…

T3SF:一款功能全面的桌面端技術練習模擬框架

關于T3SF T3SF是一款功能全面的桌面端技術練習模擬框架&#xff0c;該工具針對基于主場景事件列表的各種事件提供了模塊化的架構&#xff0c;并包含了針對每一個練習定義的規則集&#xff0c;以及允許為對應平臺參數定義參數的配置文件。 該工具的主模塊能夠執行與其他特定模…

CDN原理探究

來源于百度&#xff1a; https://baike.baidu.com/item/%E5%86%85%E5%AE%B9%E5%88%86%E5%8F%91%E7%BD%91%E7%BB%9C/4034265?frge_ala 通過上圖&#xff0c;我們可以了解到&#xff0c;使用了CDN緩存后的網站的訪問過程變為&#xff1a; 用戶向瀏覽器提供要訪問的域名&#xff…

幻獸帕魯/Palworld服務器的最佳網絡設置、內存和CPU配置是什么?

幻獸帕魯/Palworld服務器的最佳網絡設置、內存和CPU配置是什么&#xff1f; 對于4到8人的玩家&#xff0c;推薦的配置是4核16G的CPU和16G的內存。10到20人的玩家選擇8核32G的CPU和32G或以上的內存。2到4人的玩家則建議選擇4核8G的CPU和8G的內存。對于32人的玩家&#xff0c;推…

YOLOV8介紹

原文鏈接&#xff1a; 1、 詳解YOLOv8網絡結構/環境搭建/數據集獲取/訓練/推理/驗證/導出 2、Yolov8的詳解與實戰 3、YOLOV8模型訓練部署&#xff08;實戰&#xff09;&#xff08;&#xff09;有具體部署和訓練實現代碼YOLOV8模型訓練部署&#xff08;實戰&#xff09;&…

Mybatis plus核心功能-IService

目錄 1 前言 2 使用方法 2.1 繼承ServiceImpl,> 2.2 基礎業務開發的使用 2.3 復雜業務開發的使用 2.3 Lambda查詢 2.4 Lambda更新 1 前言 我本以為Mapper層的類能夠繼承BaseMapper<XXX>&#xff0c;而不用我們手動寫一些mapper方法已經夠離譜了。沒想到海油膏…