<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # Java NIO之套接字通道 ## 1.簡介 前面一篇文章講了文件通道,本文繼續來說說另一種類型的通道 – 套接字通道。在展開說明之前,咱們先來聊聊套接字的由來。套接字即 socket,最早由伯克利大學的研究人員開發,所以經常被稱為`Berkeley sockets`。UNIX 4.2BSD 內核版本中加入了 socket 的實現,此后,很多操作系統都提供了自己的 socket 接口實現。通過 socket 接口,我們就可以與不同地址的計算機實現通信。 如果大家使用過 Unix/Linux 系統下的 socket 接口,那么對 socket 編程的過程應該有一些了解。對于 TCP 服務端,接口調用的順序為`socket() -> bind() -> listen() -> accept() -> 其他操作 -> close()`,客戶端的順序為`socket() -> connect() -> 其他操作 -> close()`。如下圖所示: ![](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15218834057254.jpg) \* 圖片來源于《深入理解計算機系統》 如上所示,直接調用操作系統 socket 相關接口還是比較麻煩的。所以我們的 Java 語言對上面的步驟進行了封裝,方便使用。比如我們今天要講的套接字通道就比原生的接口好用的多。好了,關于 socket 的簡介先說到這,接下進入正題吧。 ## [](http://www.tianxiaobo.com/2018/03/25/Java-NIO%E4%B9%8B%E5%A5%97%E6%8E%A5%E5%AD%97%E9%80%9A%E9%81%93/#2-通道類型)2 通道類型 Java 套接字通道包含三種類型,分別是 | 類型 | 說明 | | --- | --- | | DatagramChannel | UDP 網絡套接字通道 | | SocketChannel | TCP 網絡套接字通道 | | ServerSocketChannel | TCP 服務端套接字通道 | Java 套接字通道類型對應于兩種通信協議 TCP 和 UDP,這個大家應該都知道。本文將介紹 TCP 網絡套接字通道的使用,并在最后實現一個簡單的聊天功能。至于 UDP 類型的通道,大家可以自己看看。 ## [](http://www.tianxiaobo.com/2018/03/25/Java-NIO%E4%B9%8B%E5%A5%97%E6%8E%A5%E5%AD%97%E9%80%9A%E9%81%93/#3基本操作)3.基本操作 ### [](http://www.tianxiaobo.com/2018/03/25/Java-NIO%E4%B9%8B%E5%A5%97%E6%8E%A5%E5%AD%97%E9%80%9A%E9%81%93/#31-打開通道)3.1 打開通道 SocketChannel 和 ServerSocketChannel 都是抽象類,所以不能直接通過構造方法創建通道。這兩個類均是使用 open 方法創建通道,如下: ``` SocketChannel socketChannel = SocketChannel.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); ``` ### [](http://www.tianxiaobo.com/2018/03/25/Java-NIO%E4%B9%8B%E5%A5%97%E6%8E%A5%E5%AD%97%E9%80%9A%E9%81%93/#32-關閉通道)3.2 關閉通道 SocketChannel 和 ServerSocketChannel 均提供了 close 方法,用于關閉通道。示例如下: ``` SocketChannel socketChannel = SocketChannel.open(); socketChannel.connect(new InetSocketAddress("www.coolblog.xyz", 80)); // do something... socketChannel.close(); /*******************************************************************/ ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(8080)); SocketChannel socketChannel = serverSocketChannel.accept(); // do something... socketChannel.close(); serverSocketChannel.close(); ``` ### [](http://www.tianxiaobo.com/2018/03/25/Java-NIO%E4%B9%8B%E5%A5%97%E6%8E%A5%E5%AD%97%E9%80%9A%E9%81%93/#33-讀寫操作)3.3 讀寫操作 #### [](http://www.tianxiaobo.com/2018/03/25/Java-NIO%E4%B9%8B%E5%A5%97%E6%8E%A5%E5%AD%97%E9%80%9A%E9%81%93/#讀操作)讀操作 通過使用 SocketChannel 的 read 方法,并配合 ByteBuffer 字節緩沖區,即可以從 SocketChannel 中讀取數據。示例如下: ``` ByteBuffer buffer = ByteBuffer.allocate(32); int num = socketChannel.read(buffer); ``` #### [](http://www.tianxiaobo.com/2018/03/25/Java-NIO%E4%B9%8B%E5%A5%97%E6%8E%A5%E5%AD%97%E9%80%9A%E9%81%93/#寫操作)寫操作 讀取數據使用的是 read 方法,那么寫入自然也就是 write 方法了。NIO 通道是面向緩沖的,所以向管道中寫入數據也需要和緩沖區配合才行。示例如下 ``` String data = "Test data..." ByteBuffer buffer = ByteBuffer.allocate(32); buffer.clear(); buffer.put(data.getBytes()); bbuffer.flip(); channel.write(buffer); ``` ### [](http://www.tianxiaobo.com/2018/03/25/Java-NIO%E4%B9%8B%E5%A5%97%E6%8E%A5%E5%AD%97%E9%80%9A%E9%81%93/#34-非阻塞模式)3.4 非阻塞模式 與文件通道不同,套接字通道可以運行在非阻塞模式下。在此模式下,調用 connect(),read() 和 write() 等方法時,進程/線程會立即返回。設置非阻塞模式的方法為`configureBlocking`,我們來看一下該方法的使用示例: ``` SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("www.coolblog.xyz", 80)); // 這里要循環檢測是否已經連接上 while(!socketChannel.finishConnect()){ // do something } // 連接建立起來后,才能進行讀取或寫入操作 ``` 由于在非阻塞模式下,調用 connect 方法會立即返回。如果在連接未建立起來的情況下,從管道中讀取,或向管道寫入數據,會觸發 NotYetConnectedException 異常。所以要進行循環檢測,以保證連接完成建立。如果代碼按照上面那樣去寫,會引發另外一個問題。非阻塞模式雖然不會阻塞線程,但是在方法返回后,還要進行循環檢測,線程實際上還是被阻塞。出現這個問題的原因是和 Java NIO 套接字通道的 IO 模型有關,套接字通道采用的是“同步非阻塞”式 IO 模型,用戶發起一個 IO 操作后,即可去做其他事情,不用等待 IO 完成。但是 IO 是否已完成,則需要用戶自己時不時的去檢測,這樣實際上還是會浪費 CPU 資源。 關于 IO 模型相關的知識,大家可以參考我之前的一篇文章[I/O模型簡述](http://www.coolblog.xyz/2018/02/08/IO%E6%A8%A1%E5%9E%8B%E7%AE%80%E8%BF%B0/),這里不再贅述。另外,大家還需要去參考一下權威資料[《UNIX網絡編程卷 第1卷:套接口API》](https://book.douban.com/subject/1500149/)第6章關于 IO 模型的介紹,那一章除了對5種 IO 模型進行了介紹,還介紹了同步與異步的概念,值得一讀。好了,本節就先說到這里。 ### [](http://www.tianxiaobo.com/2018/03/25/Java-NIO%E4%B9%8B%E5%A5%97%E6%8E%A5%E5%AD%97%E9%80%9A%E9%81%93/#35-實例演示)3.5 實例演示 本節用一個簡單的例子來演示套接字通道的使用,這個例子演示了一個客戶端與服務端互相聊天的場景。首先服務端會監聽某個端口,等待客戶端來連接。客戶端連接后,由客戶端先向服務端發送消息,然后服務端再回復一條消息。這樣,客戶端和服務端就能你一句我一句的聊起來了。背景先介紹到這,我們來看看代碼實現吧,首先看看服務端的代碼: ``` package wetalk; import static wetalk.WeTalkUtils.recvMsg; import static wetalk.WeTalkUtils.sendMsg; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Scanner; /** * WeTalk 服務端 * @author coolblog.xyz * @date 2018-03-22 12:43:26 */ public class WeTalkServer { private static final String EXIT_MARK = "exit"; private int port; WeTalkServer(int port) { this.port = port; } public void start() throws IOException { // 創建服務端套接字通道,監聽端口,并等待客戶端連接 ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress(port)); System.out.println("服務端已啟動,正在監聽 " + port + " 端口......"); SocketChannel channel = ssc.accept(); System.out.println("接受來自" + channel.getRemoteAddress().toString().replace("/", "") + " 請求"); Scanner sc = new Scanner(System.in); while (true) { // 等待并接收客戶端發送的消息 String msg = recvMsg(channel); System.out.println("\n客戶端:"); System.out.println(msg + "\n"); // 輸入信息 System.out.println("請輸入:"); msg = sc.nextLine(); if (EXIT_MARK.equals(msg)) { sendMsg(channel, "bye~"); break; } // 回復客戶端消息 sendMsg(channel, msg); } // 關閉通道 channel.close(); ssc.close(); } public static void main(String[] args) throws IOException { new WeTalkServer(8080).start(); } } ``` 上面的代碼基本上進行了逐步注釋,應該不難理解,這里就不啰嗦了。上面有兩個方法沒有貼代碼,就是`sendMsg`和`recvMsg`,由于通用操作,在下面的客戶端代碼里也可以使用,所以這里做了封裝。封裝代碼如下: ``` package wetalk; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; /** * 工具類 * * @author coolblog.xyz * @date 2018-03-22 13:13:41 */ public class WeTalkUtils { private static final int BUFFER_SIZE = 128; public static void sendMsg(SocketChannel channel, String msg) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE); buffer.put(msg.getBytes()); buffer.flip(); channel.write(buffer); } public static String recvMsg(SocketChannel channel) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE); channel.read(buffer); buffer.flip(); byte[] bytes = new byte[buffer.limit()]; buffer.get(bytes); return new String(bytes); } } ``` 工具類的代碼比較簡單,沒什么好說的。接下來再來看看客戶端的代碼。 ``` package wetalk; import static wetalk.WeTalkUtils.recvMsg; import static wetalk.WeTalkUtils.sendMsg; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SocketChannel; import java.util.Scanner; /** * WeTalk 客戶端 * @author coolblog.xyz * @date 2018-03-22 12:38:21 */ public class WeTalkClient { private static final String EXIT_MARK = "exit"; private String hostname; private int port; WeTalkClient(String hostname, int port) { this.hostname = hostname; this.port = port; } public void start() throws IOException { // 打開一個套接字通道,并向服務端發起連接 SocketChannel channel = SocketChannel.open(); channel.connect(new InetSocketAddress(hostname, port)); Scanner sc = new Scanner(System.in); while (true) { // 輸入信息 System.out.println("請輸入:"); String msg = sc.nextLine(); if (EXIT_MARK.equals(msg)) { sendMsg(channel, "bye~"); break; } // 向服務端發送消息 sendMsg(channel, msg); // 接受服務端返回的消息 msg = recvMsg(channel); System.out.println("\n服務端:"); System.out.println(msg + "\n"); } // 關閉通道 channel.close(); } public static void main(String[] args) throws IOException { new WeTalkClient("localhost", 8080).start(); } } ``` 客戶端做的事情也比較簡單,首先是打開通道,然后連接服務單。緊接著進入 while 循環,然后就可以和服務端愉快的聊天了。 上面的代碼和敘述都沒啥意思,最后我們還是來看看上面代碼的運行效果,一圖勝前言。 ![](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/socket2.gif) ## [](http://www.tianxiaobo.com/2018/03/25/Java-NIO%E4%B9%8B%E5%A5%97%E6%8E%A5%E5%AD%97%E9%80%9A%E9%81%93/#4總結)4.總結 到這里,關于套接字通道的相關內容就講完了,不知道大家有沒有看懂。本文僅從使用的角度分析了套接字通道的用法,至于套接字通道的實現,這并不是本文關注的重點。實際上,我在上一篇文章中就說過,Java 所提供的很多類實際上是對操作系統層面上一些系統調用做了一層包裝。所以大家在學習 Java 的同時,還應該去了解底層的一些東西,這樣才算是知其然,又知其所以然。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看