TCP实现聊天室(五)

简介: TCP实现聊天室(五)

通过TCP 通信,实现简单的聊天室功能,包括群发消息和私发消息。 只发送普通的字符串信息, 如果想上传文件等,可以按照上一章节的内容,进行改写。


私发消息,有一个固定的格式, @私发的人:私发的消息


与上一章节中的 多线程 echo 部分内容差不多。


一. TCP 实现聊天室功能


一.一 关闭工具类 CloseUtils


public class CloseUtils {
    /**
     * 关闭
     * @param closeables
     */
    public static void close(Closeable...closeables){
        for(Closeable closeable:closeables){
            if(null!=closeable){
                try {
                    closeable.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


一.二 多线程发送 Send


//发送者
public class Send implements  Runnable{
    private Socket client;
    private DataOutputStream dataOutputStream;
    private BufferedReader bufferedReader;
    private boolean isRunning;
    private String nickName;
    /**
     * 接收过来昵称
     * @param client
     * @param nickName
     */
    public Send(Socket client,String nickName){
        this.client=client;
        this.isRunning=true;
        this.nickName=nickName;
        try {
            this.dataOutputStream=new DataOutputStream(client.getOutputStream());
            this.bufferedReader=new BufferedReader(new InputStreamReader(System.in));
            //把昵称发送过去, 提示谁谁谁 进来了
            sendMsg(nickName);
        } catch (IOException e) {
            e.printStackTrace();
            isRunning=false;
        }
    }
    @Override
    public void run() {
        while(this.isRunning){
            try {
                //接收数据
                String content=bufferedReader.readLine();
                if(content!=null&&!"".equals(content.trim())){
                    //发送数据
                    sendMsg(content);
                    if("bye".equalsIgnoreCase(content)||"quit".equalsIgnoreCase(content)){
                        stop();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public void stop(){
        this.isRunning=false;
    }
    public void sendMsg(String msg){
        try {
            dataOutputStream.writeUTF(msg);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


一.三 多线程接收 Receiver


//接收者
public class Receiver implements  Runnable{
    private Socket client;
    private DataInputStream dataInputStream;
    private boolean isRunning;
    private String nickName;
    /**
     * 接收消息
     * @param client
     * @param nickName
     */
    public Receiver(Socket client,String nickName){
        this.client=client;
        this.isRunning=true;
        this.nickName=nickName;
        try {
            this.dataInputStream=new DataInputStream(client.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
            this.isRunning=false;
            // CloseUtils.close(client);
        }
    }
    @Override
    public void run() {
        while(this.isRunning){
            String content=readMsg();
            System.out.println(content);
            if("欢迎下次再来".equalsIgnoreCase(content)){
                stop();
            }
        }
        try {
            CloseUtils.close(this.dataInputStream,client.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void stop(){
        this.isRunning=false;
    }
    public String readMsg(){
        String content="";
        try {
            content= dataInputStream.readUTF();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return content;
    }
}


一.四 客户端 Client


public class Client {
    public static void main(String[] args) {
        System.out.println("-------------正在连接服务器-----------");
        try {
            Socket socket=new Socket("localhost",9999);
            BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in));
            System.out.println("请输入你的昵称:");
            String nickName=bufferedReader.readLine();
            //将昵称传入
            new Thread(new Send(socket,nickName)).start();
            new Thread(new Receiver(socket,nickName)).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


一.五 服务器端 Server


public class Server {
    //客户多线程 
    static class Channel implements  Runnable{
        private Socket client;
        private DataInputStream dataInputStream;
        private DataOutputStream dataOutputStream;
        private boolean isRunning;
        private String nickName;
        public Channel(Socket client){
            this.client=client;
            this.isRunning=true;
            try {
                this.dataInputStream=new DataInputStream(client.getInputStream());
                this.dataOutputStream=new DataOutputStream(client.getOutputStream());
                //昵称是读取的
                this.nickName=readMsg();
                //自己控制台展示的
                sendMsg("欢迎您的到来");
                //发送给大家的,群发的系统消息
                sendOtherMsg("大家欢迎"+nickName+"来到聊天室",true);
            } catch (IOException e) {
                e.printStackTrace();
                CloseUtils.close(dataInputStream,dataOutputStream);
            }
        }
        @Override
        public void run() {
            while(this.isRunning){
                String content=readMsg();
                //对接收的消息进行相应的判断处理。
                String responseData="";
                if("bye".equalsIgnoreCase(content)||"quit".equalsIgnoreCase(content)){
                    sendMsg("欢迎下次再来");
                    sendOtherMsg(nickName+"离开了聊天室",true);
                    this.isRunning=false;
                    //将当前对象移除
                    clientList.remove(this);
                }else{
                    responseData=nickName+"对大家说:"+content;
                    //私发是 @私发的人:  消息
                    if(content.startsWith("@")){
                        sendOne(content);
                    }else{
                        sendOtherMsg(responseData,false);
                    }
                }
            }
        }
        /**
         * 发送给单独的一个人
         * @param msg
         */
        public void sendOne(String msg){
                int index=msg.indexOf(":");
                if(index<0){
                    return ;
                }
                //发送给谁
                String toName=msg.substring(1,index);
                //发送的内容
                String content=msg.substring(index+1);
                for(Channel channel:clientList){
                    //找到了这个人
                    if(toName.equalsIgnoreCase(channel.nickName)){
                        channel.sendMsg(nickName+"悄悄地对你说:"+content);
                        break;
                    }
                }
        }
        //发送给其他人,不包括他自己。
        public void sendOtherMsg(String msg,boolean isSysInfo){
            //看是否是系统消息
            if(isSysInfo){
                //是系统消息
                for(Channel channel:clientList){
                    //是当前对象
                    if(channel==this){
                        continue;
                    }
                    channel.sendMsg(msg);
                }
            }else{
                //普通群发消息
                for(Channel channel:clientList){
                    channel.sendMsg(msg);
                }
            }
        }
        public void sendMsg(String msg){
            try {
                dataOutputStream.writeUTF(msg);
            } catch (IOException e) {
                // e.printStackTrace();
                CloseUtils.close(dataInputStream,dataOutputStream);
            }
        }
        public String readMsg(){
            String content="";
            try {
                content= dataInputStream.readUTF();
            } catch (IOException e) {
                CloseUtils.close(dataInputStream,dataOutputStream);
                //e.printStackTrace();
            }
            return content;
        }
    }
    //定义集合,在高并发时用 CopyOnWriteArrayList, 与以前的ArrayList 非并发 差不多
    private static CopyOnWriteArrayList<Channel> clientList=new CopyOnWriteArrayList<Channel>();
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket=new ServerSocket(9999);
            System.out.println("********服务器开启************");
            while(true){
                Socket client=serverSocket.accept();//多线程运行
                Channel channel=new Channel(client);
                //添加到集合里面
                clientList.add(channel);
                //启动线程
                new Thread(channel).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


一.六 运行测试


重启服务器,依次运行三个客户端,分别输入昵称为 a,b,c


那么此时, a客户端控制台打印输出


20200629111555988.png


b客户端打印输出


20200629111600431.png


c客户端打印输出


20200629111604882.png


不会接收到加入之前的数据。


此时,a 输入一句话,


20200629111610355.png


那么 b,c 都能够收到这句话。


20200629111618301.png


20200629111625535.png


b,c 也可以回应


20200629111632110.png


可以进行私发, 如 私发给 c


20200629111638969.png


那么这个时候, b应该看不到这个消息, c可以看到


b控制台


20200629111645733.png


c控制台


20200629111655785.png


如果c 客户 输入 bye, 就可以退出聊天室了


c控制台


20200629111701314.png


那么这个时候 a就可以接收到系统提示的 c离开聊天室的消息了


20200629111706943.png


这样,就实现了简单的聊天室的群发和私发功能 。


其实,关于聊天室,可以使用 WebSocket 来实现。


可以看老蝴蝶写的 WebSocket 内容。 WebSocket的了解(一)


谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!

相关文章
|
设计模式 网络协议 Java
java tcp多人聊天室
java tcp多人聊天室
203 0
java tcp多人聊天室
|
消息中间件 设计模式 网络协议
Android | TCP的C(Java|Android)/S(Java)通信实战经典聊天室案例(文末附本案例代码实现概述、观察者模式实现小结)
Android | TCP的C(Java|Android)/S(Java)通信实战经典聊天室案例(文末附本案例代码实现概述、观察者模式实现小结)
|
移动开发 网络协议 Windows
与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室
原文:与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室 [索引页][源码下载] 与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室 作者:webabcd介绍与众不同 windows phone 7.
965 0
|
6月前
|
机器学习/深度学习 人工智能 网络协议
TCP/IP五层(或四层)模型,IP和TCP到底在哪层?
TCP/IP五层(或四层)模型,IP和TCP到底在哪层?
105 4
|
监控 网络协议 网络架构
IP协议【图解TCP/IP(笔记九)】
IP协议【图解TCP/IP(笔记九)】
143 0
|
域名解析 网络协议
IP协议, TCP协议 和DNS 服务分别是干什么的?
大家好,我是阿萨。昨天讲解了网络四层协议[TCP/IP协议族分为哪4层?]今天我们学习下IP 协议, TCP 协议和DNS 协议分别是干什么的。
293 0
IP协议, TCP协议 和DNS 服务分别是干什么的?
|
网络协议
ACK的累加规则-wireshark抓包分析-不包含tcp头部、ip头部、数据链路层头部等。
ACK的累加规则-wireshark抓包分析-不包含tcp头部、ip头部、数据链路层头部等。
ACK的累加规则-wireshark抓包分析-不包含tcp头部、ip头部、数据链路层头部等。
|
网络协议 网络架构
六、TCP/IP模型 和 5层参考模型
六、TCP/IP模型 和 5层参考模型
六、TCP/IP模型 和 5层参考模型
|
网络协议
TCP/IP协议族有哪些?
大家好,我是阿萨。昨天我们学习了[URI 和URL 的区别是什么?]了解了URI 和URL的区别。 学习HTTP, 绕不开TCP/IP,那么TCP/IP 协议族分为哪4层?
310 0
TCP/IP协议族有哪些?
|
网络协议 网络性能优化 网络安全
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(2)
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(2)
网络协议报文理解刨析篇二(再谈Http和Https), 加上TCP/UDP/IP协议分析(理解着学习), 面试官都惊讶你对网络的见解(2)