最近Java潘老师在协同底层项目开发web端需要使用到socket与其通信,实现消息传输,于是重拾那遗忘久矣的Java Socket网络编程技术。还记得当时学习Java Socket编程例子的时候是写了一个在线聊天室,有单聊有群聊,玩的的不亦乐乎,真正能体会到学Java技术原来这么有意思,不过今天潘老师就总结下最基础入门的Java Socket编程案例,并写把它融入到Java Web项目中,注意不是websocket哦~
1、Java项目Socket编程案例
我们目前的需求很简单,就是新建一个Java项目,然后有客户端,有服务端,客户端可以给服务端发消息,服务端收到消息然后返回一个响应给客户端。本案例基于TCP协议,如果想使用UDP的可以自行百度去哦。
第1步:创建服务端
由于服务端中有个accept
方法是阻塞方法,用来监听端口等待客户端发送消息,所以我们服务端要单独开一个线程去监听端口,否则运行时会阻塞主线程,导致程序堵死,下面就是潘老师写的一个简单的服务端代码:
package com.panziye.socket; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class Server extends Thread { // 服务端对象 private ServerSocket serverSocket; // 默认监听8877端口 private int port = 8877; // 构造方法,初始化服务端 public Server() { try { // 创建Socket服务器对象,监听8877端口 serverSocket = new ServerSocket(port); System.out.println("ServerSocket创建了...."); } catch (Exception e) { System.out.println("ServerSocket创建出错...."); } } // 重写run方法 public void run() { System.out.println("服务端启动了,等待客户端发送消息...."); // 循环监听,直到线程中断为止 while(!this.isInterrupted()){ try { // accept是阻塞方法,等待客户端发消息 Socket socket = serverSocket.accept(); if(socket != null && !socket.isClosed()){ // 处理socket消息 handleSocket(socket); } } catch (IOException e) { e.printStackTrace(); } } } // 处理服务端接收到的socket消息 private void handleSocket(Socket socket) { InputStream is = null; InputStreamReader isr = null; BufferedReader br = null; OutputStream os = null; PrintWriter pw = null; StringBuffer result = new StringBuffer(); try { is = socket.getInputStream(); isr = new InputStreamReader(is); br = new BufferedReader(isr); String info = null; // 从流中读取客户端消息 while ((info = br.readLine()) != null) { result.append(info); } // 输出消息 System.out.println("我是服务器,客户端说:" + result); socket.shutdownInput(); // 给客户端响应消息 os = socket.getOutputStream(); pw = new PrintWriter(os); pw.write("客户端你好,欢迎访问潘老师博客:https://www.panziye.com"); pw.flush(); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭资源 try { if (pw != null) pw.close(); if (os != null) os.close(); if (br != null) br.close(); if (isr != null) isr.close(); if (is != null) is.close(); if (socket != null) socket.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * 关闭socket */ public void closeSocketServer(){ try { if(serverSocket != null && !serverSocket.isClosed()){ serverSocket.close(); } } catch (IOException e) { e.printStackTrace(); } } }
第2步:创建客户端
客户端比较简单,主要就是和服务端建立连接,然后可以像服务端发送消息,并获取服务端返回的消息。例子代码如下:
package com.panziye.socket; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; public class Client { // 主机地址 private String host = "localhost"; // 端口 private int port = 8877; /** * 客户端向服务端发送消息 */ public String sendMessage(String msg) { // 响应结果 StringBuffer result = new StringBuffer(); BufferedReader br = null; InputStream is = null; OutputStream os = null; PrintWriter pw = null; Socket socket = null; try { // 和服务器创建连接 socket = new Socket(host,port); System.out.println("和服务器已建立连接...."); // 要发送给服务器的信息 os = socket.getOutputStream(); pw = new PrintWriter(os); // 给服务端发msg pw.write(msg); pw.flush(); socket.shutdownOutput(); // 从服务器接收的信息 is = socket.getInputStream(); br = new BufferedReader(new InputStreamReader(is)); String info = null; while((info = br.readLine())!=null){ result.append(info); } } catch (Exception e) { e.printStackTrace(); }finally { // 关闭流和socket try { if(br != null) br.close(); if(is != null) is.close(); if(os != null) os.close(); if(pw != null) pw.close(); if(socket != null) socket.close(); } catch (IOException e) { e.printStackTrace(); } } return result.toString(); } }
第3步:创建测试类
最后我们创建一个测试类,来测试效果,具体代码如下:
package com.panziye.socket; public class SocketTest { public static void main(String[] args) { // 创建服务端线程并启动 new Server().start(); // 休眠3s等待服务端线程启动成功 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 创建客户端并发消息 String response = new Client().sendMessage("你好潘老师..."); System.out.println("服务端响应:"+response); } }
最后我们运行,发现可以成功建立连接,发送消息,获得响应。查看控制台打印结果具体如图:
优化:使用线程池去异步处理
为了提高效率,我们可以将handleSocket
方法也封装在一个Runnable线程中,然后可以每次accept到socket消息时就,交给线程池去处理即可,这样就可以避免阻塞情况,具体修改的代码如下。
1)新增HandleSocketRunnable线程
package light.mvc.thread; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; public class HandleSocketRunnable implements Runnable { private Socket socket = null; private InputStream is=null; private InputStreamReader isr=null; private BufferedReader br=null; private OutputStream os=null; private PrintWriter pw=null; public HandleSocketRunnable(Socket socket) { this.socket = socket; } @Override public void run() { System.out.println(Thread.currentThread().getName()); // 获取消息 String message = getMessage(); // 处理message System.out.println(message); // 发送响应 boolean flag= responseMessage("响应消息"); // 关闭socket closeSocket(); } // 获取客户端消息 private String getMessage() { StringBuffer result = new StringBuffer(); try { is = socket.getInputStream(); isr = new InputStreamReader(is); br = new BufferedReader(isr); String info = null; while((info=br.readLine())!=null){ result.append(info); } socket.shutdownInput(); } catch (Exception e) { e.printStackTrace(); } return result.toString(); } // 给客户端返回响应 private boolean responseMessage(String message) { OutputStream os=null; PrintWriter pw=null; try { os = socket.getOutputStream(); pw = new PrintWriter(os); pw.write(message); pw.flush(); } catch (Exception e) { e.printStackTrace(); return false; } return true; } // 关闭socket private void closeSocket() { //关闭资源 try { if(br!=null) br.close(); if(isr!=null) isr.close(); if(is!=null) is.close(); if(pw!=null) pw.close(); if(os!=null) os.close(); if(socket != null) socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
2)修改Server线程类
package com.panziye.socket; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Server extends Thread { // 服务端对象 private ServerSocket serverSocket; // 创建定长线程池 private ExecutorService executor = Executors.newFixedThreadPool(5); // 默认监听8877端口 private int port = 8877; // 构造方法,初始化服务端 public Server() { try { // 创建Socket服务器对象,监听8877端口 serverSocket = new ServerSocket(port); System.out.println("ServerSocket创建了...."); } catch (Exception e) { System.out.println("ServerSocket创建出错...."); } } // 重写run方法 public void run() { System.out.println("服务端启动了,等待客户端发送消息...."); // 循环监听,直到线程中断为止 while(!this.isInterrupted()){ try { // accept是阻塞方法,等待客户端发消息 Socket socket = serverSocket.accept(); if(socket != null && !socket.isClosed()){ // 线程池多线程处理接收的数据 executor.submit(new HandleSocketRunnable(socket)); } } catch (IOException e) { e.printStackTrace(); } } } /** * 关闭socket */ public void closeSocketServer(){ try { if(serverSocket != null && !serverSocket.isClosed()){ serverSocket.close(); } } catch (IOException e) { e.printStackTrace(); } } }
通过以上的代码修改我们就可以实现使用线程池去处理socket请求了,效率也会随之提高很多。
2、Socket编程案例应用到Java web项目
其实上面的代码如果你都理解了,那我们可以很轻松地将该案例应用到java web项目中,而难点就在于如何使服务端ServerThread
随着web项目启动而启动。这里以Spring项目为例。
第1步:创建监听器
这里我们可以在Spring项目中新建一个监听器,实现ServletContextListener
主要重写容器销毁和容器初始化方法:
package com.panziye.listener; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import com.panziye.socket.ServerThread; public class SocketServerListener implements ServletContextListener { private ServerThread serverThread; /** * 销毁方法 */ @Override public void contextDestroyed(ServletContextEvent arg0) { if (serverThread != null && serverThread.isInterrupted()) { serverThread.interrupt(); } } /** * 初始化方法 */ @Override public void contextInitialized(ServletContextEvent arg0) { if(serverThread == null) { // 创建线程 ServerThread serverThread= new ServerThread (null); // 设为守护线程 serverThread.setDaemon(true); // 启动socket线程 serverThread.start(); } } }
第2步:配置web.xml
我们需要将创建好的监听器配置在web.xml
中,具体如下:
<listener> <listener-class>com.panziye.listener.SocketServerListener</listener-class> </listener>
最后启动项目就会发现,Socket服务端线程随着项目启动而启动了。
好了,以上就是Java Socket编程例子并应用到了Java Web项目中,其实里面的代码还有许多地方值得优化,你都学会了吗?