Java Socket基本例子——使用最原始方法

使用最原始的java.net.ServerSocket和java.net.Socket进行socket通信。实现的效果为:客户端向服务端发送消息、服务端向客户端发送消息、保留统计客户端的信息列表、剔除已经断开的客户端等。

本文所有代码均可在https://gitee.com/songxinqiang/JavaSocketDemo查看。

服务端

接受用户输入端口并且启动服务器的入口类,还需要完成接受用户输入发送给客户端

1public class Main { 2 3 public static void main(String[] args) { 4 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); 5 System.out.print("请输入服务器端口(默认8080):"); 6 Integer port =8080; 7 try { 8 port = Integer.parseInt(reader.readLine()); 9 } catch (Exception e1) { 10 e1.printStackTrace(); 11 } 12 13 SocketServer server = new SocketServer(port); 14 while (true) { 15 try { 16 String line = reader.readLine(); 17 if (line.equals("exit")) { 18 System.exit(0); 19 break; 20 } 21 server.sendMessage(line); 22 } catch (IOException e) { 23 e.printStackTrace(); 24 } 25 } 26 } 27 28}

服务器类需要完成启动ServerSocket接受客户端连接、保留客户端信息、清除已经断开的客户端信息、对外暴露向客户端发送消息的接口

使用到了定时器,代码需要java8环境,日志记录为slf4j接口

1public class SocketServer extends TimerTask { 2 3 private static final Logger logger = LoggerFactory.getLogger(SocketServer.class); 4 5 ServerSocket serverSocket; 6 7 private List<SocketClientHandler> clients = new ArrayList<>(); 8 9 /** 10 * 开启监听,接受链接 11 */ 12 public SocketServer(Integer port) { 13 // 清理客户端 14 new Timer(true).schedule(this, 1000, 1000); 15 try { 16 serverSocket = new ServerSocket(port); 17 logger.info("服务端已启动,等待客户端连接.."); 18 new Thread(() -> { 19 while (!serverSocket.isClosed()) { 20 try { 21 Socket socket = serverSocket.accept(); 22 String clientIP = socket.getInetAddress() 23 .getHostAddress(); 24 25 logger.info("client:{}", clientIP); 26 27 SocketClientHandler handler = new SocketClientHandler(socket); 28 handler.start(); 29 30 clients.add(handler); 31 } catch (IOException e) { 32 e.printStackTrace(); 33 } 34 } 35 }).start(); 36 37 } catch (IOException e) { 38 e.printStackTrace(); 39 } 40 } 41 42 /** 43 * {@inheritDoc}<br> 44 * 用于清理已经断开的客户端 45 * 46 */ 47 @Override 48 public void run() { 49 Iterator<SocketClientHandler> it = clients.iterator(); 50 while (it.hasNext()) { 51 SocketClientHandler handler = it.next(); 52 if (!handler.isAlive()) { 53 it.remove(); 54 } 55 } 56 } 57 58 /** 59 * 往客户端发送消息 60 * 61 * @param msg 62 * 消息 63 */ 64 public void sendMessage(String msg) { 65 logger.info("send:{}", msg); 66 clients.stream() 67 .forEach(client -> client.sendMessage(msg)); 68 } 69 70}

客户端信息处理为单独的线程,保存客户端的socket对象,接受客户端消息并进行处理(这里只是打印),同时实际完成向客户端发送消息

1public class SocketClientHandler extends Thread { 2 3 private static final Logger logger = LoggerFactory.getLogger(SocketClientHandler.class); 4 5 Socket client; 6 String ip; 7 8 BufferedReader reader; 9 PrintWriter writer; 10 11 public SocketClientHandler(Socket socket) { 12 this.ip = socket.getInetAddress() 13 .getHostAddress(); 14 try { 15 this.client = socket; 16 reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 17 writer = new PrintWriter(socket.getOutputStream(), true); 18 } catch (IOException e) { 19 logger.error("init socket client error,{}", e.getMessage()); 20 } 21 } 22 23 /** 24 * {@inheritDoc}<br> 25 * 26 */ 27 @Override 28 public void run() { 29 while (!client.isClosed()) { 30 try { 31 String msg = reader.readLine(); 32 if (msg == null) { 33 break; 34 } 35 receiveMessage(msg); 36 } catch (IOException e) { 37 logger.info(e.getMessage()); 38 break; 39 } 40 } 41 } 42 43 /** 44 * 收到消息之后的处理 45 * 46 * @param msg 47 * 收到的消息 48 */ 49 public void receiveMessage(String msg) { 50 logger.info("from:{},receive:{}", ip, msg); 51 } 52 53 /** 54 * 发送消息 55 * 56 * @param msg 57 * 发送的消息 58 */ 59 public void sendMessage(String msg) { 60 logger.info("to:{},send:{}", ip, msg); 61 writer.println(msg); 62 } 63 64}

代码通过Main类启动之后要求用户输入服务器端口,然后开始接受客户端连接,打印客户端ip地址、发送过来的信息、接受用户输入并向所有客户端进行发送。

客户端

客户端的启动类完成向用户询问服务器地址和端口的操作,然后向服务端发起连接,接受用户输入,并发送到服务端

1public class SocketClient { 2 3 private static final Logger logger = LoggerFactory.getLogger(SocketClient.class); 4 5 /** 6 * 启动客户端,连接服务器 7 */ 8 public SocketClient() { 9 try { 10 // 从控制台输入 11 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); 12 13 System.out.print("请输入服务器地址:"); 14 String host = reader.readLine(); 15 System.out.print("请输入服务器端口:"); 16 Integer port = Integer.parseInt(reader.readLine()); 17 18 Socket socket = new Socket(host, port); 19 logger.info("连接:{}:{}", host, port); 20 21 // 开启多线程接收信息,并解析 22 ClientHandler thread = new ClientHandler(socket); 23 thread.start(); 24 PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); 25 while (!socket.isClosed()) { 26 String line = reader.readLine(); 27 if (line == null || line.isEmpty()) { 28 continue; 29 } 30 logger.info("读取:{}", line); 31 // 发送消息 32 writer.println(line); 33 writer.flush(); 34 } 35 36 } catch (Exception e) { 37 logger.error("服务器异常,{}", e.getMessage()); 38 } 39 } 40 41 public static void main(String[] args) { 42 new SocketClient(); 43 } 44 45}

客户端信息处理

由于客户端的启动类需要完成用户输入的收集,所以接受服务端消息的操作由单独的线程进行,用于对服务器端发送过来的消息进行处理

1public class ClientHandler extends Thread { 2 3 private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class); 4 5 private Socket socket; 6 BufferedReader reader; 7 8 public ClientHandler(Socket socket) { 9 this.socket = socket; 10 try { 11 reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 12 } catch (IOException e) { 13 e.printStackTrace(); 14 } 15 } 16 17 /** 18 * {@inheritDoc}<br> 19 * 接收消息并打印,在收到的消息为{@code null}或者空字符串时退出 20 * 21 */ 22 @Override 23 public void run() { 24 while (!socket.isClosed()) { 25 try { 26 String msg = reader.readLine(); 27 if (msg == null) { 28 break; 29 } 30 receiveMessage(msg); 31 } catch (IOException e) { 32 logger.info(e.getMessage()); 33 break; 34 } 35 } 36 } 37 38 /** 39 * 对服务器发送消息进行处理 40 * 41 * @param msg 42 * 消息内容 43 */ 44 public void receiveMessage(String msg) { 45 logger.info("收到:{}", msg); 46 } 47 48}

maven打包

为了将程序打包为一个可执行jar,同时包含日志记录依赖,使用maven打包,关键代码为(以client为例,服务器端类似,详见git)

1 <properties> 2 <assembly-plugin.version>3.1.0</assembly-plugin.version> 3 </properties> 4 5 <dependencies> 6 <dependency> 7 <groupId>ch.qos.logback</groupId> 8 <artifactId>logback-classic</artifactId> 9 <version>1.2.3</version> 10 </dependency> 11 </dependencies> 12 13 <build> 14 <plugins> 15 <!-- 构建一个单文件可执行jar --> 16 <plugin> 17 <groupId>org.apache.maven.plugins</groupId> 18 <artifactId>maven-assembly-plugin</artifactId> 19 <version>${assembly-plugin.version}</version> 20 <configuration> 21 <descriptors> 22 <descriptor>src/assembly/assembly.xml</descriptor> 23 </descriptors> 24 <descriptorRefs> 25 <descriptorRef>jar-with-dependencies</descriptorRef> 26 </descriptorRefs> 27 <archive> 28 <manifest> 29 <mainClass>cn.songxinqiang.demo.socket.SocketClient</mainClass> 30 </manifest> 31 </archive> 32 </configuration> 33 <executions> 34 <execution> 35 <id>make-assembly</id> 36 <phase>package</phase> 37 <goals> 38 <goal>single</goal> 39 </goals> 40 </execution> 41 </executions> 42 </plugin> 43 </plugins> 44 </build>

运行效果

程序使用maven打包之后运行效果为

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

KVM调整cpu和内存

一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid

Android So动态加载 优雅实现与原理分析

背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我