Java实现简单聊天室【含源码】

1. 前言

Socket通信与多线程问题对于初学者来说是比较混乱的东西,尤其两者又时常一起出现,因此经常把初学者搞得晕头转向。本文将对通过实现一个简单的聊天项目帮助初学者更好的理解Socket通信与多线程,重点在于实现功能的技术,因此图形化设计的过程省略了,将整个界面以及输入输出都放在控制台显示。

2. 成果演示

聊天室程序演示

3. 消息如何传送?

学过Socket通信的小伙伴都知道,如果两台主机之间要进行TCP通信,则需要一台充当服务器,另一台充当客户端,两者之间可以建立起一条Socket通道。

可是,当三台或者更多的主机要互相通信的时候怎么办呢
首先连接要建立起来肯定是需要服务器侦听的,属于客户-服务器模式,所以多台客户机肯定不能够直接连接通信,他们需要连接到同一个服务器作为中转站。比如客户A要发消息给客户B,那么A发出来的信息就要经过服务器,由服务器转发给客户B

由此又会产生一个问题:服务器连接那么多客户端,它怎么知道往哪里发送才能给到客户B呢
事实上,我们可以将一些基本信息封装到一个Message类中,如发送者,接受者,内容……发送的信息等,以类为单位,当服务器接收到这个Message时就能够得知里边一些基本内容,从而知道这封信来自何方,去往何处,该如何处理

//Serializable是实现序列化,若不实现这个接口的话类无法再流中传送
public class Message implements Serializable {
    private static final long serialVersionUID=1L; //版本兼容标志
    private String sender; //发送者
    private String getter; //接收者
    private String content; //内容
    private String sendTime; //发送时间
    private String messageType; //消息类型
}

服务器收到不同类型的Message时应该做出不同的操作,如收到请求登录的Message时需要判断账号密码是否正确;收到A发送给B的信息Message时需要转发给B;收到A请求关闭连接的Message时要回收Socket通道……本项目中的信息状态如下

public interface MessageType {
    String MESSAGE_LOGIN_SUCCESS="1"; //登录成功
    String MESSAGE_LOGIN_FAIL="2"; //登录失败
    String MESSAGE_COMM_MES="3";//普通信息包
    String MESSAGE_GET_ONLINE_FRIEND="4"; //得到在线用户列表
    String MESSAGE_RET_ONLINE_FRIEND="5";//返回在线用户列表
    String MESSAGE_CLIENT_EXIT="6"; //客户请求退出
    String MESSAGE_CLIENT_NO_EXIST="7"; //发送目标不存在
    String MESSAGE_CLIENT_OFFLINE="8"; //发送目标不在线
}

4. 多线程体现在哪些地方?

① 服务器的各条Socket

一条Socket通道对应一条连接,服务器的Socket是通过侦听得来的。因此服务器处需要利用一个循环不断进行侦听,当侦听到连接需求时,就建立起一条连接,账号密码验证通过后【将用户输入与账号密码表进行对比】,将这条连接保存到一个线程类中,启动这个线程类对这条通道进行维护,通过接收到的Message类型不同而进行不同应答,主函数则继续侦听……

利用ConcurrentHashMap<用户名,线程类>映射【用法同HashMap,不过在多线程中使用ConcurrentHashMap更安全】对线程类进行维护,当我们需要取得某条通道的时候,只需要通过用户名就能得出来;当某条Socket通道被弃用时,将其从映射中移除,通过这个映射,我们也可以得知有几个用户目前在线。

② 客户端的接收功能

我们的客户端界面会根据我们的输入进行信息的发送,但是同时我们还需要进行接收,因此接收的功能可以另开一个线程去实现,这样就可以实现发送/接收并发进行

原理类似,当账号密码都正确时,开启一个线程类,这个线程类可以对Socket通道的接收进行维护,根据接收的不同类型Message而作出相应处理工作

5. 各功能实现

①用户账号密码数据库

由于本项目的重点在于Socket通信与多线程理解,因此对于数据库方面利用了ConcurrentHashMap<用户名,用户>映射来进行模拟账号密码表。

即客户端的登录信息通过Socket发送到服务器端,服务端通过比对两者账号密码,如果一致就返回登录成功的信息,并进行线程开启等等一系列操作……

//模拟用户数据库
private static ConcurrentHashMap<String, User> userMap = new ConcurrentHashMap<>();
//数据库数据
static {
        userMap.put("123", new User("123", "123"));
        userMap.put("tom", new User("tom", "123"));
        userMap.put("捉妖龙", new User("捉妖龙", "123"));
    }

② 如何查看当前在线人数

前面提到,服务器中有一个映射维护了当前所有的Socket通道。因此客户端想知道当前有哪些用户在线,只需要向服务器发送一条Message,类型是MESSAGE_GET_ONLINE_FRIEND【请求得到在线用户】,服务器收到这条信息后遍历自己的映射,将关键字【即用户名】进行拼接再返回给客户端,客户端根据需要进行拼接读取即可。

③ 如何实现私聊

前边提过,几台主机之间互相通信需要依赖服务器作为中介。所以我们可以将要发送方、接收方、内容……封装到Message,服务器收到后得到其中的信息,知道这个信息的接受者。服务器在映射中寻找这个通道,如果找到了,那么用这条通道将信息发送出去,另一方接收后显示;如果映射中没有这个通道,则信息从哪里来回哪里去,同时告诉发送方,信息接收对象不存在。

实现离线留言

按照日常习惯,即使信息发送时对方不在线,信息也是可以发送出去的,对方上线后应该就能接收到。我们来尝试实现这个功能

  1. 如果要发送的对象不在数据库中,那么说明这个对象真的不存在,离线留言是没有意义的,因此这个时候告诉发送方:接收对象不存在!
  2. 如果要发送的对象确实在数据库中,只是此时没有上线,则我们需要将这部分Message暂时保存在数据库中,其中一个不错的方法是使用ConcurrentHashMap<String,Vector<Message>>进行保存【万能的映射哈哈】。将要发送给对应用户的信息保存在Vector中,当有一个新的连接建立时就以此连接作为关键字ID去映射中取值【即这个新用户是不是我们要等的人】,如果取得到值,则拿出对应Vector里面的Message进行发送;若取不到值说明此用户没有未接收的离线信息,不进行其他操作

④ 如何实现群发

群发其实原理与私聊类似,不过私聊是具体到某个人,而群发是遍历映射,对除了自己以外的所有通道发送Message

⑤ 如何实现退出客户端

当客户端选择退出时,说明客户不再从Socket通道接收数据,也不再从Socket通道发送数据,因此此时应该是完全的进程退出,可以使用System.exit(0)在退出关闭Socket通道之前必须要告知服务器,否则服务器不知道通道已经关闭而一直使用不存在的通道,这显然会带来很多异常。

服务器收到客户端发来的请求退出的Message后,将映射中维护的此Socket移除,同时需要让自己的循环等待接收Message的线程结束,关闭这条通道的socket

友情提示:服务器开启后才能用客户端连接!!

6. 源码

QQServer项目

common包

1. Message.java
package common;

import java.io.Serializable;

public class Message implements Serializable {
    private static final long serialVersionUID=1L;
    private String sender; //发送者
    private String getter; //接收者
    private String content; //内容
    private String sendTime; //发送时间
    private String messageType; //消息类型

    public Message(String sender, String messageType) {
        this.sender = sender;
        this.messageType = messageType;
    }

    public Message() {

    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getGetter() {
        return getter;
    }

    public void setGetter(String getter) {
        this.getter = getter;
    }

    public String getContent() {
        return content;
    }

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

    public String getSendTime() {
        return sendTime;
    }

    public void setSendTime(String sendTime) {
        this.sendTime = sendTime;
    }

    public String getMessageType() {
        return messageType;
    }

    public void setMessageType(String messageType) {
        this.messageType = messageType;
    }
}
2. MessageType.java
package common;
public interface MessageType {
    String MESSAGE_LOGIN_SUCCESS="1"; //登录成功
    String MESSAGE_LOGIN_FAIL="2"; //登录失败
    String MESSAGE_COMM_MES="3";//普通信息包
    String MESSAGE_GET_ONLINE_FRIEND="4"; //得到在线用户列表
    String MESSAGE_RET_ONLINE_FRIEND="5";//返回在线用户列表
    String MESSAGE_CLIENT_EXIT="6"; //客户请求退出
    String MESSAGE_CLIENT_NO_EXIST="7"; //发送目标不存在
    String MESSAGE_CLIENT_OFFLINE="8"; //发送目标不在线
}
3. User.java
package common;
import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID=1L;
    private String userId; //用户ID
    private String passwd; //用户密码

    public User(String userId, String passwd) {
        this.userId = userId;
        this.passwd = passwd;
    }

    public User() {
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
}
4. Utility.java
package common;
import java.util.Scanner;

public class Utility {
    public static Scanner scanner = new Scanner(System.in);
    /**
     * 从控制台读取长度字符串
     * @return
     */
    public static String readString(){
        String content = scanner.nextLine();//读取首行
        return content;
    }
}

main包

1. QQServer.java【服务器主函数】
package main;

import common.Message;
import common.MessageType;
import common.User;
import server.ManageServerConnectClientThread;
import server.ServerConnectClientThread;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;

public class QQServer {
    ServerSocket serverSocket;
    //模拟用户数据库
    private static ConcurrentHashMap<String, User> userMap = new ConcurrentHashMap<>();

    static {
        userMap.put("123", new User("123", "123"));
        userMap.put("tom", new User("tom", "123"));
        userMap.put("捉妖龙", new User("捉妖龙", "123"));
    }

    public static void main(String[] args) {
        new QQServer();
    }
    public QQServer() {
        try {
            System.out.println("在9999端口监听……");
            serverSocket = new ServerSocket(9999);
            while (true) {
                Socket socket = serverSocket.accept();
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                User user = (User) ois.readObject();
                //构建一个Message对象,准备回复
                Message message = new Message();
                //验证账号密码是否正确
                if (isUser(user.getUserId(), user.getPasswd())) {
                    message.setMessageType(MessageType.MESSAGE_LOGIN_SUCCESS);
                    oos.writeObject(message);
                    ServerConnectClientThread thread = new ServerConnectClientThread(socket, user.getUserId());
                    thread.start();
                    ManageServerConnectClientThread.addThread(user.getUserId(), thread);
                    ManageServerConnectClientThread.sendOffLineMessage(user.getUserId(), oos);
                } else {
                    message.setMessageType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(message);
                    socket.close();
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static ConcurrentHashMap<String, User> getUserMap() {
        return userMap;
    }

    public boolean isUser(String userId, String pw) {
        User user = userMap.get(userId);
        //没有这个用户
        if (user == null) {
            return false;
        }
        //密码不正确
        if (!user.getPasswd().equals(pw)) {
            return false;
        }
        return true;
    }

    public static boolean isUser(String userId) {
        User user = userMap.get(userId);
        if (user == null) {
            return false;
        }
        return true;
    }
}

server包

1. ManageServerConnectClientThread.java
package server;

import common.Message;
import common.MessageType;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 管理线程的类
 */
public class ManageServerConnectClientThread {
    public static ConcurrentHashMap<String, ServerConnectClientThread> map = new ConcurrentHashMap<>();
    public static ConcurrentHashMap<String, Vector<Message>> messageMap = new ConcurrentHashMap<>();

    public static String getOnlineFriends() {
        StringBuilder builder = new StringBuilder();
        for (Map.Entry<String, ServerConnectClientThread> entry : map.entrySet()) {
            builder.append(entry.getKey() + " ");
        }
        return builder.toString();
    }

    public static Socket getSocketById(String userId) {
        ServerConnectClientThread thread = map.get(userId);
        if (thread == null) {
            return null;
        } else {
            return thread.getSocket();
        }
    }

    /**
     * 向所有的Socket发送消息
     * @param socket 除了这个
     * @param oos
     */
    public static void sendAll(Socket socket, ObjectOutputStream oos, Message message) {
        try {
            for (Map.Entry<String, ServerConnectClientThread> entry : map.entrySet()) {
                Socket socket1 = getSocketById(entry.getKey());
                if (socket1 != socket) {
                    oos = new ObjectOutputStream(socket1.getOutputStream());
                    oos.writeObject(message);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务器暂存离线信息
     * @param userId 接受者ID
     * @param message 需要发送的信息
     */
    public static void addMessage(String userId, Message message) {
        Vector<Message> vector = messageMap.get(userId);
        if (vector == null) {
            vector = new Vector<>();
            messageMap.put(userId, vector);
        }
        vector.add(message);
    }

    /**
     * 尝试将服务器库存信息进行发送
     * @param userId 接受者ID
     * @param oos 输出流
     */
    public static void sendOffLineMessage(String userId, ObjectOutputStream oos) {
        Vector<Message> vector = messageMap.get(userId); //得到库存信息
        if (!(vector == null || vector.isEmpty())) {
            try {
                //说明当前用户有待发送消息
                Socket socket = getSocketById(userId);
                while (!vector.isEmpty()) {
                    Message message = vector.get(0);
                    //将消息按顺序发出去
                    oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message);
                    vector.remove(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static ConcurrentHashMap<String, ServerConnectClientThread> getMap() {
        return map;
    }

    public static void deleteSocket(String userId) {
        map.remove(userId);
    }

    public static void addThread(String useId, ServerConnectClientThread thread) {
        map.put(useId, thread);
    }
}
2. ServerConnectClientThread.java
package server;


import common.Message;
import common.MessageType;
import common.User;
import main.QQServer;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

/**
 * 该类的一个对象和某个客户端保持通讯
 */
public class ServerConnectClientThread extends Thread {
    private Socket socket; //这个线程对应的Socket
    private String userId; //对应客户的ID
    private boolean flag = true; //是否结束线程的标志
    private ObjectInputStream ois; //输入流
    private ObjectOutputStream oos; //输出流

    public ServerConnectClientThread(Socket socket, String userId) {
        this.socket = socket;
        this.userId = userId;
    }

    @Override
    public void run() {
        System.out.println("服务器与客户【" + userId + "】保持通信……");
        while (flag) {
            try {
                ois = new ObjectInputStream(socket.getInputStream());
                Message message = (Message) ois.readObject();
                actionByMessageType(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void actionByMessageType(Message message) {
        try {
            switch (message.getMessageType()) {
                case MessageType.MESSAGE_GET_ONLINE_FRIEND:
                    //对方请求列表
                    System.out.println("【" + message.getSender() + "】要看在线用户列表……");
                    Message message1 = new Message();
                    message1.setMessageType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
                    message1.setGetter(message.getSender());//发送者变接受者
                    //消息内容为找到的内容
                    message1.setContent(ManageServerConnectClientThread.getOnlineFriends());
                    oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message1);
                    break;
                case MessageType.MESSAGE_CLIENT_EXIT:
                    ManageServerConnectClientThread.deleteSocket(userId);
                    flag = false; //此线程结束的标志
                    ManageServerConnectClientThread.deleteSocket(userId); //从线程集合中除名
                    socket.close(); //将这个Socket移除
                    System.out.println("用户【" + userId + "】断开连接!");
                    break;
                case MessageType.MESSAGE_COMM_MES:
                    if (message.getGetter().equals("All")) {
                        //说明这是群发消息
                        ManageServerConnectClientThread.sendAll(socket, oos, message);
                        break;
                    }
                    //得到目标的Socket
                    Socket socket = ManageServerConnectClientThread.getSocketById(message.getGetter());
                    Message message2 = new Message();
                    if (QQServer.isUser(message.getGetter())) {//看此用户是否在数据库中
                        //注册的用户里有这号人
                        if (socket == null) {
                            //发回原处,告知当前用户离线,已经留言
                            socket = this.socket;
                            message2.setMessageType(MessageType.MESSAGE_CLIENT_OFFLINE);
                            message2.setGetter(message.getGetter());
                            //把消息放进消息盒
                            ManageServerConnectClientThread.addMessage(message.getGetter(), message);
                        } else {
                            message2 = message;
                        }
                    } else {
                        //数据库的用户里没有这号人
                        socket = this.socket;
                        message2.setMessageType(MessageType.MESSAGE_CLIENT_NO_EXIST);
                        message2.setGetter(message.getGetter());
                    }
                    oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message2);
                    break;
            }
        } catch (Exception e) {
            System.out.println("出现异常!");
        }

    }

    public Socket getSocket() {
        return socket;
    }
}

QQClient项目

common包

1. Message.java
package common;

import java.io.Serializable;

public class Message implements Serializable {
    private static final long serialVersionUID=1L; //版本兼容标志
    private String sender; //发送者
    private String getter; //接收者
    private String content; //内容
    private String sendTime; //发送时间
    private String messageType; //消息类型

    public Message(String sender, String messageType) {
        this.sender = sender;
        this.messageType = messageType;
    }

    public Message() {
    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getGetter() {
        return getter;
    }

    public void setGetter(String getter) {
        this.getter = getter;
    }

    public String getContent() {
        return content;
    }

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

    public String getSendTime() {
        return sendTime;
    }

    public void setSendTime(String sendTime) {
        this.sendTime = sendTime;
    }

    public String getMessageType() {
        return messageType;
    }

    public void setMessageType(String messageType) {
        this.messageType = messageType;
    }
}
2. MessageType.java
package common;

public interface MessageType {
    String MESSAGE_LOGIN_SUCCESS="1"; //登录成功
    String MESSAGE_LOGIN_FAIL="2"; //登录失败
    String MESSAGE_COMM_MES="3";//普通信息包
    String MESSAGE_GET_ONLINE_FRIEND="4"; //得到在线用户列表
    String MESSAGE_RET_ONLINE_FRIEND="5";//返回在线用户列表
    String MESSAGE_CLIENT_EXIT="6"; //客户请求退出
    String MESSAGE_CLIENT_NO_EXIST="7"; //发送目标不存在
    String MESSAGE_CLIENT_OFFLINE="8"; //发送目标不存在
}
3. User.java
package common;

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID=1L;
    private String userId; //用户ID
    private String passwd; //用户密码

    public User(String userId, String passwd) {
        this.userId = userId;
        this.passwd = passwd;
    }

    public User() {
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
}
4. Utility.java
package common;

import java.util.Scanner;

public class Utility {
    public static Scanner scanner = new Scanner(System.in);
    /**
     * 从控制台读取长度字符串
     * @return
     */
    public static String readString(){
        String content = scanner.nextLine();//读取首行
        return content;
    }
}

client包

1. QQView.java【客户端主函数】
package client;

import client.service.UserClientService;
import common.Utility;

public class QQView {
    private boolean loop = true; //控制是否显示菜单
    private String key = ""; //接收用户键盘输入
    private UserClientService userClientService = new UserClientService();

    public static void main(String[] args) {
        new QQView().mainMenu();
    }

    public void mainMenu() {
        while (loop) {
            System.out.println("============欢迎登录网络通信系统============");
            System.out.println("\t\t 1 登录系统");
            System.out.println("\t\t 9 退出系统");
            key = Utility.readString();
            switch (key) {
                case "1":
                    System.out.print("请输入用户号:");
                    String userId = Utility.readString();
                    System.out.print("请输入密 码:");
                    String pwd = Utility.readString();
                    //去服务端看看用户是否合法
                    if (userClientService.checkUser(userId, pwd)) {
                        System.out.println("============欢迎【" + userId + "】登录网络通信系统============");
                        //由此进入二级菜单
                        while (loop) {
                            System.out.println("============【" + userId + "】网络通信系统二级菜单============");
                            System.out.println("\t\t 1 显示在线用户列表");
                            System.out.println("\t\t 2 群发消息");
                            System.out.println("\t\t 3 私聊消息");
                            System.out.println("\t\t 9 退出系统");
                            System.out.print("请输入你的选择:");
                            key = Utility.readString();
                            String name; //发送给谁
                            String contents; //消息内容
                            switch (key) {
                                case "1":
                                    userClientService.onlineFriendList();
                                    break;
                                case "2":
                                    System.out.print("群发内容:" );
                                    contents = Utility.readString();
                                    userClientService.sendAll(contents);
                                    break;
                                case "3":
                                    System.out.print("发送给:");
                                    name = Utility.readString();
                                    System.out.print("内容:" );
                                    contents = Utility.readString();
                                    userClientService.Send(name,contents);
                                    break;
                                case "9":
                                    userClientService.closedComm();
                                    System.out.println("客户端退出...");
                                    loop = false;
                                    break;
                            }
                            try {
                                Thread.sleep(5); //为了输出好看些
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    } else {
                        System.out.println("登录失败!");
                        break;
                    }
                case "9":
                    loop = false;
                    break;
            }
        }
    }
}
client.service包
1. ClientConnectServerThread.java
package client.service;

import common.Message;
import common.MessageType;

import java.io.ObjectInputStream;
import java.net.Socket;

public class ClientConnectServerThread extends Thread {
    //该线程需要持有Socket对象
    private Socket socket;
    private String userId;
    private ObjectInputStream ois;

    public ClientConnectServerThread(Socket socket, String userId) {
        this.socket = socket;
        this.userId = userId;
    }

    @Override
    public void run() {
        //后台Socket服务器要一直保持通讯
        while (true) {
            try {
                ois = new ObjectInputStream(socket.getInputStream());
                //如果在流中没有读取到这一对象,则会停顿在此处
                Message message = (Message) ois.readObject();
                actionByMessageType(message);//根据收到的消息类型做出反应
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void actionByMessageType(Message message) {
        switch (message.getMessageType()) {
            case MessageType.MESSAGE_RET_ONLINE_FRIEND:
                //客户收到服务器的返回信息,信息内容就是在线人数
                String[] split = message.getContent().split(" ");
                System.out.println("在线用户如下:");
                for (String name : split) {
                    System.out.println("用户:" + name);
                }
                break;
            case MessageType.MESSAGE_COMM_MES:
                //收到普通消息,提出内容、发送者、发送时间打印在控制台
                System.out.println(message.getSendTime());
                if (message.getGetter().equals("All")) {
                    //说明这是群发消息
                    System.out.println("【" + message.getSender() + "】对【所有人】说:");
                } else {
                    System.out.println("【" + message.getSender() + "】对【我】说:");
                }
                System.out.print(message.getContent() + "\n\n请输入你的选择:");
                break;
            case MessageType.MESSAGE_CLIENT_NO_EXIST:
                //私聊目标不存在
                System.out.println("客户【" + message.getGetter() + "】不存在,无法发送!");
                break;
            case MessageType.MESSAGE_CLIENT_OFFLINE:
                System.out.println("客户【" + message.getGetter() + "】不在线,其在线后会收到消息!");
        }
    }

    public Socket getSocket() {
        return socket;
    }


}
2. UserClientService.java
package client.service;


import common.Message;
import common.MessageType;
import common.User;
import common.Utility;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Calendar;

/**
 * 用于客户端发送数据给服务端
 */
public class UserClientService {
    private User user = new User(); //当前客户
    private Socket socket; //当前客户对应的Socket
    private boolean flag = false; //登录是否成功的标志
    private ObjectOutputStream oos;
    private ObjectInputStream ois;

    public boolean checkUser(String userId, String pwd) {
        try {
            //封装一个User对象,发送到服务器进行检查
            user.setUserId(userId);
            user.setPasswd(pwd);
            //连接到服务器
            socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
            oos = new ObjectOutputStream(socket.getOutputStream());
            //将用户对象发送出去
            oos.writeObject(user);
            ois = new ObjectInputStream(socket.getInputStream());
            //对面会将消息封装为一个Message对象
            Message message = (Message) ois.readObject();
            //此时登录成功
            if (message.getMessageType().equals(MessageType.MESSAGE_LOGIN_SUCCESS)) {
                ClientConnectServerThread thread = new ClientConnectServerThread(socket, userId);
                thread.start();
                flag = true;
            } else {
                socket.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return flag;
    }

    /**
     * 拉取在线客户
     */
    public void onlineFriendList() {
        //这是一条拉取列表的信息
        Message message = new Message(user.getUserId(), MessageType.MESSAGE_GET_ONLINE_FRIEND);
        try {
            //每次使用流就要重新绑定一次
            oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送一条结束通道的信息给服务器
     */
    public void closedComm() {
        try {
            Message message = new Message(user.getUserId(), MessageType.MESSAGE_CLIENT_EXIT);
            oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
            socket.close(); //将当前类的socket通道关闭
            System.exit(0);//结束进程及由此进程引发的所有线程
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 与某人私聊,并发送信息
     */
    public void Send(String name, String contents) {
        try {
            Message message1 = new Message();
            message1.setMessageType(MessageType.MESSAGE_COMM_MES);//这是一条普通消息
            message1.setGetter(name);//接收人
            message1.setContent(contents);
            message1.setSender(user.getUserId());
            message1.setSendTime(formatTime());
            oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message1);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void sendAll(String contents) {
        Send("All", contents);
    }

    public String formatTime() {
        Calendar instance = Calendar.getInstance();
        int hour = instance.get(Calendar.HOUR_OF_DAY);
        int min = instance.get(Calendar.MINUTE);
        StringBuilder builder = new StringBuilder("\n");
        if (hour < 10)
            builder.append("0");
        builder.append(hour+":");
        if (min < 10)
            builder.append("0");
        builder.append(min);
        return builder.toString();
    }
}

7. 总结

本程序参考了韩顺平老师的网课B站链接,韩老师的课很好,很适合初学者,同时韩老师也是建议初学者可以把这个小项目吃透,这样对Socket和多线程会有更深一步的理解,这也是我写这篇博客的原因,帮助自己从头到尾整理一遍思路,当然,如果能帮助到你就更好了!

  • 79
    点赞
  • 562
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
好的,下面是一个简单Java 聊天室源码示例,包服务器端和客户端程序: 1. 服务器端程序 ```java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; public class ChatServer { private ArrayList<PrintWriter> clientOutputStreams; public static void main(String[] args) { new ChatServer().startServer(); } public void startServer() { clientOutputStreams = new ArrayList<PrintWriter>(); try { ServerSocket serverSock = new ServerSocket(5000); while (true) { Socket clientSocket = serverSock.accept(); PrintWriter writer = new PrintWriter(clientSocket.getOutputStream()); clientOutputStreams.add(writer); Thread t = new Thread(new ClientHandler(clientSocket)); t.start(); System.out.println("Got a connection"); } } catch(IOException ex) { ex.printStackTrace(); } } public void broadcast(String message) { for (PrintWriter writer : clientOutputStreams) { writer.println(message); writer.flush(); } } class ClientHandler implements Runnable { BufferedReader reader; Socket socket; public ClientHandler(Socket clientSocket) { try { socket = clientSocket; InputStreamReader isReader = new InputStreamReader(socket.getInputStream()); reader = new BufferedReader(isReader); } catch (IOException ex) { ex.printStackTrace(); } } public void run() { String message; try { while ((message = reader.readLine()) != null) { System.out.println("read " + message); broadcast(message); } } catch (IOException ex) { ex.printStackTrace(); } } } } ``` 2. 客户端程序 ```java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class ChatClient { BufferedReader reader; PrintWriter writer; Socket sock; public static void main(String[] args) { new ChatClient().startClient(); } public void startClient() { try { sock = new Socket("127.0.0.1", 5000); InputStreamReader isReader = new InputStreamReader(sock.getInputStream()); reader = new BufferedReader(isReader); writer = new PrintWriter(sock.getOutputStream()); System.out.println("networking established"); Thread readerThread = new Thread(new IncomingReader()); readerThread.start(); } catch (IOException ex) { ex.printStackTrace(); } } public class IncomingReader implements Runnable { public void run() { String message; try { while ((message = reader.readLine()) != null) { System.out.println("read " + message); } } catch (IOException ex) { ex.printStackTrace(); } } } public void sendMessage(String message) { writer.println(message); writer.flush(); } } ``` 这是一个简单Java 聊天室实现。如果你想要实现更多的功能,可以根据需求进行修改和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值