TCP实现一个简易的多客户端聊天(Java实现)

使用TCP实现一个简易版的聊天系统,首先说一下思路
1.在服务器开多个线程来对应客户端的连接,服务器只做信息的中转工作画个图来理解
在这里插入图片描述
在做一对一客户端服务器的消息交换时,只需要通过socket来不断的交换信息即可,但是现在是实现客户端和客户端之间,所以我们所发送的消息不能是简单的字符串,封装一个数据的集合来表示数据,服务器端要识别数据的一些信息,所以必须要有以下几个属性:来向,去向,数据类型,数据内容。这个数据信息在传送的过程中是以对象流来传递的,如果想要真正实现局域网的功能,对象流是不行的,可以以json或者xml封装,这里只是为了练习不过多要求。
2.当客户端发送信息流到服务器端后,服务器端解析信息的属性,根据信息的类型和去向,在服务器端的多个线程中去找与去向对应的线程,然后通过这一连接将数据返回去,当然这个就要求服务器端对多个线程进行管理,我用到了集合Vector。客户端主要是一个死循环,在一直不停的发消息,同时为了能读到服务器端发来的消息,另起了一个线程不停的去接收服务器端的数据。

下面将代码贴上
第一个类,MessageType

final class MessageType {
    public static final int TYPE_LOGIN = 0X1;
    public static final int TYPE_SEND = 0X2;
}

这个类主要是用来区分后面的类Message,也就是我们所说的信息的类型,这里我们设定了两种类型,一种是登录信息,一种是发送信息,服务器端可以通过这个属性来区分信息的类型。

下来是Message类

class Message implements Serializable {
    private String from;
    private String to;
    private String info;
    private int message;

    public Message(String from, String to, String info, int message) {
        this.from = from;
        this.to = to;
        this.info = info;
        this.message = message;
    }

    @Override
    public String toString() {
        return "Message{" +
                "from='" + from + '\'' +
                ", to='" + to + '\'' +
                ", info='" + info + '\'' +
                ", message=" + message +
                '}';
    }

    public Message() {}
    public String getFrom() {
        return from;
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public String getTo() {
        return to;
    }

    public void setTo(String to) {
        this.to = to;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }

    public int getMessage() {
        return message;
    }

    public void setMessage(int message) {
        this.message = message;
    }
}

根据上面我们的分析,也就是信息所要携带的四种属性:来向,去向,信息内容,信息类型,再就是一些getset方法。

下面是服务器端

public class Server {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Vector<UserThread> userThreads = new Vector<>(5);
        ServerSocket serverSocket;
        try {
            serverSocket = new ServerSocket(6666);
            System.out.println("服务器启动成功");
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("客户端:"+socket.getRemoteSocketAddress()+"连接成功");
                executorService.execute(new UserThread(socket, userThreads));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class UserThread implements Runnable {
    private String name;
    private Message message;
    private Socket socket;
    private Vector<UserThread> userThreads;
    private ObjectInputStream objectInputStream = null;
    private ObjectOutputStream objectOutputStream = null;
    public UserThread( Socket socket,Vector<UserThread> userThreads) {
        this.socket = socket;
        this.userThreads = userThreads;
        userThreads.add(this);
    }

    @Override
    public void run() {
        try {
            objectInputStream = new ObjectInputStream(socket.getInputStream());
            objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        while(true) {
            try {
                message =(Message) objectInputStream.readObject();
                System.out.println(message);
            } catch (ClassNotFoundException|IOException e) {
                e.printStackTrace();
            }

            if(message.getMessage() == MessageType.TYPE_LOGIN) {
                this.name = message.getFrom();
                Message message1 = new Message("服务器", this.message.getFrom(), "登录成功", MessageType.TYPE_LOGIN);
                try {
                    objectOutputStream.writeObject(message1);
                    objectOutputStream.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(message.getMessage() == MessageType.TYPE_SEND) {
                String to = message.getTo();
                Iterator<UserThread> iterator = userThreads.iterator();
                while(iterator.hasNext()) {
                    UserThread next = iterator.next();
                    if(next.getName().equals(to)) {
                        try {
                            next.getObjectOutputStream().writeObject(message);
                            next.getObjectOutputStream().flush();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        break;
                    }
                }
            }
        }
    }

    public Vector<UserThread> getUserThreads() {
        return userThreads;
    }

    public void setUserThreads(Vector<UserThread> userThreads) {
        this.userThreads = userThreads;
    }

    public ObjectInputStream getObjectInputStream() {
        return objectInputStream;
    }

    public void setObjectInputStream(ObjectInputStream objectInputStream) {
        this.objectInputStream = objectInputStream;
    }

    public ObjectOutputStream getObjectOutputStream() {
        return objectOutputStream;
    }

    public void setObjectOutputStream(ObjectOutputStream objectOutputStream) {
        this.objectOutputStream = objectOutputStream;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }

    public Socket getSocket() {
        return socket;
    }

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

}

这块贴上了两个类,一个是服务器类,一个是服务器中用来对应客户端的线程类。
先来说服务器类:这块我用到了线程池来创建线程,给了5个线程,所以这个服务器端可以接受的客户端是5个,然后就是一个accept阻塞等待连接,直接给线程池提交任务。
再来说UserThread类:这个类是真正实现逻辑的地方,说一下大体思路,就是不断的读信息,如果读到的信息类型(MessageType)是登录信息,那么就证明第一次连接建立,如果读到的是发送信息,那么就根据信息的去向(to),在线程集合(Vector)中去找对应的线程,然后将Message转发给对应的线程。

下面是客户端类:

public class Clint {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Scanner scanner = new Scanner(System.in);
        Socket socket = null;
        String name;
        ObjectOutputStream objectOutputStream = null;
        try {
            System.out.println("请输入名称");
            name = scanner.nextLine();
            socket = new Socket("192.168.178.1",6666);
            objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            Message message = new Message();

            //启动一个线程用来读消息
            executorService.execute(new ReadThread(socket,name));

            //第一次登陆
            message.setFrom(name);
            message.setInfo("我要登陆");
            message.setMessage(MessageType.TYPE_LOGIN);
            message.setTo(null);
            objectOutputStream.writeObject(message);
            objectOutputStream.flush();

            //后续发消息
            while(true) {
                /**
                 * ????用原来的message就会一直发送旧的信息包,为什么
                 */
                Message MessageSend = new Message();
                MessageSend.setFrom(name);
                MessageSend.setMessage(MessageType.TYPE_SEND);
                System.out.println("请输入目标名称");
                MessageSend.setTo(scanner.nextLine());
                System.out.println("请输入消息:");
                MessageSend.setInfo(scanner.nextLine());
                objectOutputStream.writeObject(MessageSend);
                objectOutputStream.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                objectOutputStream.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
class ReadThread implements Runnable {
    Socket socket;
    String name;
    public ReadThread(Socket socket,String name) {
        this.socket = socket;
        this.name = name;
    }
    @Override
    public void run() {
        ObjectInputStream objectInputStream = null;
        try {
            objectInputStream = new ObjectInputStream(socket.getInputStream());
            while(true) {
                try {
                    Message message = (Message) objectInputStream.readObject();
                    if(message.getFrom() != name && message.getInfo()!=null) {
                        System.out.println("["+message.getFrom()+"] : "+"["+message.getInfo()+"]" );
                    } else {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                } catch (IOException|ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                objectInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端类实现逻辑很简单,主线程不断的给服务器端发消息, 另起一个线程不断读服务器发来的消息。

下来测试一下:
1.同时启动三个客户端和一个服务器端
在这里插入图片描述
2.输入客户端的名称,使之登录成功(这块可以继续加上功能:密码验证等)
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
服务器端一直在监听消息,现在状态是三个客户端都成功登录

在这里插入图片描述
2.现在用c给b和a分别发消息
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
功能是肯定实现了,不做过多的测试。

总结:这次做这个小练习有几点需要注意:
1.通过socket获取信息的时候不能放在线程的构造中,这样会阻塞服务器端。
2.通过对象流是不能完成局域网的聊天功能的,需要用josn和xml封装
3.代码中的问号,后续解决

猜你喜欢

转载自blog.csdn.net/weixin_42220532/article/details/90273582