Bootstrap

基于Java Socket写一个多线程的聊天室(附源码)

什么是socket编程

Socket编程是在TCP/IP上的网络编程,但是Socket在上述模型的什么位置呢。这个位置被一个天才的理论家或者是抽象的计算机大神提出并且安排出来

​ 我们可以发现Socket就在应用程序的传输层和应用层之间,设计了一个Socket抽象层,传输层的底一层的服务提供给Socket抽象层,Socket抽象层再提供给应用层,问题又来了,应用层和Socket抽象层之间和传输层,网络层之间如何通讯的呢,了解这个之前,我们还是回到原点

​ 要想理解Socket编程怎么通过Socket关键词实现服务器和客户端通讯,必须得先了解TCP/IP是怎么通讯的,在这个的基础上再去理解Socket的握手通讯

套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。

​ 当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。

java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。

以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:

  • 服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
  • 服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
  • 服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
  • Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
  • 在服务器端,accept() 方法返回服务器上一个新的 Socket 引用,该 Socket 连接到客户端的 Socket。

多线程的聊天室

  1. 封装 常量类 用于保存所用到的常量
public class Constant {
    //所有成功的常量
    public static final String SUCCESS="SUCCESS";
    //所有失败的常量
    public static final String FAIL="fail";
    //密码
    public static final String DEFAULT_PASSWORD="123";
    //服务器名字
    public static final String SERVER_NAME="server";
    public static final String OK="ok";
    public static final String NO="no";
    //保存在綫的用戶
    public static final Map<String, Socket> USERS = new ConcurrentHashMap<>(8);
}

//选择的功能 用静态常量表示
public class MessageType {
    public static final int TO_SERVER=1;
    public static final int TO_CLIENT=2;
    public static final int TO_ALL=3;
    public static final int LOGIN=4;
    public static final int FROM_SERVER=5;
    public static final int RECEIVE=6;

}
  1. 封装工具类 减少代码冗余
public class MsgUtils {

    //从流 中读取消息
    public static Optional<Message> readMsg(InputStream inputStream) {
        ObjectInputStream ois ;
        try {
            ois = new ObjectInputStream(inputStream);
            //封装成optional 将来外界访问 可以避免 空指针
            return Optional.ofNullable((Message) ois.readObject());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return Optional.empty();
    }
    //从流 中写入消息
    public static void writeMsg(OutputStream outputStream, Message message) {
        ObjectOutputStream oos;
        try {
            oos = new ObjectOutputStream(outputStream);
            oos.writeObject(message);
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

//输入
public class ScannerUtil {
    private static final Scanner scanner = new Scanner(System.in);

    public static String input() {
        return scanner.next();
    }


}
  1. 客户端代码(client)
public class Client {

    @SuppressWarnings("all")
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket();
        //连接服务器
        socket.connect(new InetSocketAddress(8848));
        //发消息
        OutputStream outputStream = socket.getOutputStream();
        InputStream inputStream = socket.getInputStream();

        String username = null;

        while (true) {
            //登录
            if (username == null) {
                username = login(outputStream, inputStream);
            } else {
                printOrder();
                String input = ScannerUtil.input();
                switch (Integer.parseInt(input)) {
                    case MessageType.TO_SERVER:
                        sendToServer(username, outputStream, inputStream);
                        break;
                    case MessageType.TO_CLIENT:
                        sendToFriend(username, outputStream, inputStream);
                        break;
                    case MessageType.TO_ALL:
                        sendToAll(username, outputStream, inputStream);
                        break;
                    case MessageType.RECEIVE:
                        receiveMsg(inputStream);
                        break;
                }
            }


        }
    }

    private static void receiveMsg(InputStream inputStream) {
        while (true) {
            Optional<Message> msg = MsgUtils.readMsg(inputStream);
            msg.ifPresent(m -> System.out.println(m.getUsername() + ":" + m.getContent()));
        }
    }

    private static void sendToAll(String username, OutputStream outputStream, InputStream inputStream) {
        while (true) {
            System.out.println(username + ":");
            String msg = ScannerUtil.input();
            MsgUtils.writeMsg(outputStream,
                    new Message(MessageType.TO_ALL, msg, username));
        }

    }

    private static void sendToFriend(String username, OutputStream outputStream, InputStream inputStream) {
        System.out.println("請輸入好友名字");
        String friend = ScannerUtil.input();
        boolean flag = true;
        while (flag) {
            System.out.println(username + ":");
            String msg = ScannerUtil.input();
            if ("bye".equals(msg)) {
                flag = false;
            }
            MsgUtils.writeMsg(outputStream,
                    new Message(MessageType.TO_CLIENT, msg, username, friend));
        }
    }

    //给服务器发消息
    private static void sendToServer(String username, OutputStream outputStream, InputStream inputStream) {
        System.out.println(username + ":");
        String msg = ScannerUtil.input();

        MsgUtils.writeMsg(outputStream,
                new Message(MessageType.TO_SERVER, msg, username));
        Optional<Message> message = MsgUtils.readMsg(inputStream);
        message.ifPresent(m -> System.out.println(m.getUsername() + ":" + m.getContent()));
    }

    private static void printOrder() {
        System.out.println("请选择功能:"
                + MessageType.TO_SERVER + ",给服务器发消息" +
                +MessageType.TO_CLIENT + ",给好友发消息 " +
                MessageType.TO_ALL + ",给群聊发消息 " +
                MessageType.RECEIVE + ",接受消息 ");
    }

    //登录的方法
    private static String login(OutputStream outputStream, InputStream inputStream) {
        System.out.println("请您输入用户名");
        String name = ScannerUtil.input();
        System.out.println("请您输入密码");
        String pwd = ScannerUtil.input();
        Message message = new Message();
        message.setType(MessageType.LOGIN);
        message.setUsername(name);
        message.setPassword(pwd);
        //发给服务器
        MsgUtils.writeMsg(outputStream, message);
        //接受来自服务端的消息
        Optional<Message> msg = MsgUtils.readMsg(inputStream);
        if (msg.isPresent() && Constant.SUCCESS.equals(msg.get().getContent())) {
            return name;
        }
        return null;
    }
}

  1. 服务端代码(server)
public class Server {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        try (
                ServerSocket serverSocket = new ServerSocket();
        ) { //绑定
            serverSocket.bind(new InetSocketAddress(8848));
            System.out.println("服务器开启----8848 port");
            while (true) {
                //监听
                Socket socket = serverSocket.accept();
                new ServerThread(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  1. 保存一个消息对象实体类 实现了 Serializable 可以进行序列化
public class Message  implements Serializable {

//序列化ID
    private static final long serialVersionUID = -9016687226866473553L;
    private Integer type;

    private String content;

    //用户名和密码
    private String username;
    private String password;


    private String friendUserName;



    public Message() {
    }

    public Message( Integer type,String content, String username) {
        this.type = type;
        this.content = content;
        this.username = username;
    }
    public Message( String username, String password,Integer type) {
        this.type = type;
        this.username = username;
        this.password = password;
    }

    public Message(Integer type, String content, String username,  String friendUserName) {
        this.type = type;
        this.content = content;
        this.username = username;
        this.friendUserName = friendUserName;
    }

    public Integer getType() {
        return type;
    }

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

    public String getContent() {
        return content;
    }

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

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

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

    public String getFriendUserName() {
        return friendUserName;
    }

    public void setFriendUserName(String friendUserName) {
        this.friendUserName = friendUserName;
    }

    @Override
    public String toString() {
        return "Message{" +
                "type=" + type +
                ", content='" + content + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", friendUserName='" + friendUserName + '\'' +
                '}';
    }
}

  1. 多线程的server端 用于多个用户
public class ServerThread extends Thread {

    private Socket socket;
    public ServerThread(){}
    public ServerThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {

        try (
                InputStream inputStream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream();
        ) {
            while (true) {
                Optional<Message> message = MsgUtils.readMsg(inputStream);
                if (message.isPresent()) {
                    Message msg = message.get();
                    switch (msg.getType()) {
                        case MessageType.TO_SERVER:
                            sendToClient(inputStream, outputStream, msg);
                            break;
                        case MessageType.TO_CLIENT:
                            SendToTarget(msg);
                            break;
                        case MessageType.TO_ALL:
                            SendToAll(msg);
                            break;
                        case MessageType.LOGIN:
                            loginHandler( outputStream, msg, socket);
                            break;
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

    private  void sendToClient(InputStream inputStream, OutputStream outputStream, Message message) {
        System.out.println(message.getUsername() + ":" + message.getContent());
        MsgUtils.writeMsg(outputStream, new Message(MessageType.TO_SERVER, Constant.OK, Constant.SERVER_NAME));
    }

    //給目標用戶發消息
    private  void SendToTarget( Message message) {
      //找到對應的socket
        Socket socket = Constant.USERS.get(message.getFriendUserName());
        try {
            MsgUtils.writeMsg(socket.getOutputStream(),message);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private  void SendToAll( Message message) {
        //遍歷所有在綫用戶 拿到他們的socket
    for (Map.Entry<String,Socket>entry:Constant.USERS.entrySet()){
        try {
            MsgUtils.writeMsg(entry.getValue().getOutputStream(),message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    }

    private static void loginHandler(OutputStream outputStream, Message message, Socket socket) {

        if (Constant.USERS.containsKey(message.getUsername())){
            MsgUtils.writeMsg(outputStream, new Message(MessageType.FROM_SERVER, Constant.FAIL, Constant.SERVER_NAME));
            return;
        }
        //判断登陆的逻辑
        if (message.getUsername() == null || !Constant.DEFAULT_PASSWORD.equals(message.getPassword())) {
            MsgUtils.writeMsg(outputStream, new Message(MessageType.FROM_SERVER, Constant.FAIL, Constant.SERVER_NAME));
        } else {

            //登陆成功 放入在线用户map中
            Constant.USERS.put(message.getUsername(), socket);
            System.out.println(message.getUsername() + "--> 登陆成功");
            MsgUtils.writeMsg(outputStream, new Message(MessageType.FROM_SERVER, Constant.SUCCESS, Constant.SERVER_NAME));

        }
    }
}

实现效果

登录

在这里插入图片描述

;