Java中的網絡通訊是經過Socket實現的,Socket分爲ServerSocket和Socket兩大類,ServerSocket用於服務端,能夠經過accept方法監聽請求,監聽到請求後返回Socket,Socket用於具體完成數據傳輸,客戶端直接使用Socket發起請求並傳輸數據。java
ServerSocket的構造方法一共有5個。最方便的是傳入一個端口參數的方法。
accept方法是阻塞方法,也就是說調用accept方法後程序會停下來等待鏈接請求,在接收到請求以前程序將不會繼續執行,當接收到請求以後,accept方法會返回一個Socket。
服務端代碼示例:segmentfault
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; public class Server { public static void main(String[] args) { try { //建立一個ServeSocket,設置端口爲8080 ServerSocket serverSocket = new ServerSocket(8080); //運行Socket監聽,等待請求 此方法會阻塞線程,當有請求時纔會繼續執行 Socket socket = serverSocket.accept(); //接收到請求以後使用Socket進行通訊,建立BufferedReader用於讀取請求的數據 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter out = new PrintWriter(socket.getOutputStream()); String line = in.readLine(); System.out.println(line); //建立PrintlnWriter,用於發送數據 out.println("已經接受到了數據"); out.flush(); System.out.println("Server關閉" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss").format(new Date())); //關閉資源 out.close(); in.close(); socket.close(); serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } finally { } } }
客戶端代碼示例:網絡
import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; /** * 客戶端 * @author sanchan */ public class Client { public static void main(String[] args) { //須要先啓動Server不然報錯java.net.ConnectException: Connection refused try { String msg="你好,ServerSocket!"; //建立一個Socket,與本機8080端口鏈接 Socket socket=new Socket("127.0.0.1",8080); //使用Socket建立PrintWriter和BufferedReader進行數據的讀寫 PrintWriter out=new PrintWriter(socket.getOutputStream()); out.println(msg); out.flush(); BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream())); String line=in.readLine(); System.out.println(line); //關閉資源 in.close(); out.close(); socket.close(); System.out.println("client關閉"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss").format(new Date())); } catch (Exception e) { e.printStackTrace(); } finally { } } }
nio(new IO)是JDK1.4新增長的IO模式,nio在底層採用了與新的處理方式大大的提升了Java IO的效率。Socket也屬於IO的一種,nio提供了相對應的類:ServerSocketChannel和SocketChannel,分別對應原來的ServerSocket和Socket。socket
試想一下若是電商是隻要有訂單就派人直接取貨去送貨【這種模式就至關於以前的Socket方式】,而不是如今的快遞模式:將貨物統一到中轉站,分揀員按照配送範圍分配快遞員,而後快遞員統一送貨【這種模式就至關於NioSocket模式】。那麼你得多長時間收到你得快遞/(ㄒoㄒ)/~~
Buffer就是貨物,Channel就是快遞員,Selector就是中轉站的分揀員。this
SerSocketChannel可使用自身的靜態工廠方法open建立。 每一個ServerSocketChannel對應一個ServerSocket,能夠調用其socket方法來獲取【不過若是使用該ServerSocket監聽請求就又回到原來的 普通Socket 模式了,通常只用於使用bind方法綁定端口】。 ServerSocketChannel能夠經過`SelectableChannel configureBlocking(boolean block)` 方法來設置是否採用阻塞模式。設置非阻塞模式以後能夠調用register方法註冊Selector【阻塞模式不可使用Selector】
Selector可使用自身的靜態工廠方法open建立。 建立後經過上面所說的Channel的register方法註冊到ServerSocketChannel或者SocketChannel上。
經過select方法等待請求,select方法可傳入表明最長等待時間的long型參數。在設定時間內接收到相應操做的請求則返回能夠處理請求的數量,不然在超時後返回0,程序繼續執行。若是傳入0或者使用無參的重載方法,則會採用阻塞模式直到有相應操做的請求出現。
接收到請求後Selector調用selectedKeys返回SelectionKey的Set集合。
SelectionKey保存了處理當前請求的Channel和Selector,並提供了不一樣的操做類型。前面提到的Channel註冊Selector的register方法參數中第二個參數就是SelectionKey定義的。共有四種:
SelectionKey.OP_ACCEPT //請求操做 SelectionKey.OP_CONNECT //連接操做 SelectionKey.OP_READ //讀操做 SelectionKey.OP_WRITE //寫操做
只有在register方法中註冊了對應的操做Selector纔會關心相應類型操做的請求。.net
Selector和Channel是多對多關係。 Selector是按不一樣的操做類型進行分揀,將分揀結果保存在SelectionKey中,可分別經過SelectionKey的channel、selector方法來獲取對應的Channel和Selector。可使用SelectionKey的isAcceptable、isConnectable、isReadable和isWritable方法來判斷是什麼類型的操做。
capacity:容量。
Buffer最多能夠保存元素的數量,建立時設置,使用過程當中不可修改。線程
limit:可使用的上限。
剛建立Buffer時limit等於capacity。若是給limit設置【不能超過capacity】以後,limit就成了最大可訪問的值。code
例如,一個Buffer的capacity爲100,表示最多能夠保存100個數據,只寫入20個以後就要讀取,在讀取時limit就會設置爲20。orm
position:當前所操做元素所在索引位置。
position從0開始,隨着get和put方法自動更新。server
mark:用來暫時保存position的值。
position保存到mark以後就能夠修改並進行相關的操做,操做完成後能夠經過reset方法將mark的值恢復到position。
mark默認值爲-1,且其值必須小於position的值。
例如,Buffer中一共保存了20個數據,position爲10,如今想讀取15到20之間的數據,這時就能夠調用Buffer的mark方法將目前的position保存到mark中,而後調用Buffer的position(15)將position指向第15個元素,這時就能夠讀取。讀取完成以後使用Buffer的reset就能夠將position恢復到10.
若是調用Buffer的position方法時傳入的值小於mark當前的值,則會將mark設爲-1。
這四個屬性大小關係:mark<=position<=limit<=capacity
咱們將前面的普通Socket示例的服務端改寫一下:
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.util.Iterator; /** * @author sanchan * @since 1.0 */ public class NIOServer { public static void main(String[] args) { /** * 啓動監聽 * 當監聽到請求時根據SelectionKey的操做類型交給內部類Handler進行處理 */ try { //建立ServerSocketChannel ServerSocketChannel ssc = ServerSocketChannel.open(); //設置監聽8080端口 ssc.socket().bind(new InetSocketAddress(8080)); //設置爲非阻塞模式 ssc.configureBlocking(false); //爲ServerSocketChannel註冊Selector Selector selector = Selector.open(); ssc.register(selector, SelectionKey.OP_ACCEPT); //建立Handler Handler handler = new Handler(1024); while (true) { //等待請求,每次等待阻塞3s,超過3秒後線程繼續運行,若是傳入0或使用無參重載方法,將一直阻塞 if (selector.select(3000) == 0) { System.out.println("等待請求超時~~~~~"); continue; } System.out.println("處理請求~~~~~"); //獲取等待處理的SelectionKey Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); try { //根據不一樣請求操做選擇對應的處理方法 if (key.isAcceptable()) { handler.handleAccept(key); } if (key.isReadable()) { handler.handleRead(key); } } catch (IOException e) { e.printStackTrace(); //若是異常就說明鏈接結束,移除 keyIterator.remove(); continue; } } } } catch (IOException e) { e.printStackTrace(); } finally { } } /** * 請求處理類 */ private static class Handler { private int bufferSize = 1024; private String localCharset = "UTF-8"; public Handler() { } public Handler(int bufferSize) { this(bufferSize, null); } public Handler(String localCharset) { this(-1, localCharset); } public Handler(int bufferSize, String localCharset) { if (bufferSize > 0) this.bufferSize = bufferSize; if (localCharset != null) this.localCharset = localCharset; } /** * 處理請求操做 * * @param key * @throws IOException */ public void handleAccept(SelectionKey key) throws IOException { //獲取Channel SocketChannel sc = ((ServerSocketChannel) key.channel()).accept(); //設置非阻塞 sc.configureBlocking(false); //註冊讀操做的Selector sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize)); } /** * 處理讀操做 * * @param key * @throws IOException */ public void handleRead(SelectionKey key) throws IOException { //獲取Channel SocketChannel sc = ((SocketChannel) key.channel()); //獲取ByteBuffer /** * Buffer專門用於存儲數據,有四個極爲重要的屬性: * 1. capacity:容量。 * Buffer最多能夠保存元素的數量,建立時設置,使用過程當中不可修改。 * 2. limit:可使用的上限。 * 剛建立Buffer時limit等於capacity。若是給limit設置【不能超過capacity】以後,limit就成了最大可訪問的值。 * 例如,一個Buffer的capacity爲100,表示最多能夠保存100個數據,只寫入20個以後就要讀取,在讀取時limit就會設置爲20。 * 3. position:當前所操做元素所在索引位置。 * position從0開始,隨着get和put方法自動更新。 * 4. mark:用來暫時保存position的值。 * position保存到mark以後就能夠修改並進行相關的操做,操做完成後能夠經過reset方法將mark的值恢復到position。 * mark默認值爲-1,且其值必須小於position的值。 * 例如,Buffer中一共保存了20個數據,position爲10,如今想讀取15到20之間的數據,這時就能夠調用Buffer的mark方法將目前的position保存到mark中,而後調用Buffer的position(15)將position指向第15個元素,這時就能夠讀取。讀取完成以後使用Buffer的reset就能夠將position恢復到10. * 若是調用Buffer的position方法時傳入的值小於mark當前的值,則會將mark設爲-1。 */ ByteBuffer buffer = (ByteBuffer) key.attachment(); //重置ByteBuffer。設置limit=capacity、position=0、mark=-1 buffer.clear(); //沒有獲取到內容則關閉 if (sc.read(buffer) == -1) { sc.close(); } else { /** * flip()做用: * 在保存數據時保存一個數據position加1,保存完成後要讀取數據 * 就得設置limit=position,position=0 **/ buffer.flip(); //返回數據到客戶端 String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString(); System.out.println("從客戶端獲取到了數據:" + receivedString); String sendString = "服務端已經獲取到了數據:" + receivedString; buffer = ByteBuffer.wrap(sendString.getBytes(localCharset)); sc.write(buffer); //關閉SocketChannel sc.close(); } } } }
本人的直播課程在 7 月份就要開始了,但願小夥伴們支持一下,如今報名有優惠噢