In diesem Artikel wird hauptsächlich das Prinzip der Implementierung eines nativen Socket-Kommunikationsmechanismus in JAVA vorgestellt. Der Herausgeber hält es für recht gut, daher werde ich es jetzt mit Ihnen teilen und als Referenz verwenden. Folgen wir dem Herausgeber und werfen wir einen Blick darauf.
Dieser Artikel stellt das Prinzip des nativen Socket-Kommunikationsmechanismus in JAVA vor und teilt es mit allen. Die Details sind wie folgt:
Aktuelle Umgebung
jdk == 1.8
Wissenspunkte
Socket-Verbindungsverarbeitung
IO-Eingabe- und Ausgabestream-Verarbeitung
Datenformatverarbeitung anfordern
Modelloptimierung anfordern
Szenario
Heute sprechen wir über Socket-Kommunikationsprobleme in JAVA. Hier nehmen wir als Beispiel das einfachste One-Request-One-Response-Modell und gehen davon aus, dass wir jetzt mit der Baidu-Site kommunizieren müssen. Wie können wir den nativen Socket von JAVA nutzen, um dies zu erreichen?
Stellen Sie eine Socket-Verbindung her
Zuerst müssen wir eine Socket-Verbindung (Kerncode) herstellen
import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; // 初始化 socket Socket socket = new Socket(); // 初始化远程连接地址 SocketAddress remote = new InetSocketAddress(host, port); // 建立连接 socket.connect(remote);
Verarbeitung von Socket-Eingabe- und Ausgabeströmen
Nachdem wir erfolgreich eine Socket-Verbindung hergestellt haben, können wir deren Eingabe- und Ausgabeströme abrufen. Das Wesentliche der Kommunikation ist die Verarbeitung von Eingabe- und Ausgabeströmen. Über den Eingabestream werden die Daten von der Netzwerkverbindung gelesen und über den Ausgabestream werden die lokalen Daten an das entfernte Ende gesendet.
Socket-Verbindungen ähneln tatsächlich der Verarbeitung von Dateiströmen, beide führen E/A-Vorgänge aus.
Der Code zum Abrufen von Eingabe- und Ausgabestreams lautet wie folgt:
// 输入流 InputStream in = socket.getInputStream(); // 输出流 OutputStream out = socket.getOutputStream();
In Bezug auf die Verarbeitung von E/A-Streams verwenden wir im Allgemeinen entsprechende Verpackungsklassen Wenn E/A-Streams direkt verarbeitet werden, müssen wir mit Byte [] arbeiten, was relativ umständlich ist. Wenn wir eine Wrapper-Klasse verwenden, können wir diese direkt mit Typen wie string und int verarbeiten, was die IO-Byte-Operationen vereinfacht.
wird im Folgenden unter Verwendung von BufferedReader
und PrintWriter
als Eingabe- und Ausgabeverpackungsklassen verarbeitet.
// 获取 socket 输入流 private BufferedReader getReader(Socket socket) throws IOException { InputStream in = socket.getInputStream(); return new BufferedReader(new InputStreamReader(in)); } // 获取 socket 输出流 private PrintWriter getWriter(Socket socket) throws IOException { OutputStream out = socket.getOutputStream(); return new PrintWriter(new OutputStreamWriter(out)); }
Datenanforderung und -antwort
Bei Socket-Verbindung und IO-Eingangs- und Ausgangsstrom sollte Folgendes gelten Dies muss erledigt sein. Senden Sie Anforderungsdaten an und erhalten Sie die Antwortergebnisse der Anforderung.
Mit der Unterstützung der IO-Verpackungsklasse können wir direkt im String-Format übertragen, und die Verpackungsklasse hilft uns, die Daten in den entsprechenden Byte-Stream umzuwandeln.
Da wir über HTTP auf die Baidu-Site zugreifen, müssen wir keine zusätzlichen Ausgabeformate definieren. Mithilfe des Standard-HTTP-Übertragungsformats können Sie Anforderungsantworten ausführen (einige spezifische RPC-Frameworks verfügen möglicherweise über angepasste Kommunikationsformate).
Der angeforderte Dateninhalt wird wie folgt verarbeitet:
public class HttpUtil { public static String compositeRequest(String host){ return "GET / HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "User-Agent: curl/7.43.0\r\n" + "Accept: */*\r\n\r\n"; } }
Der Code zum Senden der Anforderungsdaten lautet wie folgt:
// 发起请求 PrintWriter writer = getWriter(socket); writer.write(HttpUtil.compositeRequest(host)); writer.flush(); 接收响应数据代码如下: // 读取响应 String msg; BufferedReader reader = getReader(socket); while ((msg = reader.readLine()) != null){ System.out.println(msg); }
Bisher haben wir alle Kerncodes zum Erstellen von Verbindungen, Senden von Anfragen und Empfangen von Antworten unter nativen Sockets besprochen.
Der vollständige Code lautet wie folgt:
import java.io.*; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; import com.test.network.util.HttpUtil; public class SocketHttpClient { public void start(String host, int port) { // 初始化 socket Socket socket = new Socket(); try { // 设置 socket 连接 SocketAddress remote = new InetSocketAddress(host, port); socket.setSoTimeout(5000); socket.connect(remote); // 发起请求 PrintWriter writer = getWriter(socket); System.out.println(HttpUtil.compositeRequest(host)); writer.write(HttpUtil.compositeRequest(host)); writer.flush(); // 读取响应 String msg; BufferedReader reader = getReader(socket); while ((msg = reader.readLine()) != null){ System.out.println(msg); } } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } private BufferedReader getReader(Socket socket) throws IOException { InputStream in = socket.getInputStream(); return new BufferedReader(new InputStreamReader(in)); } private PrintWriter getWriter(Socket socket) throws IOException { OutputStream out = socket.getOutputStream(); return new PrintWriter(new OutputStreamWriter(out)); } }
Nachfolgend zeigen wir die Ergebnisse der Socket-Kommunikation durch Instanziierung eines Clients.
public class Application { public static void main(String[] args) { new SocketHttpClient().start("www.baidu.com", 80); } }
Ergebnisausgabe:
Modelloptimierung anfordern
Auf diese Weise gibt es zwar kein Problem bei der Realisierung der Funktion. Wenn wir jedoch genau hinschauen, stellen wir fest, dass es während des IO-Schreib- und Lesevorgangs zu einer IO-Blockierung kommt. Das heißt:
// 会发生 IO 阻塞 writer.write(HttpUtil.compositeRequest(host)); reader.readLine();
Wenn Sie also 10 verschiedene Standorte gleichzeitig anfordern möchten, gehen Sie wie folgt vor:
public class SingleThreadApplication { public static void main(String[] args) { // HttpConstant.HOSTS 为 站点集合 for (String host: HttpConstant.HOSTS) { new SocketHttpClient().start(host, HttpConstant.PORT); } } }
Es muss die Antwort auf die erste Anfrage abgeschlossen sein, bevor die nächste Site-Verarbeitung eingeleitet wird.
Dies ist auf der Serverseite offensichtlicher. Obwohl es sich hier um eine Client-Verbindung handelt, ähneln die spezifischen Vorgänge denen auf der Serverseite. Anfragen können nur seriell einzeln bearbeitet werden, was den Reaktionszeitstandard definitiv nicht einhält.
Multithreading
Manche Leute denken, das sei überhaupt kein Problem, JAVA sei eine Multithread-Programmiersprache. Für diese Situation ist ein Multithread-Modell am besten geeignet.
public class MultiThreadApplication { public static void main(String[] args) { for (final String host: HttpConstant.HOSTS) { Thread t = new Thread(new Runnable() { public void run() { new SocketHttpClient().start(host, HttpConstant.PORT); } }); t.start(); } } }
Diese Methode scheint auf den ersten Blick nützlich zu sein, aber bei einem hohen Maß an Parallelität wird die Anwendung viele Threads verwenden. Wie wir alle wissen, belegt jeder Thread auf dem Server tatsächlich ein Dateihandle. Die Anzahl der Handles auf dem Server ist begrenzt, und eine große Anzahl von Threads führt zu einem erheblichen Verbrauch beim Wechseln zwischen Threads. Daher muss diese Methode in Szenarien mit großer Parallelität unerträglich sein.
Multi-Thread + Thread-Pool-Verarbeitung
Da zu viele Threads nicht ausreichen, können wir einfach die Anzahl der erstellten Threads steuern. Für die Socket-Verarbeitung wird nur eine feste Anzahl von Threads gestartet, wodurch nicht nur die Multithread-Verarbeitung genutzt wird, sondern auch der Systemressourcenverbrauch gesteuert wird.
public class ThreadPoolApplication { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(8); for (final String host: HttpConstant.HOSTS) { Thread t = new Thread(new Runnable() { public void run() { new SocketHttpClient().start(host, HttpConstant.PORT); } }); executorService.submit(t); new SocketHttpClient().start(host, HttpConstant.PORT); } } }
Bezüglich der Anzahl der gestarteten Threads wird im Allgemeinen der CPU-intensive Typ auf N+1 eingestellt (N ist die Anzahl der CPU-Kerne) und der Der IO-intensive Typ wird auf 2N + 1 festgelegt.
Diese Methode scheint die optimale zu sein. Gibt es etwas Besseres? Wenn ein Thread mehrere Socket-Verbindungen gleichzeitig verarbeiten kann und nicht blockiert, wenn die Eingabe- und Ausgabedaten jedes Sockets nicht bereit sind, ist es dann besser? Diese Technologie wird „IO-Multiplexing“ genannt. Die entsprechende Implementierung wird im nio-Paket von JAVA bereitgestellt.
Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung der Prinzipien, wie Java den nativen Socket-Kommunikationsmechanismus implementiert. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!