用 Socket 实现多人聊天

Socket 是一种网络通信的端点,它允许程序在网络上进行数据的发送和接收。它通常由一个 IP 地址和一个端口号组成,能够在网络上唯一标识一个通信端点。

在这里插入图片描述


工作原理
客户端和服务器:在网络通信中,Socket 通常分为客户端和服务器两部分。客户端通过 Socket 连接到服务器的 Socket,然后可以进行数据的发送和接收。

连接建立:在 TCP(传输控制协议)中,客户端和服务器之间的连接建立过程称为“三次握手”。这确保了通信双方的准备状态,并建立起可靠的连接。

数据传输:一旦连接建立,数据可以通过 Socket 进行传输。TCP 提供可靠的数据传输服务,而 UDP(用户数据报协议)提供无连接、不保证可靠性的传输服务。


效果演示

开启 3 个客户端, 并登录上去三个账户.

在这里插入图片描述

私聊消息

yaya 私发消息给 snow:
在这里插入图片描述
snow 接受到的消息:
在这里插入图片描述
snow 回复 yaya :

在这里插入图片描述

yaya 收到消息:

在这里插入图片描述

群聊消息

查看在线用户

在这里插入图片描述

群发

在这里插入图片描述

收到消息:
在这里插入图片描述
在这里插入图片描述





代码结构

在这里插入图片描述


代码

客户端

客户端连接服务端线程

import java.io.ObjectInputStream;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @description: 客户端连接服务端线程
 * @author: Snow
 * @date: 2024/6/25
 * **************************************************
 * 修改记录(时间--修改人--修改说明):
 */
public class ClientConnectServerThread extends Thread {

    /** 该线程持有Socket */
    private Socket socket;

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

    @Override
    public void run() {
        //  因为该线程需要在后台与服务端进行通信, 所以需要使用while循环
        while ( true ) {
            try {
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //  如果服务器没有发生消息,线程会阻塞到这里
                Message message = (Message) ois.readObject();
                //  判断消息类型
                if( MessageType.MESSAGE_RET_ONLINE_FRIEND.equals(message.getMessageType()) ){
                    //  去除消息 并显示
                    String[] split = message.getContent().split(" ");
                    System.out.println("===当前在线用户列表===");
                    for (int i = 0; i < split.length; i++) {
                        System.out.println("用户:" + split[i]);
                    }
                } else if( MessageType.MESSAGE_COMMON_MSG.equals(message.getMessageType()) ){
                    //  接收消息并打印
                    String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss "));
                    System.out.println( time + message.getSender() + " 对 " + message.getGetter() + " 说: " + message.getContent());
                }else if( MessageType.MESSAGE_TO_ALL_MSG.equals(message.getMessageType()) ){
                    //  接收消息并打印
                    String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss "));
                    System.out.println( time + message.getSender() + " 对所有在线用户说: " + message.getContent());
                } else {
                    System.out.println("其他消息类型,暂时不处理!");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public Socket getSocket() {
        return socket;
    }

    public void setSocket(Socket socket) {
        this.socket = socket;
    }
}

管理客户端连接到服务器线程

import java.util.concurrent.ConcurrentHashMap;
/**
 * @description: 管理客户端连接到服务器线程
 * @author: Snow
 * @date: 2024/6/25
 * **************************************************
 * 修改记录(时间--修改人--修改说明):
 */
public class ManageClientConnectServerThread {

    // 存放线程 key 为 uniqueId , value 为 ClientConnectServerThread
    private static ConcurrentHashMap<String, ClientConnectServerThread> hm = new ConcurrentHashMap<>();

    // 添加线程
    public static void addClientConnectServerThread(String uniqueId, ClientConnectServerThread clientConnectServerThread) {
        hm.put(uniqueId, clientConnectServerThread);
    }

    // 获取线程
    public static ClientConnectServerThread getClientConnectServerThread(String uniqueId) {
        return hm.get(uniqueId);
    }

    public static void removeClientConnectServerThread(String uniqueId) {
        hm.remove(uniqueId);
    }
}

消息服务

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

/**
 * @description: 消息服务
 * @author: Snow
 * @date: 2024/6/27
 * **************************************************
 * 修改记录(时间--修改人--修改说明):
 */
public class MessageService {

    /** 发消息给某人 */
    public void sendMessageToOne(String content, String getter, String sender) {
        Message message = new Message();
        message.setMessageType(MessageType.MESSAGE_COMMON_MSG);
        message.setSender(sender);
        message.setGetter(getter);
        message.setContent(content);
        try {
            //  获取当前线程的 socket
            Socket socket = ManageClientConnectServerThread.getClientConnectServerThread(sender).getSocket();
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /** 群发消息 */
    public void sendMessageToAll(String content, String sender) {
        Message message = new Message();
        message.setMessageType(MessageType.MESSAGE_TO_ALL_MSG);
        message.setSender(sender);
        message.setContent(content);
        try {
            ClientConnectServerThread clientConnectServerThread = ManageClientConnectServerThread.getClientConnectServerThread(sender);
            Socket socket = clientConnectServerThread.getSocket();
            new ObjectOutputStream(socket.getOutputStream()).writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端用户服务

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

/**
 * @description: 客户端用户服务
 * @author: Snow
 * @date: 2024/6/25
 * **************************************************
 * 修改记录(时间--修改人--修改说明):
 */
public class UserClientService {

    private User user = new User();

    private Socket socket;

    public boolean checkUser(String userId, String password) {
        user.setUserId(userId);
        user.setPassword(password);
        boolean b = false;
        try {
            //  服务端的地址和端口
            socket = new Socket(InetAddress.getByName("127.0.0.1"), 8888);
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            //  发送user对象
            oos.writeObject(user);
            //  读取从 服务端来的 message 对象
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Message message = (Message)ois.readObject();
            //  解析登录结果
            if( MessageType.MESSAGE_LOGIN_OK.equals(message.getMessageType()) ){
                //  创建和服务器端保存通信的线程
                ClientConnectServerThread clientThread = new ClientConnectServerThread(socket);
                //  启动线程
                clientThread.start();
                //  为了客户端的扩展, 将线程放入到集合中
                ManageClientConnectServerThread.addClientConnectServerThread(userId, clientThread);
                b = true;
            } else {
                //  关闭 socket
                socket.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return b;
    }

    /** 向服务端请求获取在线用户列表 */
    public void getOnlineFriendList() {
        //  发送一个 message 对象到服务端
        Message message = new Message();
        message.setMessageType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
        message.setSender(user.getUserId());
        try {
            //  获取当前线程的 socket
            Socket socket = ManageClientConnectServerThread.getClientConnectServerThread(user.getUserId()).getSocket();
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    /** 退出 */
    public void exit() {
        //  发送一个 message 对象到服务端
        Message message = new Message();
        message.setMessageType(MessageType.MESSAGE_CLIENT_EXIT);
        message.setSender(user.getUserId());
        try {
            //  获取当前线程的 socket
            Socket socket = ManageClientConnectServerThread.getClientConnectServerThread(user.getUserId()).getSocket();
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
            System.out.println(user.getUserId() + " 退出系统");
            ManageClientConnectServerThread.removeClientConnectServerThread(user.getUserId());
            socket.close(); //  关闭 socket
            System.exit(0); //  结束进程
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

客户端面板

/**
 * @description: 客户端面板
 * @author: Snow
 * @date: 2024/6/24
 * **************************************************
 * 修改记录(时间--修改人--修改说明):
 */
public class MainClientView {

    public static void main(String[] args) {
        MainClientView qqView = new MainClientView();
        qqView.mainMenu();
        System.out.println("谢谢使用");
    }

    /** 控制是否显示菜单 */
    private boolean loop = true;

    /** 接收用户的键盘输入 */
    private String key = "";

    /** 客户端用户服务 */
    private UserClientService userClientService = new UserClientService();

    private MessageService messageService = new MessageService();

    private void mainMenu() {
      while (loop){
          System.out.println("=======================WELCOME================");
          System.out.println("\t\t 1.登录");
          System.out.println("\t\t 9.退出");
          System.out.print("请输入你的选择...");

          key = Utility.readString(1);

          switch (key){
              case "1":
                  System.out.println("请输入用户...");
                  String userId = Utility.readString(10);
                  System.out.println("请输入密码...");
                  String password = Utility.readString(10);
                  if ( userClientService.checkUser(userId, password) ){
                      System.out.println("============== WELCOME " + userId + " 登录成功 =============");
                      while (loop){
                          System.out.println("\n===========二级菜单(userId:" + userId + ")==============");
                          System.out.println("\t\t 1.显示在线用户列表");
                          System.out.println("\t\t 2.群发消息");
                          System.out.println("\t\t 3.私聊消息");
                          System.out.println("\t\t 4.发送文件");
                          System.out.println("\t\t 9.退出系统");
                          System.out.print("请输入你的选择...");
                          System.out.println();
                          key = Utility.readString(1);
                          switch (key){
                              case "1":
                                  userClientService.getOnlineFriendList();
                                  break;
                              case "2":
                                  System.out.println("\n===========群发窗口==============");
                                  System.out.println("\t\t 请输入要发送的内容...");
                                  String s = Utility.readString(100);
                                  messageService.sendMessageToAll(s, userId);
                                  break;
                              case "3":
                                  System.out.println("\n===========私聊窗口==============");
                                  System.out.println("\t\t 1.请选择要私聊的心动对象...");
                                  String getUserId = Utility.readString(10);
                                  System.out.println("\t\t 2.请输入要发送的内容...");
                                  String content = Utility.readString(100);
                                  messageService.sendMessageToOne(content, getUserId, userId);
                                  break;
                              case "4":
                                  System.out.println("发送文件");
                                  break;
                              case "9":
                                  loop = false;
                                  userClientService.exit();
                                  break;
                          }
                      }
                  }else {
                      System.out.println("用户名或密码错误!");
                  }
                  break;
              case "9":
                  loop = false;
                  break;
          }
      }
    }
}

服务端

管理服务器连接的客户端线程

import java.util.concurrent.ConcurrentHashMap;

/**
 * @description: 管理服务器连接的客户端线程
 * @author: Snow
 * @date: 2024/6/25
 * **************************************************
 * 修改记录(时间--修改人--修改说明):
 */
public class ManageClientsThread {

    private static ConcurrentHashMap<String, ServerConnectClientThread> hm = new ConcurrentHashMap();

    public static void addServerConnectClientThread(String uniqueId, ServerConnectClientThread serverConnectClientThread) {
        hm.put(uniqueId, serverConnectClientThread);
    }

    public static ServerConnectClientThread getServerConnectClientThread(String uniqueId) {
        return hm.get(uniqueId);
    }

    /** 返回在线唯一标识id集合 */
    public static String getOnlineUser() {
        StringBuilder sb = new StringBuilder();
        for (String uniqueId : hm.keySet()) {
            sb.append(uniqueId + " ");
        }
        return sb.toString();
    }

    /** 删除指定在线用户 */
    public static void removeServerConnectClientThread(String uniqueId) {
        hm.remove(uniqueId);
    }
}

该服务端和客户端保持通信的线程

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

/**
 * @description: 该服务端和客户端保持通信的线程
 * @author: Snow
 * @date: 2024/6/25
 * **************************************************
 * 修改记录(时间--修改人--修改说明):
 */
public class ServerConnectClientThread extends Thread {

    private Socket socket;

    private String userId;

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

    public Socket getSocket() {
        return socket;
    }

    public String getUserId() {
        return userId;
    }

    public void run() {
        //  发送和接收消息
        while (true) {
            System.out.println("服务端和客户端["+userId+"]保持通信,读取数据...");
            try {
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                Message messageRequest = (Message) ois.readObject();
                if( MessageType.MESSAGE_GET_ONLINE_FRIEND.equals(messageRequest.getMessageType()) ){
                    System.out.println("客户端["+userId+"]请求在线用户列表");
                    String onlineUser = ManageClientsThread.getOnlineUser();
                    Message messageResponse = new Message();
                    messageResponse.setMessageType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
                    messageResponse.setContent(onlineUser);
                    messageResponse.setGetter(messageRequest.getSender());
                    //  返回给客户端
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(messageResponse);
                }else if( MessageType.MESSAGE_CLIENT_EXIT.equals(messageRequest.getMessageType()) ){
                    //  将该线程从集合中移除
                    ManageClientsThread.removeServerConnectClientThread(messageRequest.getSender());
                    //  关闭socket
                    socket.close();
                    System.out.println("客户端["+userId+"]退出了");
                    break;
                } else if( MessageType.MESSAGE_COMMON_MSG.equals(messageRequest.getMessageType()) ){
                    //  私发消息
                    Message messageResponse = new Message();
                    messageResponse.setMessageType( MessageType.MESSAGE_COMMON_MSG );
                    //  获取 收取人的 socket
                    ServerConnectClientThread serverConnectClientThread = ManageClientsThread.getServerConnectClientThread(messageRequest.getGetter());
                    if( serverConnectClientThread == null ){
                        //  接受人不存在
                        messageResponse.setSender(messageRequest.getSender());
                        messageResponse.setGetter(messageRequest.getSender());
                        messageResponse.setContent("接受人 " + messageRequest.getGetter() + " 不存在");
                        new ObjectOutputStream(socket.getOutputStream()).writeObject(messageResponse);
                    }else{
                        messageResponse.setSender(messageRequest.getSender());
                        messageResponse.setGetter(messageRequest.getGetter());
                        messageResponse.setContent(messageRequest.getContent());
                        Socket socket = serverConnectClientThread.getSocket();
                        new ObjectOutputStream(socket.getOutputStream()).writeObject(messageResponse);
                    }
                } else if( MessageType.MESSAGE_TO_ALL_MSG.equals(messageRequest.getMessageType()) ){
                    //  获取在线用户
                    String onlineUser = ManageClientsThread.getOnlineUser();
                    String[] onlineUsers = onlineUser.split(" ");
                    if( onlineUsers == null || onlineUsers.length == 0 ){
                        System.out.println("没有在线用户");
                    }
                    for (int i = 0; i < onlineUsers.length; i++) {
                        if( !onlineUsers[i].equals(messageRequest.getSender()) ){
                            //  获取 收取人的 socket
                            ServerConnectClientThread serverConnectClientThread = ManageClientsThread.getServerConnectClientThread(onlineUsers[i]);
                            Socket socket = serverConnectClientThread.getSocket();
                            new ObjectOutputStream(socket.getOutputStream()).writeObject(messageRequest);
                        }
                    }
                } else {
                    System.out.println("先不处理");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

服务端Main

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

/**
 * @description: 服务端Main
 * @author: Snow
 * @date: 2024/6/25
 * **************************************************
 * 修改记录(时间--修改人--修改说明):
 */
public class MainServer {

    public static void main(String[] args) {
        new MainServer();
    }

    private ServerSocket serverSocket = null;

    public MainServer() {
        try {
            serverSocket = new ServerSocket(8888);
            //  当和客户端建立连接时,服务端会返回一个socket对象,继续监听
            while ( true ) {
                Socket socket = serverSocket.accept();
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                //  获取客户端传来的 user 验证其身份
                User user = (User)ois.readObject();
                Message message = new Message();
                if( "123456".equals(user.getPassword()) ){
                    // 创建一个线程和客户端保持通信
                    ServerConnectClientThread serverThread = new ServerConnectClientThread(socket, user.getUserId());
                    // 启动线程
                    serverThread.start();
                    //  把该线程对象放入客户端集合中,进行管理
                    ManageClientsThread.addServerConnectClientThread(user.getUserId(), serverThread);
                    //  向客户端写回消息
                    message.setMessageType( MessageType.MESSAGE_LOGIN_OK );
                    oos.writeObject(message);
                }else{
                    message.setMessageType( MessageType.MESSAGE_LOGIN_FAIL );
                    //  向客户端写回消息
                    oos.writeObject(message);
                    socket.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //  关闭资源
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

实体对象

Message

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * @description:
 * @author: Snow
 * @date: 2024/6/24
 * **************************************************
 * 修改记录(时间--修改人--修改说明):
 */
public class Message implements Serializable {

    private String sender;

    private String getter;

    private String content;

    private LocalDateTime sendTime;

    private String messageType;

    public Message () {
    }

    public Message(String sender, String getter, String content, LocalDateTime sendTime, String messageType) {
        this.sender = sender;
        this.getter = getter;
        this.content = content;
        this.sendTime = sendTime;
        this.messageType = messageType;
    }

    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 LocalDateTime getSendTime() {
        return sendTime;
    }

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

    public String getMessageType() {
        return messageType;
    }

    public void setMessageType(String messageType) {
        this.messageType = messageType;
    }
}

User

import java.io.Serializable;

/**
 * @description:
 * @author: Snow
 * @date: 2024/6/24
 * **************************************************
 * 修改记录(时间--修改人--修改说明):
 */
public class User implements Serializable {
    private String userId;

    private String password;

    public User() {
    }

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

    public String getUserId() {
        return userId;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

MessageType

/**
 * @description:
 * @author: Snow
 * @date: 2024/6/24
 * **************************************************
 * 修改记录(时间--修改人--修改说明):
 */
public interface MessageType {

    /** 登录成功 */
    String MESSAGE_LOGIN_OK = "1";

    /** 登录失败 */
    String MESSAGE_LOGIN_FAIL = "2";

    /** 普通消息 */
    String MESSAGE_COMMON_MSG = "3";

    /** 群发消息 */
    String MESSAGE_TO_ALL_MSG = "4";

    /** 要求在线用户列表 */
    String MESSAGE_GET_ONLINE_FRIEND = "5";

    /** 返回在线用户列表 */
    String MESSAGE_RET_ONLINE_FRIEND = "6";

    /** 客户端请求退出 */
    String MESSAGE_CLIENT_EXIT = "7";
}

工具类

import java.util.Scanner;

public class Utility {

    private static Scanner scanner;

    static {
        scanner=new Scanner(System.in);
    }

    public Utility(){

    }

    public static char readMenuSelection(){
        while (true){
            String str=readKeyBoard(1,false);
            char c=str.charAt(0);
            if (c == '1' || c == '2' || c == '3' || c == '4' || c == '5') {
                return c;
            }

            System.out.print("选择错误,请重新输入:");
        }
    }

    public static char readChar(){
        String str=readKeyBoard(1,false);
        return str.charAt(0);
    }

    public static char readChar(char defaultValue){
        String str=readKeyBoard(1, true);
        return str.length()==0?defaultValue:str.charAt(0);
    }

    public static int readInt(){
        while (true){
            String str=readKeyBoard(2, false);
            try {
                int n=Integer.parseInt(str);
                return n;
            }catch (NumberFormatException var3){
                System.out.println("数字输入错误,请重新输入:");
            }
        }
    }    public static int readInt(int defaultValue) {
        while(true) {
            String str = readKeyBoard(2, true);
            if (str.equals("")) {
                return defaultValue;
            }

            try {
                int n = Integer.parseInt(str);
                return n;
            } catch (NumberFormatException var4) {
                System.out.print("数字输入错误,请重新输入:");
            }
        }
    }

    public static String readString(int limit) {
        return readKeyBoard(limit, false);
    }

    public static String readString(int limit, String defaultValue) {
        String str = readKeyBoard(limit, true);
        return str.equals("") ? defaultValue : str;
    }

    public static char readConfirmSelection(){
        while (true){
            String str=readKeyBoard(1,false).toUpperCase();
            char c=str.charAt(0);
            if(c=='Y'||c=='N'){
                return c;
            }
            System.out.print("选择错误,请重新输入:");
        }
    }
    private static String readKeyBoard(int limit, boolean blankReturn){
        String line="";

        while (scanner.hasNextLine()){
            line=scanner.nextLine();
            if(line.length()==0){
                if(blankReturn){
                    return line;
                }
            }else {
                if(line.length()>=1 && line.length()<=limit){
                    break;
                }
                System.out.println("输入长度(不大于" + limit + ")错误,请重新输入:");
            }
        }
        return line;
    }

}

结尾

以上就是简单的实现多人聊天的简陋版代码了;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值