俗世遊子:專一技術研究的程序猿html
沒有實戰案例的理論基礎都是在耍流氓,因此今天主要是想經過這裏的案例可以讓你們加深對以前的理解java
本節咱們會一步步實現一個點對點聊天小程序編程
InetAddress
是Java對IP地址的封裝,這個類是一個基礎類,下面的ServerSocket
和DatagramSocket
都離不開這個類小程序
InetAddress
沒法經過new
的方式來初始化,只能提供過其提供的靜態方法來調用:api
// 獲取本地地址 InetAddress localHost = InetAddress.getLocalHost();
這裏是InetAddress
的一些方法:瀏覽器
// 主機名:DESKTOP-ATG4KKE System.out.println("主機名:" + localHost.getHostName()); // IP地址:192.168.87.1 System.out.println("IP地址:" + localHost.getHostAddress()); // 是否正常:true System.out.println("是否正常:" + localHost.isReachable(5000));
這裏是我測試時的輸出,服務器
關於isReachable()
的方法,用來檢測該地址是否能夠訪問,由此咱們能夠作一些健康檢查操做,好比:網絡
// 經過主機IP或者域名來獲得InetAddress對象 InetAddress inetAddress = InetAddress.getByName("192.168.87.139"); System.out.println("是否正常:" + inetAddress.isReachable(5000));
在5s以內盡最大可能嘗試鏈接到主機,若是沒有就認爲主機不可用,這裏受限於防火牆和服務器配置併發
固然,作健康檢查這種方法仍是low了點,生產環境中確定不會這麼幹oracle
PS: 生產環境的網絡操做不會使用到這節裏的東西,大部分狀況下采用的都是Netty
ServerSocket
是服務端套接字,是基於TCP/IP
協議下的實現
一般咱們這樣來構建:
ServerSocket serverSocket = new ServerSocket(9999); ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(new InetSocketAddress(9999));
這樣就完成了服務端的初始化,而且將端口9999
綁定起來
若是客戶端想要和ServerSocket
創建鏈接,咱們須要這麼作
for(;;) { Socket socket = serverSocket.accpet(); // Socket[addr=/0:0:0:0:0:0:0:1,port=62445,localport=9999] System.out.println(socket); }
accpet()
是偵聽與ServerSocket
創建的鏈接,這個方法是一個阻塞方法,會一直等待鏈接接入進來
若是有鏈接接入進來,咱們能夠經過返回值來獲得當前接入進來的Socket
在網絡中傳遞數據其實也是按照IO
流的方式進行傳遞的,可是咱們只能獲取到字節流:
InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream();
InputStream
讀取數據,OutputStream
寫出數據,這些基本操做咱們在以前的IO流中都介紹過,這裏就再也不多說
這裏咱們爲了可以提升效率,能夠採用包裝流
或者處理流
來處理,這前面也介紹過了
其實到這裏,ServerSocket
的關鍵介紹也就完了,下面咱們來作一個小例子:
Hello World
public class _ServerSocket { // 用來存儲請求客戶端和Socket之間的對應關係 static Map<String, Socket> MAP = new HashMap<>(); public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(new InetSocketAddress(9999)); for (; ; ) { String token = UUID.randomUUID().toString().replace("-", "").toLowerCase(); Socket socket = serverSocket.accept(); // 對應 MAP.put(token, socket); outHtml(socket); } } catch (IOException e) { e.printStackTrace(); } } public static void outHtml(Socket socket) { OutputStream outputStream = null; try { outputStream = socket.getOutputStream(); outputStream.write(("HTTP/1.1 200 OK\n\nHello World").getBytes("UTF-8")); } catch (IOException e) { e.printStackTrace(); } finally { if (null != outputStream) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
HTTP/1.1 200 OK\n\nHello World\n
這是HTTP協議下返回類型,前面是Response固定格式,
Hello World
是真正返回的內容,這樣咱們的ServerSocket
就可以經過瀏覽器來訪問了
Socket
屬於客戶端套接字,只有先和服務端套接字創建鏈接才能作其餘的操做,Socket
的使用方式很是簡單
Socket socket = new Socket("127.0.0.1", 9999); // 驗證是否鏈接成功 if (socket.isConnected()) { System.out.println("到服務端鏈接成功"); }
這是其中一種構造方法,更多狀況下是採用這種方式
和服務端的鏈接創建成功以後,後續的操做就和ServerSocket
的通訊步驟
同樣了,這裏就再也不多廢話了
下面用一個完整的例子來鞏固一下
public class Server { /** * 將客戶端標識和socket關聯起來 */ private static final Map<String, Socket> SOCKET_MAP = new HashMap<>(); /** * 反向關聯,用來獲取標識 */ private static final Map<Socket, String> SOCKET_TOKEN_MAP = new HashMap<>(); public static void main(String[] args) throws IOException { /** * 開啓ServerSocket並監聽9999端口 */ ServerSocket serverSocket = new ServerSocket(9999); for (;;) { /** * 等待客戶端鏈接 */ Socket socket = serverSocket.accept(); /** * IO讀取是阻塞式方法,因此須要開啓新線程,這裏能夠優化成線程池 */ new Thread(() -> { try { saveToMap(socket); getClientMsg(socket); } catch (IOException e) { e.printStackTrace(); } }).start(); } } /** * 綁定SOCKET */ private static void saveToMap(Socket socket) throws IOException { String token = StringUtil.uuid(); SOCKET_MAP.put(token, socket); SOCKET_TOKEN_MAP.put(socket, token); System.out.println("---客戶端鏈接成功,編號:" + token); System.out.println("當前用戶:" + SOCKET_MAP.size()); /** * 由於沒有登陸,因此這裏要告知客戶端本身的標識 */ send(token, token, token); } /** * 獲取客戶端發送過來的消息,併發送出指定指定的客戶端 */ private static void getClientMsg(Socket socket) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = ""; while ((line = reader.readLine()) != null) { // 讀取到一行之後,從這裏發送出去 send(socket, line); } } /** * 發送消息 */ private static void send(Socket socket, String line) throws IOException { String[] s = line.split("#"); final String from = SOCKET_TOKEN_MAP.get(socket); send(s[0], s[1], from); } /** * 發送消息 * @param token * @param msg * @param from 這裏在目標客戶端展現 * @throws IOException */ private static void send(String token, String msg, String from) throws IOException { Socket sk = SOCKET_MAP.get(token); if (null == sk) return; String s = from + ":" + msg; System.out.println("---發送給客戶端:" + s ); // 字符流輸出 BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(sk.getOutputStream())); writer.write(s); writer.newLine(); writer.flush(); } }
public class Client { public static void main(String[] args) throws IOException { /** * 鏈接到服務端 */ Socket socket = new Socket("127.0.0.1", 9999); /** * 開新線程讀取消息,能夠優化 */ new Thread(() -> { try { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = ""; while (StringUtil.isNotBlank(line = reader.readLine())) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } }).start(); /** * 從控制檯寫入消息併發送出去 */ Scanner scanner = new Scanner(System.in); while (scanner.hasNext()) { String next = scanner.next(); send(next, socket); } } private static void send(String msg, Socket socket) throws IOException { BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); writer.write(msg); writer.newLine(); writer.flush(); } }
代碼已經經過測試,註釋寫的也很是清楚,你們能夠嘗試下,按照標識#消息
的格式就能夠點對點聊天
了。
若是想要羣聊
:
很久沒有用
Socket
寫聊天程序了,差點就放棄了下次改用
Netty
來寫,Netty
比Socket
方便多了
DatagramSocket
是用於發送和接收數據報包的套接字,是基於UDP協議
下的實現。根據類中官方介紹:
數據報套接字是數據包傳遞服務的發送或接收點。 在數據報套接字上發送或接收的每一個數據包都通過單獨尋址和路由。 從一臺機器發送到另外一臺機器的多個數據包可能會以不一樣的方式路由,而且可能以任何順序到達
咱們也能明白UDP協議
的特性。
該類表示數據報包
,在DatagramSocket
中傳遞和接收數據都是靠這個類來完成的,好比:
byte[] buffer = new byte[1024]; DatagramPacket p = new DatagramPacket(buffer, buffer.length);
DatagramPacket p = new DatagramPacket("123".getBytes(), "123".getBytes().length, InetAddress.getByName("localhost"), 9999);
發送數據出去,DatagramPacket
須要指定接收端的IP和端口,這樣纔可以發送出去
下面咱們來看看具體如何用
DatagramSocket socket = new DatagramSocket(9999); DatagramSocket s = new DatagramSocket(null); s.bind(new InetSocketAddress(9999));
兩種方式均可以完成初始化,沒有什麼區別
byte[] buffer = new byte[1024]; DatagramPacket p = new DatagramPacket(buffer, buffer.length); socket.receive(p); System.out.println(new String(p.getData(), 0, p.getLength()));
根據DatagramPacket
的接收參數,構造出來一個byte[]
,而後調用receive()
,這樣消息就接收到了
receive()
是一個阻塞方法,只有等有消息的時候纔會繼續執行
DatagramPacket p = new DatagramPacket("123".getBytes(), "123".getBytes().length, InetAddress.getByName("localhost"), 9999); socket.send(p);
構造發送數據包,而後調用send()
方法就能夠完成數據包的發送
UDP不須要鏈接,直接經過IP+PORT的方式就能夠發送數據
public class _DatagramPacket { public static void main(String[] args) throws IOException { // 從命令行獲得須要綁定的端口和發送數據的端口 DatagramSocket datagramSocket = new DatagramSocket(Integer.parseInt(args[0])); System.out.println("已啓動"); new Thread(() -> { byte[] buffer = new byte[1024]; DatagramPacket p = new DatagramPacket(buffer, buffer.length); try { for (;;) { // 構建接收數據 datagramSocket.receive(p); System.out.println(p.getPort() + ":" + new String(buffer, 0, p.getLength())); } } catch (IOException e) { e.printStackTrace(); } }).start(); Scanner scanner = new Scanner(System.in); DatagramPacket p = new DatagramPacket(new byte[0], 0, new InetSocketAddress("127.0.0.1", Integer.parseInt(args[1]))); while (scanner.hasNext()) { String next = scanner.next(); // 構建發送數據包 p.setData(next.getBytes()); datagramSocket.send(p); } }
有瑕疵,空格會換行,這裏交給你們去修改了
到這裏,關於Socket編程
方面的東西就聊完了,沒有介紹不少的API方法,這些在用到的時候再看也是同樣的。
如下是java.net
所在的目錄文檔: