使用Java实现聊天室过程中出现的问题

使用Java练习聊天室,首先我们要了解的是基本概念模型图样,如图下:请添加图片描述

Client就是客户端
  1. 包装的对象分别要有对象名字,socket通信对象 ,输出流和输入流,包装消息的队列(LinkedBlockingQueue阻塞队列);
  2. 封装的方法分别有接收消息,发送普通消息,发送在线人数的信息和最终关闭socket通信对象
  3. 注意所有客户端都用线程开启
public class ClientCom {
   public static final ExecutorService service = Executors.newCachedThreadPool();

    private String name;//名字
    private Socket socket;
    private ObjectInputStream in;
    private ObjectOutputStream out;

    private Set<String> users;

    private LinkedBlockingQueue<MessageObject> queue = new LinkedBlockingQueue<>();

    public ClientCom(String name,String serverIp,int serverPort) throws IOException {
        this.name = name;
        this.socket = new Socket(serverIp,serverPort);
        out = new ObjectOutputStream(socket.getOutputStream());
        in = new ObjectInputStream(socket.getInputStream());

        //初始化里将初始化信息定义
        MessageObject msgObj =
                new MessageObject(MessageObject.TYPE_INIT,null,this.name,null,null);
        //输出初始化信息
        out.writeObject(msgObj);


        //开始接收信息
        startReceiving();
    }

    private void startReceiving() {
        service.execute(()->{
            while (true){
                try {
                    //读取信息
                    MessageObject msgObj = (MessageObject) in.readObject();
                    //如果是用户名单,则返回这个名单
                    if(msgObj.isUserList()){
                        this.users = msgObj.getUsers();
                    //如果是普通信息的话,则将消息扔到队列当中
                    }else if(msgObj.isNormal()){
                        queue.put(msgObj);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                    break;
                }
            }
            this.close();
        });

    }

    //发送普通信息
    public void sendMsg(MessageObject msgObj){
        try {
            out.writeObject(msgObj);
        } catch (IOException e) {
            e.printStackTrace();
            this.close();
        }
    }

    private void close() {
        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public Set<String> getUserSet() {

        return users;
    }

    public String getName() {

        return name;
    }

    public LinkedBlockingQueue<MessageObject> getQueue() {
        return queue;
    }
}
消息封装
  1. private static final long serialVersionUID = 999L;//注意一定要整理好版本
  2. 设置消息类型:普通消息,初始化消息,返回对象个数消息;
  3. 包装对象:消息的类型,消息的发送者,消息的接受者
public class MessageObject implements Serializable {//消息对象
    private static final long serialVersionUID = 999L;

    public static final int TYPE_INIT=1;//初始化消息
    public static final int TYPE_NORMAL=2;//普通消息

    public static final int TYPE_USERLIST=3;//用户列表消息


    private int type;
    private String receiver;
    private String sender;

    private String content;

    private Set<String> users;

    public boolean isNormal(){//是否普通消息
        return type == TYPE_NORMAL;
    }

    public boolean isUserList(){//是否客户端表
        return type == TYPE_USERLIST;
    }

    public boolean isInit(){//是否为初始化消息
        return type == TYPE_INIT;
    }

}

服务端
  1. 创建一个Map集合存储所有的ServerCom对象,出于安全角度,我们采用ConcurrentHashMap
  2. 缓存收到的新消息的队列(公共消息队列)
  3. 缓存私人消息(私人消息队列)
  4. 初始化时进行通信socket的连接并将消息放入Map集合当中,然后执行发送消息
  5. 静态块执行将公共消息队列内容获取到
  6. 接收消息的动作:读入流读到信息,将公共消息队列的消息放入私人消息队列
  7. 发出消息的动作:将私人消息队列写出
  8. 最后关闭通信连接
public class ServerCom {

    private static ExecutorService service = Executors.newCachedThreadPool();
    
    //存储所有的ServerCom对象
    private static Map<String,ServerCom> serverComMap = new ConcurrentHashMap<>();
    
    //缓存收到的新消息的队列
    private static LinkedBlockingQueue<MessageObject> publicQueue = new LinkedBlockingQueue<>();

    static {
        //处理新消息的线程
        service.execute(()->{
            while(true){
                try {
                    MessageObject messageObject = publicQueue.take();
                    ServerCom com = serverComMap.get(messageObject.getReceiver());
                    com.putMsg(messageObject);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });


        //发送用户列表消息
        service.execute(()->{
            while(true){
                Set<String> _users = serverComMap.keySet();
                Set<String> users = new HashSet<>(_users);
                MessageObject msgObj = new MessageObject(MessageObject.TYPE_USERLIST,null,null,null,users);
                serverComMap.values().forEach(com->com.putMsg(msgObj));
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });


    }

    private String name;//对应客户单的名字
    private Socket socket;
    private ObjectInputStream in;
    private ObjectOutputStream out;

    private LinkedBlockingQueue<MessageObject> queue = new LinkedBlockingQueue<>();

    public ServerCom(Socket socket) throws IOException, ClassNotFoundException {
        this.socket = socket;
        in = new ObjectInputStream(socket.getInputStream());
        out = new ObjectOutputStream(socket.getOutputStream());

        //刚刚建立连接,从客户端读取初始化消息
        MessageObject msgObj = (MessageObject) in.readObject();
        if(!msgObj.isInit()){
            socket.close();
            throw new RuntimeException("非初始化消息,拒绝连接!");
        }

        this.name = msgObj.getSender();

        serverComMap.put(this.name,this);

        log.debug("与客户端({})连接成功!",this.name);

        //启动收消息线程
        startReceiving();
        //启动发消息线程
        startSending();
        
        //启动发送用户列表消息的线程
        //startSendUsers();
    }

    private void startSendUsers() {
        service.execute(()->{
            while (true){
                try {
                    Set<String> users = serverComMap.keySet();
                    MessageObject msgObj = new MessageObject(MessageObject.TYPE_USERLIST,null,null,null,users);
                    out.writeObject(msgObj);
                    TimeUnit.SECONDS.sleep(10);
                } catch (IOException e) {
                    e.printStackTrace();
                    break;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.close();
        });
    }

    public void putMsg(MessageObject messageObject) {
        try {
            queue.put(messageObject);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void startSending() {
        service.execute(()->{
            while (true){
                try {
                    MessageObject messageObject = this.queue.take();
                    log.debug("{}",messageObject);
                    out.writeObject(messageObject);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                    break;
                }
            }
            this.close();
        });
    }

    private void startReceiving() {
        service.execute(()->{
            while (true){
                try {
                   MessageObject msgObj = (MessageObject) in.readObject();
                   publicQueue.put(msgObj);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    e.printStackTrace();
                    break;
                }
            }
            this.close();
        });
    }

    private void close() {
        serverComMap.remove(name);
        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }



}
组件部分(最终显示)

基础组件:

public abstract class ClientFrame extends JFrame {


     protected ClientCom clientCom;

     private JTextField nameTextField = new JTextField("u1",10);

     private JTextField serverIpTextField = new JTextField("localhost",10);

     private JTextField serverPortTextField = new JTextField("9999",10);

     private JButton connButton = new JButton("连接");

     private JTextArea msgTextArea = new JTextArea();

     private JComboBox<String> userComboBox = new JComboBox<>();
     private JTextField sendTextField = new JTextField(50);
     private JButton sendButton = new JButton("发送");


    public ClientFrame(String title){
        super(title);

        this.setSize(800,600);
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

        Dimension size = this.getSize();

        this.setLocation((screenSize.width-size.width)/2,(screenSize.height-size.height)/2);

        JPanel paramPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
        paramPanel.add(new JLabel(" 服务器IP:"));
        paramPanel.add(serverIpTextField);
        paramPanel.add(new JLabel(" 服务器端口:"));
        paramPanel.add(serverPortTextField);
        paramPanel.add(new JLabel(" 您的名称:"));
        paramPanel.add(nameTextField);
        paramPanel.add(connButton);

        this.getContentPane().add(paramPanel,BorderLayout.NORTH);

        this.getContentPane().add(msgTextArea,BorderLayout.CENTER);

        JPanel sendPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));

        sendPanel.add(new JLabel("我对"));
        sendPanel.add(userComboBox);
        sendPanel.add(new JLabel("说:"));
        sendPanel.add(sendTextField);

        sendPanel.add(sendButton);

        this.getContentPane().add(sendPanel,BorderLayout.SOUTH);



        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        this.setVisible(true);

        connButton.addActionListener(this::connButtonActionListener);

        sendButton.addActionListener(this::sendButtonActionListener);

        this.msgTextArea.setEnabled(false);
        this.userComboBox.setEnabled(false);
        this.sendTextField.setEnabled(false);
        this.sendButton.setEnabled(false);




    }



    public synchronized void setMsg(String msg){
        msgTextArea.setText(msgTextArea.getText()+"\n"+msg);
    }





    protected void resetUserSet(){
        userComboBox.removeAllItems();
        if(clientCom != null && clientCom.getUserSet() != null){
            clientCom.getUserSet().forEach(name->userComboBox.addItem(name));
        }

    }

    private void connButtonActionListener(ActionEvent actionEvent) {
        try {
            String name = nameTextField.getText();
            String serverIp = serverIpTextField.getText();
            int serverPort = Integer.valueOf(serverPortTextField.getText());
            if(serverPort < 1){
                JOptionPane.showMessageDialog(null,"服务器端口错误!");
                return;
            }


            this.clientCom = createClientCom(name,serverIp,serverPort);
                    //new ClientCom(name,serverIp,serverPort);

            this.refreshUsers();


            this.nameTextField.setEnabled(false);
            this.serverIpTextField.setEnabled(false);
            this.serverPortTextField.setEnabled(false);
            this.connButton.setEnabled(false);

            this.msgTextArea.setEnabled(true);
            this.userComboBox.setEnabled(true);
            this.sendTextField.setEnabled(true);
            this.sendButton.setEnabled(true);

            JOptionPane.showMessageDialog(null,"连接服务器成功!");

            showMsg();

        } catch (Exception e) {
            JOptionPane.showMessageDialog(null,"错误:"+e.getMessage());
        }
    }




    private void sendButtonActionListener(ActionEvent actionEvent) {
        String receiverName = (String)userComboBox.getSelectedItem();
        String msg = sendTextField.getText();
        String myName = clientCom.getName();//????
        this.sendTextField.setText("");

        sendButtonAction(myName,receiverName,msg);

    }

    protected ClientCom getClientCom() {
        return clientCom;
    }







    /**
     * 点击发送按钮的逻辑
     */
    protected abstract void sendButtonAction(String senderName,String recevierName,String content);



    /**
     * 接收展示消息逻辑
     */
    protected abstract void showMsg();

    /**
     * 创建客户端通信组件
     * @param name
     * @param serverIp
     * @param serverPort
     * @return
     */
    protected abstract ClientCom createClientCom(String name, String serverIp, int serverPort);

    /**
     * 刷新用户列表
     */
    protected abstract void refreshUsers();
}

组件改进重写:

public class MyFrame extends ClientFrame{
    public MyFrame(String title) {
        super(title);
    }

    @Override
    protected void sendButtonAction(String senderName, String recevierName, String content) {
        //封装消息对象
        MessageObject messageObject = new MessageObject(MessageObject.TYPE_NORMAL,recevierName,senderName,content,null);

        //调用ClientCom的send方法
        ClientCom com = this.getClientCom();
        com.sendMsg(messageObject);
    }

    @Override
    protected void showMsg() {
        ClientCom.service.execute(()->{
            while(true){
                try {
                    MessageObject messageObject = this.getClientCom().getQueue().take();
                    String msg = messageObject.getSender()+"对"+messageObject.getReceiver()+"说"+messageObject.getContent();
                    this.setMsg(msg);
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    protected ClientCom createClientCom(String name, String serverIp, int serverPort)  {
        try {
            return new ClientCom(name,serverIp,serverPort);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void refreshUsers() {
        ClientCom.service.execute(()->{
            while (true){
                while(this.getClientCom().getUserSet()==null || this.getClientCom().getUserSet().isEmpty()){
                    this.resetUserSet();//显示用户列表
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                this.resetUserSet();//显示用户列表
                try {
                    TimeUnit.SECONDS.sleep(20);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

    }
}

最后的测试

Server:

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ssc = new ServerSocket(9999);
        while(true){
            try {
                Socket sc = ssc.accept();
                new ServerCom(sc);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
}

Client:

public class Client {
    public static void main(String[] args) {
        new MyFrame("聊天室");
    }
}

出现问题
  1. 首先我们服务端和客户端的端口要一致
  2. 初始化的部分一定将通信组件初始化进来
  3. 消息类型一定每个都要强制转换
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值