歡迎你們搜索「小猴子的技術筆記」關注個人公衆號,有問題能夠及時和我交流。編程
你知道插座嗎?你知道網絡編程中的插座嗎?也許你會有點迷惑,什麼是插座!可是我若是說出「套接字」、「socket」這樣的關鍵字你就會恍然大悟。tomcat
所謂的「插座」叫作套接字又叫作socket,用來表示一個端點,能夠與網絡中其餘的socket進行鏈接,而後進行數據的傳輸。安全
咱們都知道在網絡上中能夠經過IP地址肯定惟一的一臺主機,而後主機和主機之間進行通信。可是準確來講:網絡通信中的雙方並非主機,而是主機中的進程。這就須要肯定主機中那個進程進行的網絡通信,所以還須要一個端口號來肯定主機中的惟一進程。微信
若是從操做系統的角度來講:套接字是使用標準的Unix文件描述符來與其餘計算機進行通信的一種方式。網絡
IP+PORT的組合就構成了網絡中惟一標識符「套接字」。端口號的範圍是0-65535,可是低於256的端口號進行了保留,做爲系統的標準的應用程序端口。好比HTTP的默認的80,POP3的110,Telent的23,FTP的21等。app
套接字容許兩個進程進行通信,這兩個進程可能運行在同一個機器上,也可能運行在不一樣機器上。
套接字主要有兩類:流式套接字和數據報套接字。框架
流式套接字提供了面向鏈接、可靠的數據傳輸服務,能夠很是準確的實現按照順序接收數據。若是你經過流式套接字發送"h","e","l","l","o"五個字符,它到達另外一端的順序也將會是"h","e","l","l","o"。緣由就在於流式套接字使用的是TCP進行數據傳輸,可以保證數據的安全性。流式套接字是最常使用的,一些衆所周知的協議使用的都是它,如HTTP,TCP,SMTP,POP3等。
數據報套接字:提供無鏈接的服務,你不須要像流式套接字那樣創建一個鏈接,而只須要將地址信息一同打包而後發出去。該服務使用的是UDP進行傳輸,延遲小,效率高可是不能保證數據傳輸的準確性。socket
若是咱們建立一個ServerSocket須要經歷如下幾個步驟:bind-->listen-->accpet-->recv-->write-->close。這也是底層Linux/或者Unix對一個端口監聽經歷的步驟。this
bind:綁定一個地址和端口,確認端點的信息。編碼
ServerSocket serverSocket = new ServerSocket(8888);
這裏也許你看到了並無指明IP,那麼「ServerSocket」會自動獲取本機的ip地址做爲填充,源碼以下:
public ServerSocket(int port) throws IOException { this(port, 50, null); }
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException { setImpl(); if (port < 0 || port > 0xFFFF) throw new IllegalArgumentException("Port value out of range: " + port); if (backlog < 1) backlog = 50; try { bind(new InetSocketAddress(bindAddr, port), backlog); } catch(SecurityException e) { close(); throw e; } catch(IOException e) { close(); throw e; } }
public InetSocketAddress(InetAddress addr, int port) { holder = new InetSocketAddressHolder( null, addr == null ? InetAddress.anyLocalAddress() : addr, checkPort(port)); }
會對「InetAddress」進行判斷,若是爲空了就獲取本地的地址。
listen:當端口和ip綁定以後就會進行到監聽狀態,這個時候會監聽是否有鏈接請求上來。
accept:若是客戶端的ip和端口符合服務端,也就是插頭可以對應上插座了,就接收這個客戶端的鏈接。這裏「ServerSocket.accept()」將「listen」和「accept」進行了整合,監聽而且接受一個鏈接。
Socket socket = serverSocket.accept();
send:發送數據,須要先得到socket的的輸出流,而後經過輸出流進行數據的發送。
OutputStream out = socket.getOutputStream();
拿到的是輸出流,所以發送的是字節,須要將字符串進行字節的轉換以後,在進行編碼以後發送。若是沒有進行編碼的話,默認採用的是系統的默認編碼。
out.write("hello".getBytes(StandardCharsets.UTF_8));
recv: 接受數據,也須要先獲取到socket的輸入流。
InputStream inputStream = socket.getInputStream();
獲取輸入流以後按照字節進行讀取數據。若是數據發送完畢了,也就是調用了「socket.close()」就會讀取到「-1」,表示沒有數據能夠讀到了。
InputStream in = socket.getInputStream(); StringBuffer sb = new StringBuffer(); int len; while ((len = in.read(bytes)) != -1) { sb.append(new String(bytes, 0, len)); } System.out.println("接收到消息:" + sb.toString());
close:關閉(這個不是必須的,若是是長連接,那麼將不會關閉socket,除非服務發生異常)
serverSocket.close();
上述的客戶端是阻塞式的,一次只能接受一個socket客戶端進行鏈接。
客戶端要經歷:connect-->recv/send-->close
Socket socket = new Socket("localhost", 8888);
recv:獲取到輸入流,讀取字節數字。
byte[] bytes = new byte[1024]; InputStream in = socket.getInputStream(); StringBuffer sb = new StringBuffer(); int len; while ((len = in.read(bytes)) != -1) { sb.append(new String(bytes, 0, len)); } System.out.println("接收到消息:" + sb.toString());
write:獲取到輸出流,將數據按照字節輸出給服務端。
OutputStream out = socket.getOutputStream(); out.write("hello".getBytes(StandardCharsets.UTF_8));
close:關閉資源
in.close(); out.close(); socket.close(); System.out.println("數據接收完畢");
至此基於一個簡單的socket網絡編程就完成了。咱們須要重點了解,socket是基於IP+Port組合的,用於網絡中的通訊之間的創建。網絡編程會在咱們的平常開發中起到很重要的做用,不少框架都是基於socket演變而來的,好比tomcat、netty等。 最後歡迎你們添加個人微信公衆號號"小猴子的技術筆記",若是有問題能夠及時和我交流。