[Java网络编程] TCP协议实现聊天室

功能

水了一发控制台上的聊天室,能够发送公共消息和私密消息,效果如下

 思路

用户端初始时,首先输入将要在聊天室中显示的用户名,然后创建与服务器的 TCP 连接,新建两个线程用以发送与接收消息。在发送线程初始化时,将刚刚输入的用户名发送到服务器,之后就开始无限循环工作了。

用户可以发送两种类型的消息,公共消息和私密消息。公共消息没有格式,私密消息的格式为 @<接收人>:<内容>。

服务器端用 ServerSocket 创建套接字,阻塞式等待新的连接。当有用户试图连接服务器时,服务器新建一个 Channel 线程,来收发该用户的消息,并将该 Channel 加入用户表中,以转发消息。

服务器收到用户发送的公共消息时,会将其转发给所有其他在线用户,通过遍历用户表来实现;至于私密消息,则遍历用户表,一一比对用户表中的用户名与私密消息的目标用户名,将消息转发给匹配的用户。服务器还能发送系统消息,以提醒用户上下线等。

消息的实现是通过一个 Message 类,非常粗糙,只有通过一个 Type 来区分公共消息、私密消息、系统消息,而不是继承(水啊水)。重写了 toString 方法,以更方便的展示消息。

代码

服务器端

package NET_CHATROOM;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;

public class Server {
    // 适合多线程的数据结构
    private static CopyOnWriteArrayList<Channel> clientList = new CopyOnWriteArrayList<>();

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        System.out.println("-----SERVER-----");
        // 指定端口,使用ServerSocket创建服务器套接字
        ServerSocket server = new ServerSocket(8888);
        while (true) {
            // 阻塞式等待TCP连接
            Socket client = server.accept();
            System.out.println("建立TCP连接");
            // 创建新线程处理业务
            Channel channel = new Channel(client);
            // 将当前连接加入用户表中
            clientList.add(channel);
            // 开启线程
            new Thread(channel).start();
        }
    }

    static class Channel implements Runnable {
        private Socket socket;
        private ObjectInputStream ois;
        private ObjectOutputStream oos;
        private String username;
        private boolean isOnline;

        Channel(Socket socket) throws IOException, ClassNotFoundException {
            this.socket = socket;
            this.ois = new ObjectInputStream(this.socket.getInputStream());
            this.oos = new ObjectOutputStream(this.socket.getOutputStream());
            // 得到用户名
            this.username = receive().getUsername();
            this.isOnline = true;
            // 发送欢迎消息和提醒上线消息
            send(new Message(username).genSysMsg(Message.SysMsgType.WELCOME));
            sendToOthers(new Message(username).genSysMsg(Message.SysMsgType.ONLINE));
        }

        private Message receive() throws IOException, ClassNotFoundException {
            return (Message) ois.readObject();
        }

        private void send(Message msg) throws IOException {
            oos.writeObject(msg);
            oos.flush();
        }

        private void send(Message msg, String toUsername) throws IOException {
            for (Channel channel: clientList) {
                if (channel.username.equals(toUsername)) {
                    channel.send(msg);
                    break;
                }
            }
        }

        private void sendToOthers(Message msg) throws IOException {
            for (Channel client: clientList) {
                if (client != this) {
                    client.send(msg);
                }
            }
        }

        private void release() throws IOException {
            Utils.close(socket, ois, oos);
        }

        @Override
        public void run() {
            while (isOnline) {
                try {
                    // 接收消息,然后按消息类型发送
                    Message msg = receive();
                    if (msg.getType() == Message.Type.PRI) {
                        send(msg, msg.getToUsername());
                    } else {
                        sendToOthers(msg);
                    }
                } catch (IOException | ClassNotFoundException e) {
                    e.printStackTrace();
                    // 释放资源,发送下线提醒
                    try {
                        isOnline = false;
                        sendToOthers(new Message(username).genSysMsg(Message.SysMsgType.OFFLINE));
                        release();
                        clientList.remove(this);
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    }
}


用户端

package NET_CHATROOM;

import javax.rmi.CORBA.Util;
import java.io.*;
import java.net.Socket;
import java.util.Date;

public class Client {
    public static void main(String[] args) throws IOException {
        System.out.println("-----CLIENT-----");
        // 初始化用户名
        System.out.print("请输入你的用户名:");
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String username = reader.readLine();

        Socket server = new Socket("localhost", 8888);
        // 开启接收与发送两个线程
        new Thread(new Send(server, username)).start();
        new Thread(new Receive(server)).start();
    }
}

class Send implements Runnable {
    private boolean isOnline;
    private String username;
    private Socket socket;
    private ObjectOutputStream oos;
    private BufferedReader reader;

    Send(Socket socket, String username) throws IOException {
        this.isOnline = true;
        this.username = username;
        this.socket = socket;
        this.oos = new ObjectOutputStream(socket.getOutputStream());
        this.reader = new BufferedReader(new InputStreamReader(System.in));
        send("初始化用户名");
    }

    private void send(String content) throws IOException {
        // 根据发送的内容判断消息类型
        if (content.charAt(0) == '@') {
            // 私密消息格式 @<接收人>:<内容>
            String toUsername = content.split(":")[0].substring(1);
            oos.writeObject(new Message(toUsername, username, content, new Date(), Message.Type.PRI));
        } else {
            oos.writeObject(new Message(username, content, new Date(), Message.Type.PUB));
        }
        oos.flush();
    }

    private void release() throws IOException {
        Utils.close(socket, oos, reader);
    }

    @Override
    public void run() {
        while (isOnline) {
            try {
                send(reader.readLine());
            } catch (IOException e) {
                e.printStackTrace();
                isOnline = false;
                try {
                    release();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

class Receive implements Runnable {
    private boolean isOnline;
    private Socket socket;
    private ObjectInputStream ois;

    Receive(Socket socket) throws IOException {
        this.isOnline = true;
        this.socket = socket;
        this.ois = new ObjectInputStream(socket.getInputStream());
    }

    private Message receive() throws IOException, ClassNotFoundException {
            return (Message) ois.readObject();
    }

    private void release() throws IOException {
        Utils.close(socket, ois);
    }

    @Override
    public void run() {
        while (isOnline) {
            try {
                Message msg = receive();
                if (msg != null) {
                    System.out.println(msg.toString());
                }
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
                isOnline = false;
                try {
                    release();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

消息类

package NET_CHATROOM;

import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Message implements Serializable {
    enum Type {PUB, PRI, SYS}
    enum SysMsgType {WELCOME, ONLINE, OFFLINE}
    private String toUsername;
    private String username;
    private String content;
    private Date date;
    private Type type;

    Message(String username) {
        this.username = username;
    }

    Message(String username, String content, Type type) {
        this.username = username;
        this.content = content;
        this.type = type;
    }

    Message(String username, String content, Date date) {
        this.username = username;
        this.content = content;
        this.date = date;
    }

    Message(String username, String content, Date date, Type type) {
        this.username = username;
        this.content = content;
        this.date = date;
        this.type = type;
    }

    Message(String toUsername, String username, String content, Date date, Type type) {
        this.toUsername = toUsername;
        this.username = username;
        this.content = content;
        this.date = date;
        this.type = type;
    }

    // 生成系统消息
    Message genSysMsg(SysMsgType smt) {
        switch (smt) {
            case WELCOME:
                return new Message("【系统消息】", username + "欢迎您", Type.SYS);
            case ONLINE:
                return new Message("【系统消息】", username + "上线了", Type.SYS);
            case OFFLINE:
                return new Message("【系统消息】", username + "下线了", Type.SYS);
            default:
                return null;
        }
    }

    @Override
    public String toString() {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        switch (type) {
            case PUB:
                return String.format("%s %s\n\t%s", username, df.format(date), content);
            case PRI:
                return String.format("【私密消息】%s %s\n\t%s", username, df.format(date), content);
            case SYS:
                return String.format("%s %s", username, content);
            default:
                return null;
        }

    }

    public String getToUsername() {
        return toUsername;
    }

    public void setToUsername(String toUsername) {
        this.toUsername = toUsername;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }

    public static void main(String[] args) {
        Message msg = new Message("sender", "hello", new Date(), Type.PUB);
        System.out.println(msg.toString());
    }
}

工具类

package NET_CHATROOM;

import java.io.Closeable;
import java.io.IOException;

public class Utils {
    public static void close(Closeable... closeables) throws IOException {
        for (Closeable closeable: closeables) {
            if (closeable != null) {
                closeable.close();
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/HNUCSEE_LJK/article/details/104286455