Java Socket编程例子并应用到Java Web项目

Java技术 潘老师 3年前 (2021-09-11) 1549 ℃ (0) 扫码查看

最近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();
        }
    }
}
该服务端主要监听本机的8877端口,记得处理完客户端消息后一定要关闭IO流和socket

第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);
    }

}

最后我们运行,发现可以成功建立连接,发送消息,获得响应。查看控制台打印结果具体如图:
Java Socket编程例子并应用到Java Web项目

优化:使用线程池去异步处理

为了提高效率,我们可以将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项目中,其实里面的代码还有许多地方值得优化,你都学会了吗?


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/java/3623.html
喜欢 (3)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】