對於java網絡編程來講,咱們能接觸到的最底層即是socket了。我相信大部分閱讀此篇文章的同仁都用過socket。可是,我這篇教程的理念之一就是事無鉅細,或者說,囉裏囉嗦。由於原本就是一個思惟的過程,因此,看官老爺們,就當作意識流風格來看吧(也是由於我文筆較差,所以文章的觀賞性可能不是太好,給本身找個理由)。java
我在剛學編程的時候,實際上是一臉茫然的,雖然也會爲屏幕輸出「hello world",感到興奮,可是興奮之餘會以爲,嗯~?,這東西跟我想象的不同啊,就一個黑屏上輸出幾個字符,能幹啥?這玩意跟網站啥的不沾邊啊。包括學習了一些算法、數據結構後,仍是以爲,網站啥的,跟我學的不沾邊。算法
後來在學習參加了一個項目,涉及到網絡編程,項目簡單點說就是樹莓派跟pc經過路由器進行數據交換。從那時候起開始使用socket,固然那個時候不求甚解,根本跟學過的網絡模型也對不上號。什麼三次握手,四次揮手,沒用到啊。先無論,幹就完了。shell
秉着這個徐循漸進的學習及思考過程,我就開始了socket編程。編程
來一段服務端socket示例:服務器
代碼 1-1網絡
public class OioServer { private ServerSocket serverSocket; private void openServer(int port) throws IOException { // 1 建立ServerSocket serverSocket = new ServerSocket(); // 2 綁定端口 SocketAddress socketAddress = new InetSocketAddress(port); serverSocket.bind(socketAddress); } @Test public void testOpenServer() throws IOException { OioServer oioServer = new OioServer(); oioServer.openServer(8081); // block Scanner scan = new Scanner(System.in); scan.next(); } }
運行test後,服務器開始監聽port
端口了。而後咱們驗證下是否是真的在監聽,我再window上測試的,因此這裏以window爲例。首先要打開cmd窗口,而後輸入命令數據結構
代碼 1-2app
netstat -aon|findstr "8081"
圖1-1socket
能夠看到真的在監聽了, 如今雖然開始監聽了,可是,真的有客戶端從8081進來該怎麼辦?咱們還須要處理鏈接,這個鏈接就是咱們的主角socket
.學習
修改1-1代碼以下:
代碼1-2
public class OioServer { private ServerSocket serverSocket; private void openServer(int port) throws IOException { // 1 建立ServerSocket serverSocket = new ServerSocket(); // 2 綁定端口 SocketAddress socketAddress = new InetSocketAddress(port); serverSocket.bind(socketAddress); } @Test public void testOpenServer() throws IOException { OioServer oioServer = new OioServer(); oioServer.openServer(8081); Socket socket = oioServer.listenAccept(); } }
此時,服務端就運行有客戶端進來鏈接了。而後咱們再寫一個客戶端。
代碼 1-3
public class OioClient{ public Socket connect(String host, int port) throws IOException { Socket socket = new Socket(); socket.connect(new InetSocketAddress(host, port)); return socket } @Test public void testClient() throws IOException, InterruptedException { OioClient oioServer = new OioClient(); oioServer.connect("127.0.0.1", 8081); // block Scanner scan = new Scanner(System.in); scan.next(); } }
運行test,爲例不讓test退出,咱們用標準出入block住test線程。記住,如今咱們服務端還在監聽,我服務端和客戶端都跑在同一臺pc上。效果如圖
圖1-2
能夠看到第二行本地地址是127.0.0.1:6283
遠程地址是127.0.0.1:8081
,那咱們是否是能夠猜到,這個TCP鏈接是客戶端的,客戶端的IP是6283
,咱們雖然沒指定,可是系統會給咱們隨機分配一個可用的端口,不信能夠試一下,下次可能就不同了。
瓜熟蒂落,第三行就是咱們服務端的TCP鏈接。
那麼咱們是否是能夠有一個這樣的結論:socket
其實就是一個TCP鏈接的封裝(固然也能夠是UDP,這裏不作討論)?或者socket的下層是TCP/IP層?
到如今咱們大概知道了socket跟TCP的聯繫,既跟網絡模型的聯繫。
有了TCP連接,咱們就能夠開始傳輸數據了。不少網上例子是,簡歷socket鏈接後,客戶端給服務器發送數據,而後服務器接收數據並回復。可是請不要誤解,過程不必定所有是這樣的。咱們客戶端和服務端創建連接後,兩邊都獲得一個socket
,這兩個socket
是等價的。若是你對TCP有了解的話,必定會知道TCP的四次揮手,網上關於四次揮手的圖大概也都是下圖這個樣子:
圖1-3
可是呢,四次揮手一樣能夠由服務端發起。所以,就算是在TCP層,兩個socket的是等價的。
咱們獲得活躍的socket後,就能夠對socket進行讀寫操做了。既然socket沒什麼區別,那咱們能夠無差別對待socket。咱們來寫socket讀寫取的方法:
代碼1-4
public class SocketUtils { /** * 從socket中讀數據 */ public static String read(Socket socket) { try { InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; int len; StringBuilder sb = new StringBuilder(); while ((len = inputStream.read(bytes)) != -1) { //注意指定編碼格式,發送方和接收方必定要統一,建議使用UTF-8 sb.append(new String(bytes, 0, len, "UTF-8")); } return sb.toString(); } catch (IOException e) { e.printStackTrace(); return null; } } /** * 往socket中寫數據 */ public static void write(Socket socket, String response) { try { OutputStream outputStream = socket.getOutputStream(); outputStream.write(response.getBytes("UTF-8")); outputStream.flush(); socket.shutdownOutput(); } catch (IOException e) { e.printStackTrace(); } } }
上面代碼以入參的形勢將socket傳入,而後就行讀寫,注意,咱們這兩個並無調用方法socket.close
,也就是說socket能夠重複讀寫數據,而網上大部分教程直接會吧socket給關閉,形成初學者覺得每次讀寫玩就得關閉socket的錯覺。
在方法read
中,根據(len = inputStream.read(bytes)) != -1
來判斷是否讀取完數據。而寫入數據端怎麼告訴接收端它完成了寫入呢?咱們能夠用socket.shutdownOutput()
方法。
好,接下來咱們就來測試下發送與接收數據。
代碼1-4
public class OioServer { ... @Test public void testOpenServer() throws IOException { OioServer oioServer = new OioServer(); oioServer.openServer(8081); Socket socket = oioServer.listenAccept(); SocketUtils.write(socket, "Can you hear me?"); String msg = SocketUtils.read(socket); System.out.println(msg); // block Scanner scan = new Scanner(System.in); scan.next(); } }
代碼1-5
public class OioClient { ... @Test public void testClient() throws IOException, InterruptedException { OioClient oioServer = new OioClient(); Socket socket = oioServer.connect("127.0.0.1", 8081); String msg = SocketUtils.read(socket); System.out.println(msg); SocketUtils.write(socket, "Yes, I can hear you!"); // block Scanner scan = new Scanner(System.in); scan.next(); } }
先執行testOpenServer()
再執行testClient()
。就會完成對話。固然咱們能夠重複調SocketUtils.read
和 SocketUtils.write
進行通信,只要兩邊socket不關閉。
此篇文章總結了Socket的一些使用,包括服務器啓動,socket創建,並證實了socket的等價性