Java的Socket實(shí)現(xiàn)長(zhǎng)連接以及數(shù)據(jù)的發(fā)送和接收方式
Socket實(shí)現(xiàn)長(zhǎng)連接以及數(shù)據(jù)的發(fā)送和接收
既然是長(zhǎng)連接就免不了心跳檢測(cè),這里使用了一種比較簡(jiǎn)單的做法:服務(wù)端對(duì)當(dāng)前線程計(jì)時(shí),重要的話說(shuō)三遍,服務(wù)端、服務(wù)端、服務(wù)端!
如果超時(shí)沒(méi)有收到任何數(shù)據(jù)就關(guān)閉該線程對(duì)應(yīng)的Socket。
代碼復(fù)制粘貼即可運(yùn)行。
- 發(fā)送時(shí):將String轉(zhuǎn)byte[]
- 接收時(shí):將byte[]轉(zhuǎn)String
效果圖
客戶端代碼
import java.io.*; import java.net.Socket; import java.util.Scanner; public class SocketClient { public static void main(String[] args) { try { Socket socket = new Socket("localhost", 8888); //得到一個(gè)輸出流,用于向服務(wù)器發(fā)送數(shù)據(jù) OutputStream outputStream = socket.getOutputStream(); System.out.println("請(qǐng)輸入16進(jìn)制數(shù)據(jù):"); Scanner sc = new Scanner(System.in); while (true) { String data = sc.nextLine(); if ("exit".equals(data)) { return; } byte[] byteArray = HexStrToByteArray(data); outputStream.write(byteArray); //刷新緩沖 outputStream.flush(); //得到一個(gè)輸入流,用于接收服務(wù)器響應(yīng)的數(shù)據(jù) InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1]; // 一次讀取一個(gè)byte String info = ""; while (true) { if (inputStream.available() > 0) { inputStream.read(bytes); String hexStr = ByteArrayToHexStr(bytes); info += HexStrToStr(hexStr); //已經(jīng)讀完 if (inputStream.available() == 0) { System.out.println("收到來(lái)自服務(wù)端的信息:" + info); break; } } } } } catch (IOException e) { e.printStackTrace(); } } /** * 16進(jìn)制Str轉(zhuǎn)byte[] * * @param hexStr * @return */ public static byte[] HexStrToByteArray(String hexStr) { if (hexStr == null) { return null; } if (hexStr.length() == 0) { return new byte[0]; } byte[] byteArray = new byte[hexStr.length() / 2]; for (int i = 0; i < byteArray.length; i++) { String subStr = hexStr.substring(2 * i, 2 * i + 2); byteArray[i] = ((byte) Integer.parseInt(subStr, 16)); } return byteArray; } /** * byte[]轉(zhuǎn)16進(jìn)制Str * * @param byteArray */ public static String ByteArrayToHexStr(byte[] byteArray) { if (byteArray == null) { return null; } char[] hexArray = "0123456789ABCDEF".toCharArray(); char[] hexChars = new char[byteArray.length * 2]; for (int i = 0; i < byteArray.length; i++) { int temp = byteArray[i] & 0xFF; hexChars[i * 2] = hexArray[temp >>> 4]; hexChars[i * 2 + 1] = hexArray[temp & 0x0F]; } return new String(hexChars); } /** * 16進(jìn)制的Str轉(zhuǎn)Str * * @param hexStr * @return */ public static String HexStrToStr(String hexStr) { //能被16整除,肯定可以被2整除 byte[] array = new byte[hexStr.length() / 2]; try { for (int i = 0; i < array.length; i++) { array[i] = (byte) (0xff & Integer.parseInt(hexStr.substring(i * 2, i * 2 + 2), 16)); } hexStr = new String(array, "UTF-8"); } catch (Exception e) { e.printStackTrace(); return ""; } return hexStr; } }
服務(wù)端代碼
使用InputStream對(duì)象的available()方法判斷客戶端的內(nèi)容是否發(fā)送完畢
dataInputStream.available()
官方解釋:
返回此輸入流下一個(gè)方法調(diào)用可以不受阻塞地從此輸入流讀?。ɑ蛱^(guò))的估計(jì)字節(jié)數(shù)。
下一個(gè)調(diào)用可能是同一個(gè)線程,也可能是另一個(gè)線程。
一次讀取或跳過(guò)此估計(jì)數(shù)個(gè)字節(jié)不會(huì)受阻塞,但讀取或跳過(guò)的字節(jié)數(shù)可能小于該數(shù)。
用我的大白話就是:返回剩余未讀長(zhǎng)度
import java.io.*; import java.net.Socket; /** * 長(zhǎng)連接 */ public class ServerThread extends Thread { //16進(jìn)制數(shù)字字符集 public static final String HEXSTRING = "0123456789ABCDEF"; //心跳超時(shí)時(shí)間 private static final int TIMEOUT = 60 * 1000; private Socket m_socket; //接收到數(shù)據(jù)的最新時(shí)間 private long m_lastReceiveTime = System.currentTimeMillis(); //該線程是否正在運(yùn)行 private boolean m_isRuning = false; public ServerThread(Socket socket) { this.m_socket = socket; } @Override public void start() { if (m_isRuning) { System.out.println(">>>線程" + this.getId() + "啟動(dòng)失敗,該線程正在執(zhí)行"); return; } else { m_isRuning = true; super.start(); } } @Override public void run() { //字節(jié)輸入流 InputStream inputStream = null; //字節(jié)輸出流 OutputStream outputStream = null; try { inputStream = m_socket.getInputStream(); outputStream = m_socket.getOutputStream(); String info = ""; //按byte讀 byte[] bytes = new byte[1]; while (m_isRuning) { //檢測(cè)心跳 if (System.currentTimeMillis() - m_lastReceiveTime > TIMEOUT) { m_isRuning = false; //跳出,執(zhí)行finally塊 break; } //返回下次調(diào)用可以不受阻塞地從此流讀取或跳過(guò)的估計(jì)字節(jié)數(shù),如果等于0則表示已經(jīng)讀完 if (inputStream.available() > 0) { //重置接收到數(shù)據(jù)的最新時(shí)間 m_lastReceiveTime = System.currentTimeMillis(); inputStream.read(bytes); String tempStr = ByteArrayToHexStr(bytes) ; info += tempStr; //已經(jīng)讀完 if (inputStream.available() == 0) { System.out.println(">>>線程" + this.getId() + "收到:" + info); String responseStr = "Hello"; //響應(yīng)內(nèi)容 String hexStr = StrToHexStr(responseStr); hexStr = hexStr.replaceAll("0[x|X]|,",""); byte[] byteArray = HexStrToByteArray(hexStr); outputStream.write(byteArray); outputStream.flush(); //重置,不然每次收到的數(shù)據(jù)都會(huì)累加起來(lái) info = ""; System.out.println(">>>線程" + this.getId() + "回應(yīng):" + responseStr); } } } } catch (Exception e) { e.printStackTrace(); } //關(guān)閉資源 finally { System.out.println(">>>線程" + this.getId() + "的連接已斷開(kāi)\n"); try { if (outputStream != null) outputStream.close(); if (inputStream != null) inputStream.close(); if (m_socket != null) m_socket.close(); m_isRuning = false; } catch (IOException e) { e.printStackTrace(); } } } /** * byte[]轉(zhuǎn)16進(jìn)制Str * * @param byteArray */ public static String ByteArrayToHexStr(byte[] byteArray) { if (byteArray == null) { return null; } char[] hexArray = HEXSTRING.toCharArray(); char[] hexChars = new char[byteArray.length * 2]; for (int i = 0; i < byteArray.length; i++) { int temp = byteArray[i] & 0xFF; hexChars[i * 2] = hexArray[temp >>> 4]; hexChars[i * 2 + 1] = hexArray[temp & 0x0F]; } return new String(hexChars); } /** * Str轉(zhuǎn)16進(jìn)制Str * * @param str * @return */ public static String StrToHexStr(String str) { //根據(jù)默認(rèn)編碼獲取字節(jié)數(shù)組 byte[] bytes = str.getBytes(); StringBuilder stringBuilder = new StringBuilder(bytes.length * 2); //將字節(jié)數(shù)組中每個(gè)字節(jié)拆解成2位16進(jìn)制整數(shù) for (int i = 0; i < bytes.length; i++) { stringBuilder.append("0x"); stringBuilder.append(HEXSTRING.charAt((bytes[i] & 0xf0) >> 4)); stringBuilder.append(HEXSTRING.charAt((bytes[i] & 0x0f) >> 0)); //去掉末尾的逗號(hào) if (i != bytes.length - 1) { stringBuilder.append(","); } } return stringBuilder.toString(); } /** * 16進(jìn)制Str轉(zhuǎn)byte[] * * @param hexStr 不帶空格、不帶0x、不帶逗號(hào)的16進(jìn)制Str,如:06EEF7F1 * @return */ public static byte[] HexStrToByteArray(String hexStr) { byte[] byteArray = new byte[hexStr.length() / 2]; for (int i = 0; i < byteArray.length; i++) { String subStr = hexStr.substring(2 * i, 2 * i + 2); byteArray[i] = ((byte) Integer.parseInt(subStr, 16)); } return byteArray; } }
開(kāi)啟服務(wù)端
import java.net.ServerSocket; import java.net.Socket; public class MySocketServer { public static void main(String[] args) { try { System.out.println(">>>服務(wù)啟動(dòng),等待終端的連接\n"); ServerSocket server = new ServerSocket(8888); int count = 0; while (true) { //開(kāi)啟監(jiān)聽(tīng) Socket socket = server.accept(); count++; System.out.println(">>>開(kāi)啟第" + count + "次長(zhǎng)連接..."); ServerThread thread = new ServerThread(socket); thread.start(); } } catch (Exception e) { e.printStackTrace(); } } }
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java中POST、GET、@RequestBody和@RequestParam區(qū)別詳析
在前后端傳json數(shù)據(jù)進(jìn)行交互的時(shí)候,同學(xué)們會(huì)經(jīng)常用到的兩個(gè)注解,@RequestBody和@RequestParam主要是用來(lái)接收前端傳給后端的json數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于Java中POST、GET、@RequestBody和@RequestParam區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-10-10Java字符串處理全解析(String、StringBuilder與StringBuffer)
這篇文章主要介紹了Java字符串處理全解析(String、StringBuilder與StringBuffer),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2025-04-04java使用poi讀取doc和docx文件的實(shí)現(xiàn)示例
這篇文章主要介紹了java使用poi讀取doc和docx文件的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03SpringBoot和Springfox(Swagger)版本不兼容的解決方案
documentationPluginsBootstrapper這個(gè) bean 無(wú)法正常啟動(dòng),原因是遇到了空指針異常(NullPointerException),這通常是由于 Spring Boot 和 Springfox 的版本不兼容導(dǎo)致的路徑匹配策略沖突,本文給大家介紹了SpringBoot和Springfox(Swagger)版本不兼容的解決方案2024-12-12關(guān)于scanner.nextInt()等next()和scanner.nextIine()連用注意事項(xiàng)
這篇文章主要介紹了關(guān)于scanner.nextInt()等next()和scanner.nextIine()連用注意事項(xiàng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2023-04-04Java?Spring?Dubbo三種SPI機(jī)制的區(qū)別
這篇文章主要介紹了Java?Spring?Dubbo三種SPI機(jī)制的區(qū)別,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-08-08