Java 网络通信编程(1):服务器多任务连接+广播消息实现

在之前的文章中,我们学习了网络编程的基础知识。今天,我们将进入实践阶段,动手搭建自定义的服务器和客户端,重点实现服务器的多任务连接与消息广播功能。

1.自定义客户端(MClient)

首先,我们来搭建自定义客户端,与运行在本地(127.0.0.1),端口为2000的服务器进行连接,并获取它的输入输出流。

java 复制代码
Socket socket = new Socket("127.0.0.1", 2000);
System.out.println("连接服务器");

is = socket.getInputStream();
os = socket.getOutputStream();

之后考虑发送与读取消息,将它们分别包装成方法体。

  • 发送消息时需注意在字符串末尾拼接换行让每条信息正确排版。通过调用输出流的 write() 方法,存储由字符串转为字节数组的消息,再用 flush() 方法确保强制写入。
  • 我们可以利用一定长度的缓存区来读取消息,read() 方法会读取消息并存储到括号里的 bytes 数组中,用 String msg = new String(bytes);定义msg,可以将 byte 数组直接转为字符串,最后通过 trim() 方法返回消去多余空位的字符串。
java 复制代码
//发送消息方法体
    public void sendMsg(String msg)throws Exception{
        String str = msg+"\r\n";//拼接换行
        os.write(str.getBytes());
        os.flush();
    }

    //读取消息方法体
    public String readMsg()throws Exception{
        byte[] bytes = new byte[1024];//保存消息的缓存区
        is.read(bytes);
        String msg = new String(bytes);//将 byte 数组转为字符串
        return msg.trim();//去除空位
    }

由于客户端在发送消息的同时需要实时接收服务端的消息,我们应该开启一个独立线程。接收消息则需要从控制台读取对应的每一行输入。

java 复制代码
 //启动线程来实时读取服务器发来的消息
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    String str;
                    try {
                        str = readMsg();
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("server:"+str);
                }
            }
        }).start();
java 复制代码
 //发送消息
        while (true) {
            Scanner scanner = new Scanner(System.in);
            String line = scanner.nextLine();
            sendMsg(line);
        }

整合后我们便可得到完整的自定义服务器代码。

java 复制代码
mport java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class MClient {
    public static void main(String[] args) {
        try {
            new MClient().startClient();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public InputStream is;
    public OutputStream os;

    public void startClient() throws Exception {
        Socket socket = new Socket("127.0.0.1", 2000);
        System.out.println("连接服务器");

        is = socket.getInputStream();
        os = socket.getOutputStream();

        //启动线程来实时读取服务器发来的消息
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    String str;
                    try {
                        str = readMsg();
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("server:"+str);
                }
            }
        }).start();


        //发送消息
        while (true) {
            Scanner scanner = new Scanner(System.in);
            String line = scanner.nextLine();
            sendMsg(line);
        }
    }
    //发送消息方法体
    public void sendMsg(String msg)throws Exception{
        String str = msg+"\r\n";//拼接换行
        os.write(str.getBytes());
        os.flush();
    }

    //读取消息方法体
    public String readMsg()throws Exception{
        byte[] bytes = new byte[1024];//保存消息的缓存区
        is.read(bytes);
        String msg = new String(bytes);//将 byte 数组转为字符串
        return msg.trim();//去除空位
    }
}

2.自定义服务端

2.1服务端框架(MServer)

此处服务器担任"消息中转站"的角色,它要与多个客户端建立实时连接。对于实时性,结合之前搭建客户端的经验,我们容易想到利用线程。

服务端对于每个连接过来的客户端,都会创建对应的 Socket 对象,为了实现消息广播功能,我们可以建立一个 ArrayList 集合 用来存储 Socket 对象,就像一个 "在线客户端名单"。接着循环用 accept() 方法阻塞监听每一个连接过来的客户端 ,将其加入ArrayList 集合。accept() 方法是阻塞的,服务器在连接到新客户端之前不会向下执行代码,为了不影响每个客户端,我们需要在下面启动该客户端独立、全新的线程来保持通信

java 复制代码
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class MServer {
    public static void main(String[] args) {
        try {
            new MServer().startServer();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void startServer() throws Exception {
        ServerSocket server = new ServerSocket(2000);
        System.out.println("启动服务器");

        ArrayList<Socket> list = new ArrayList<>();

        //重复监听连接过来的客户端
        while(true) {
            //阻塞监听连接过来的客户端
            Socket socket = server.accept();
            list.add(socket);

            //启动线程,保持通信
            ServerThread serverThread = new ServerThread(socket,list);
            new Thread(serverThread).start();
        }
    }
}
2.2通信线程(ServerThread)

搭建好服务器框架后我们来完善线程的具体内容。

收发消息的方法体我们可以基本套用客户端的,但客户端的 sendMsg() 只需要操作唯一的一个输出流 (客户端只连一个服务器,只有一个和服务器通信的输出流);而服务端的 sendMsg() 必须针对不同客户端的输出流(每个客户端对应一个独立的 Socket,每个 Socket 有专属的输出流),通过指定不同的 os 参数来区分要发送的目标客户端。

java 复制代码
//发送消息方法体
    public void sendMsg(OutputStream os, String msg) throws Exception {
        String str = msg + "\r\n";//拼接换行
        os.write(str.getBytes());
        os.flush();
    }

通信线程对每个客户端都是独立的,因此构造函数中需要传入对应的 Socket 对象。同时为了实现向其他客户端发送消息的功能,需要其他客户端的 Socket 对象,因而传入 ArrayList 集合,所有线程共享同一个 ArrayList 集合。

java 复制代码
public ServerThread(Socket socket, ArrayList<Socket> list) {
        this.socket = socket;
        this.list = list;
        //获取客户端输入输出流
        try {
            is = socket.getInputStream();
            os = socket.getOutputStream();
            sendMsg(os, "客户端连接成功");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

最后重写 run() 方法。对于广播功能,我们应该遍历 ArrayList ,对除去客户端本身的其他客户端,将消息写入输出流。

java 复制代码
public void run() {
        while (true) {
            try {
                //接收信息
                String msg = readMsg();
                System.out.println("client:" + msg);

                //广播功能
                for (Socket s : list) {
                    if (s != socket) {//排除自己客户端
                        OutputStream os = s.getOutputStream();
                        sendMsg(os, msg);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

        }
    }

3.结果演示

如此我们便实现了服务器多任务连接+广播消息。最后演示一下运行结果。

启动服务器和多个客户端,均连接成功。服务器会向客户端发送"客户端连接成功"的提示。

在任意客户端发送消息,服务器会接收到并将消息中转至其他客户端。

(两个不同客户端分别发送"abc123"和"你好")

相关推荐
wasp5202 小时前
Hudi Flink 集成分析
大数据·服务器·flink
独自破碎E3 小时前
【双指针+字符串】字符串变形
android·java
weixin_462446234 小时前
一键安装 Hadoop 3.3.6 自动化脚本详解 |(含 JAVA_HOME 自动配置)
java·hadoop·自动化
张柏慈10 小时前
Java性能优化:实战技巧与案例解析
java
天“码”行空10 小时前
简化Lambda——方法引用
java·开发语言
带刺的坐椅11 小时前
MCP 进化:让静态 Tool 进化为具备“上下文感知”的远程 Skills
java·ai·llm·agent·solon·mcp·tool-call·skills
java1234_小锋11 小时前
Java线程之间是如何通信的?
java·开发语言
张张努力变强11 小时前
C++ Date日期类的设计与实现全解析
java·开发语言·c++·算法