Java基础 03即时聊天案例简易版 164难

本文介绍了一种基于TCP协议实现多客户端间通信的方案,详细解释了服务器如何作为中转站处理客户端间的通信请求,包括消息类型定义、消息包设计、服务器端与客户端的代码实现,以及如何启动和管理客户端线程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多客户端之间的通信
服务器可以与多个客户端实现通信了,那我们真正的目的是要实现多个客户端之间的通信,使用TCP协议实现的方案是:

客户端的数据包通过服务器中转,发送到另一个客户端,如下图所示:
在这里插入图片描述
在这里插入图片描述
这个数据包 包含的内容
在这里插入图片描述
客户端A要发送消息给客户端B
首先发送给服务器中处理客户端A的线程A1
A1检索到A要发送给B 所以A1联系B1 通过B1回传给客户端B 完成通信

然后吧 这个服务器中的线程A1 B1就有了联系 所以不能再单独放了
把他们统一的管理起来 用 集合 的方式 Vector
在这里插入图片描述
MessageType

package com.vince.communication;

/**
 * Created by vince on 2017/6/7.
 */
public final class MessageType {
    public static final int TYPE_LOGIN = 0x1;//登录消息类型
    public static final int TYPE_SEND = 0x2;//发送消息的类型
}

消息包 Message
一会我们要发送这个消息对象 怎么发送 这里待会我们使用流发送 所以对象要序列化

package com.vince.communication;

import java.io.Serializable;

/**
 * Created by vince on 2017/6/7.
 * 消息包
 */
public class Message implements Serializable{
    private String from;//发送者
    private String to;//接收者
    private int type;//消息类型
    private String info;//消息

    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 int getType() {
        return type;
    }

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

    public String getInfo() {
        return info;
    }

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

    public Message() {
    }

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

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

服务器端

package com.vince.communication;

import javax.security.sasl.SaslClient;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by vince on 2017/6/7.
 * 服务器端
 */
public class Server {

    public static void main(String[] args) {
        //保存客户端处理的线程
        Vector<UserThread> vector = new Vector<>();
        ExecutorService es = Executors.newFixedThreadPool(5);
        //创建服务器端的Socket
        try {
            ServerSocket server = new ServerSocket(8888);
            System.out.println("服务器已启动,正在等待连接...");
            while(true){
                Socket socket = server.accept();
                UserThread user = new UserThread(socket,vector);
                es.execute(user);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

/**
 * 客户端处理的线程
 */
class UserThread implements Runnable{
    private String name;//客户端的用户名称(唯一)
    private Socket socket;
    private Vector<UserThread> vector;//客户端处理线程的集合
    private ObjectInputStream ois;
    private ObjectOutputStream oos;
    private boolean flag = true;

    public UserThread(Socket socket,Vector<UserThread> vector){
        this.socket = socket;
        this.vector = vector;
        vector.add(this);
    }
    @Override
    public void run() {
        try{
            System.out.println("客户端"+socket.getInetAddress().getHostAddress()+"已连接");
            ois = new ObjectInputStream(socket.getInputStream());
            oos = new ObjectOutputStream(socket.getOutputStream());

            while (flag){
                //读取消息对象
                Message msg  = (Message)ois.readObject();
                int type = msg.getType();
                switch (type){
                    case MessageType.TYPE_SEND:
                        String to = msg.getTo();
                        UserThread ut;
                        int size = vector.size();
                        for (int i = 0; i < size; i++) {
                            ut = vector.get(i);
                            if (to.equals(ut.name) && ut!=this){
                                ut.oos.writeObject(msg);
                                break;
                            }
                        }
                        break;
                    case MessageType.TYPE_LOGIN:
                        name = msg.getFrom();
                        msg.setInfo("欢迎你:");
                        oos.writeObject(msg);
                        break;
                }
            }
            ois.close();
            oos.close();
        }catch (IOException | ClassNotFoundException e){
            e.printStackTrace();
        }
    }
}

客户端

package com.vince.communication;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by vince on 2017/6/7.
 */
public class Client {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        ExecutorService es = Executors.newSingleThreadExecutor();
        try {
            Socket socket = new Socket("localhost", 8888);
            System.out.println("服务器连接成功");
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            //向服务器发送登录信息
            System.out.println("请输入名称:");
            String name = input.nextLine();
            Message msg = new Message(name, null, MessageType.TYPE_LOGIN, null);
            oos.writeObject(msg);
            msg = (Message) ois.readObject();
            System.out.println(msg.getInfo() + msg.getFrom());

            //启动读取消息的线程
            es.execute(new ReadInfoThread(ois));

            //使用主线程来实现发送消息
            boolean flag = true;
            while(flag){
                msg = new Message();
                System.out.println("To:");
                msg.setTo(input.nextLine());
                msg.setFrom(name);
                msg.setType(MessageType.TYPE_SEND);
                System.out.println("Info:");
                msg.setInfo(input.nextLine());
                oos.writeObject(msg);
            }

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

//读取消息
class ReadInfoThread implements Runnable {
    private ObjectInputStream in;

    public ReadInfoThread(ObjectInputStream in){
        this.in = in;
    }
    private boolean flag = true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        try {
            while (flag) {

                Message message = (Message) in.readObject();
                System.out.println("[" + message.getFrom() + "]对我说:" + message.getInfo());

            }
            if(in!=null){
                in.close();
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}


先启动服务器
在这里插入图片描述
再启动两个客户端
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第一个客户端 起名字·bin
在这里插入图片描述
第二个客户端叫vince
在这里插入图片描述
第一个客户端 bin 给vince发消息
在这里插入图片描述
说 good good study day day up
在这里插入图片描述
然后第二个客户端vince收到消息
在这里插入图片描述
然后给bin回消息
在这里插入图片描述
在这里插入图片描述
bin就收到了
在这里插入图片描述

启动客户端后 向服务器发送登录信息
第二步 启动接收线程 专门用来接收消息的线程
接下来使用主线程实现循环发送消息的过程

对服务器来说 服务器启动后 要读取

使用Vector来保存服务器当中的所有的用户线程任务Runnable(UserThread线程 )

每对应一个socket 即客户端的连接 就启动一个UserThread线程 每次都添加到Vector里面去
(这个添加的方法是 把对象vector添加到UserThread里面去 所以在构造方法 把UserThread自己添加进Vector里面)

然后开始启动线程 es.execute(user);

然后启动线程的第一步 run方法里面的
第一步 先把相关的流先构造完
然后就是循环 不断读取消息对象
读取的消息对象 取出类型进行判断
如果是登录信息 就返回一个欢迎你的消息
客户端就读取到欢迎信息 并输出出来 这是服务器端和客户端第一次交互

如果是发送消息的话
就取出要发送的对象
在集合Vector里面循环去找
找出所有的线程 判断要发送的人的名字跟线程保存的名字相不相同 (因为登陆的时候就把名字保存到线程了 所以他找的也是已经登陆的用户) 并且不是自己 毕竟这个循环会找到自己
找到了 就写到线程里面去 线程就会把消息发送出去

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值