使用最原始的java.net.ServerSocket和java.net.Socket進行socket通訊。實現的效果爲:客戶端向服務端發送消息、服務端向客戶端發送消息、保留統計客戶端的信息列表、剔除已經斷開的客戶端等。java
本文全部代碼都可在https://gitee.com/songxinqiang/JavaSocketDemo查看。git
接受用戶輸入端口而且啓動服務器的入口類,還須要完成接受用戶輸入發送給客戶端apache
public class Main { public static void main(String[] args) { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("請輸入服務器端口(默認8080):"); Integer port =8080; try { port = Integer.parseInt(reader.readLine()); } catch (Exception e1) { e1.printStackTrace(); } SocketServer server = new SocketServer(port); while (true) { try { String line = reader.readLine(); if (line.equals("exit")) { System.exit(0); break; } server.sendMessage(line); } catch (IOException e) { e.printStackTrace(); } } } }
服務器類須要完成啓動ServerSocket接受客戶端鏈接、保留客戶端信息、清除已經斷開的客戶端信息、對外暴露向客戶端發送消息的接口服務器
使用到了定時器,代碼須要java8環境,日誌記錄爲slf4j接口多線程
public class SocketServer extends TimerTask { private static final Logger logger = LoggerFactory.getLogger(SocketServer.class); ServerSocket serverSocket; private List<SocketClientHandler> clients = new ArrayList<>(); /** * 開啓監聽,接受連接 */ public SocketServer(Integer port) { // 清理客戶端 new Timer(true).schedule(this, 1000, 1000); try { serverSocket = new ServerSocket(port); logger.info("服務端已啓動,等待客戶端鏈接.."); new Thread(() -> { while (!serverSocket.isClosed()) { try { Socket socket = serverSocket.accept(); String clientIP = socket.getInetAddress() .getHostAddress(); logger.info("client:{}", clientIP); SocketClientHandler handler = new SocketClientHandler(socket); handler.start(); clients.add(handler); } catch (IOException e) { e.printStackTrace(); } } }).start(); } catch (IOException e) { e.printStackTrace(); } } /** * {@inheritDoc}<br> * 用於清理已經斷開的客戶端 * */ @Override public void run() { Iterator<SocketClientHandler> it = clients.iterator(); while (it.hasNext()) { SocketClientHandler handler = it.next(); if (!handler.isAlive()) { it.remove(); } } } /** * 往客戶端發送消息 * * @param msg * 消息 */ public void sendMessage(String msg) { logger.info("send:{}", msg); clients.stream() .forEach(client -> client.sendMessage(msg)); } }
客戶端信息處理爲單獨的線程,保存客戶端的socket對象,接受客戶端消息並進行處理(這裏只是打印),同時實際完成向客戶端發送消息併發
public class SocketClientHandler extends Thread { private static final Logger logger = LoggerFactory.getLogger(SocketClientHandler.class); Socket client; String ip; BufferedReader reader; PrintWriter writer; public SocketClientHandler(Socket socket) { this.ip = socket.getInetAddress() .getHostAddress(); try { this.client = socket; reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); writer = new PrintWriter(socket.getOutputStream(), true); } catch (IOException e) { logger.error("init socket client error,{}", e.getMessage()); } } /** * {@inheritDoc}<br> * */ @Override public void run() { while (!client.isClosed()) { try { String msg = reader.readLine(); if (msg == null) { break; } receiveMessage(msg); } catch (IOException e) { logger.info(e.getMessage()); break; } } } /** * 收到消息以後的處理 * * @param msg * 收到的消息 */ public void receiveMessage(String msg) { logger.info("from:{},receive:{}", ip, msg); } /** * 發送消息 * * @param msg * 發送的消息 */ public void sendMessage(String msg) { logger.info("to:{},send:{}", ip, msg); writer.println(msg); } }
代碼經過Main類啓動以後要求用戶輸入服務器端口,而後開始接受客戶端鏈接,打印客戶端ip地址、發送過來的信息、接受用戶輸入並向全部客戶端進行發送。socket
客戶端的啓動類完成向用戶詢問服務器地址和端口的操做,而後向服務端發起鏈接,接受用戶輸入,併發送到服務端maven
public class SocketClient { private static final Logger logger = LoggerFactory.getLogger(SocketClient.class); /** * 啓動客戶端,鏈接服務器 */ public SocketClient() { try { // 從控制檯輸入 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("請輸入服務器地址:"); String host = reader.readLine(); System.out.print("請輸入服務器端口:"); Integer port = Integer.parseInt(reader.readLine()); Socket socket = new Socket(host, port); logger.info("鏈接:{}:{}", host, port); // 開啓多線程接收信息,並解析 ClientHandler thread = new ClientHandler(socket); thread.start(); PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); while (!socket.isClosed()) { String line = reader.readLine(); if (line == null || line.isEmpty()) { continue; } logger.info("讀取:{}", line); // 發送消息 writer.println(line); writer.flush(); } } catch (Exception e) { logger.error("服務器異常,{}", e.getMessage()); } } public static void main(String[] args) { new SocketClient(); } }
客戶端信息處理ide
因爲客戶端的啓動類須要完成用戶輸入的收集,因此接受服務端消息的操做由單獨的線程進行,用於對服務器端發送過來的消息進行處理ui
public class ClientHandler extends Thread { private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class); private Socket socket; BufferedReader reader; public ClientHandler(Socket socket) { this.socket = socket; try { reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); } catch (IOException e) { e.printStackTrace(); } } /** * {@inheritDoc}<br> * 接收消息並打印,在收到的消息爲{@code null}或者空字符串時退出 * */ @Override public void run() { while (!socket.isClosed()) { try { String msg = reader.readLine(); if (msg == null) { break; } receiveMessage(msg); } catch (IOException e) { logger.info(e.getMessage()); break; } } } /** * 對服務器發送消息進行處理 * * @param msg * 消息內容 */ public void receiveMessage(String msg) { logger.info("收到:{}", msg); } }
爲了將程序打包爲一個可執行jar,同時包含日誌記錄依賴,使用maven打包,關鍵代碼爲(以client爲例,服務器端相似,詳見git)
<properties> <assembly-plugin.version>3.1.0</assembly-plugin.version> </properties> <dependencies> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> </dependencies> <build> <plugins> <!-- 構建一個單文件可執行jar --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>${assembly-plugin.version}</version> <configuration> <descriptors> <descriptor>src/assembly/assembly.xml</descriptor> </descriptors> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>cn.songxinqiang.demo.socket.SocketClient</mainClass> </manifest> </archive> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
程序使用maven打包以後運行效果爲