Java小项目 - 聊天室

Java聊天室

已经实现功能:
  • 群聊
  • 私聊
  • 匿名发送消息
还没有实现的功能:
  • 只能实现匿名给别人发送消息,收到消息的人不能再给发回去
使用的技术:
  • Java多线程以及Java Socket编程
整体思路:

首先,要实现两个客户端之间的聊天,并不是一个客户端直接发送给另一个客户端,而是需要有一个服务器
比如现在是客户端A和客户端B聊天,首先A会将信息发送给服务器,其中信息并不只是聊天的信息,还应该包括要发送给谁等关键信息,例如:B:hi~ 这表示A要给B发送信息,发送的内容是hi~;
然后服务器会将信息整理一下,将其中的关键信息(比如:要聊天的对象)和A原本要发送给B的信息(如上文的:hi~)分开,然后在转发给相应的用户,具体如下图:
在这里插入图片描述
具体代码实现:

1、客户端代码:

package fei.MultiThreadChatRoom;

import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * 多线程聊天室的客户端要干的事:
 * 两个线程
 *      一个线程用来读取服务器发送的消息
 *      一个线程用来给服务器发送消息
 */

/**
 * 读取服务器发送的信息线程
 */
class ReadFromServer implements Runnable
{
    private Socket client;

    //通过构造方法传入当前客户端
    public ReadFromServer(Socket client) {
        this.client = client;
    }

    @Override
    public void run() {
        Scanner in;
        try {
            in = new Scanner(client.getInputStream());
            in.useDelimiter("\n");
            while (true)
            {
                if (in.hasNext())
                {
                    System.out.println(in.next());
                }
                //如果客户端关闭
                if (client.isClosed())
                {
                    System.out.println("此客户端关闭!");
                    break;
                }
            }
            in.close();
        } catch (IOException e) {
            System.out.println("读线程异常,错误为"+e);
        }
    }
}

/**
 * 将信息发送给客户端线程
 */
class SendToServer implements Runnable
{
    private Socket client;

    public SendToServer(Socket cilent) {
        this.client = cilent;
    }

    @Override
    public void run() {
        try {
            Scanner in = new Scanner(System.in);
            in.useDelimiter("\n");
            PrintStream out = new PrintStream(client.getOutputStream(), true, "UTF-8");
            while (true)
            {
                System.out.println("请输入要发送的信息...");
                String str = null;
                if (in.hasNext())
                {
                    str = in.nextLine().trim();
                    //发送消息
                    out.println(str);
                }
                //如果发送的消息是byebye,客户端关闭
                if (str.startsWith("byebye"))
                {
                    System.out.println("客户端关闭!");
                    in.close();
                    out.close();
                    client.close();
                    break;
                }
            }
        } catch (IOException e) {
            System.out.println("客户端写线程异常,错误为:"+e);
        }

    }
}

public class MultiThreadClient {
    public static void main(String[] args){
        //1、连接服务器
        Socket client = null;//端口号建议使用1000以后的,因为1000之前的大多数都已经被网络协议占用
        try {
            client = new Socket("127.0.0.1", 6666);
            //2、获取输入输出流
            new Thread(new ReadFromServer(client)).start();
            new Thread(new SendToServer(client)).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2、服务器端代码:

package com.bittech.MultiThreadChatRoom;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MultiThreadServer {
    //定义一个map用来存放已经注册过的客户端,因为是多线程,所以用ConcurrentHashMap
    private static Map<String, Socket> map = new ConcurrentHashMap();

    //定义一个内部类,用来具体处理每一个客户端的通信问题
    public static class Chat implements Runnable
    {
        private Socket client;

        //通过构造方法传入一个客户端对象
        public Chat(Socket client) {
            this.client = client;
        }

        @Override
        public void run() {
            try {
                //获取客户端输入
                Scanner in = new Scanner(client.getInputStream());
                String str ;
                while(true)
                {
                    if (in.hasNext())
                    {
                        str = in.nextLine();
                        System.out.println("G:-->"+str);
                        //windows下换行符为\r\n,在这里将\r\n换位\n,因为在Linux系统下换行符默认为\n
                        Pattern pattern = Pattern.compile("\r");
                        Matcher matcher = pattern.matcher(str);
                        str = matcher.replaceAll("");
                        //将从客户端接受的到的字符串处理好之后,开始处理客户端之间的通信问题
                        //如果字符串以userName:开头,比如:客户端:userName:xiaofeixia -->表示用户xiaofeixia正准备要注册
                        if (str.startsWith("userName:"))
                        {
                            //获取用户名
                            String userName = str.split(":")[1];
                            //调用注册的方法
                            registerUsers(userName, client);
                            continue;
                        }
                        //如果字符串以"G:"开头,表示群聊
                        //e.g: G:hi,大家在干嘛~~~
                        if (str.startsWith("G:"))
                        {
                            //获取聊天字符串
                            String strToEveryone = str.split(":")[1];
                            chatInGroup (strToEveryone, client);//----------------------------------------
                            continue;
                        }

                        /**
                         * 如果字符串以“P:”开头,表示私聊,其形式为: P:xianfeixia-hi,在干嘛呢~~~
                         * : 后面跟着的是用户名
                         * - 后面跟的是聊天内容
                         */
                        if (str.startsWith("P:"))
                        {
                            //获取要聊天的人的用户名
                            String userName = str.split(":")[1].split("-")[0];
                            //获取聊天内容
                            String strToOne = str.split(":")[1].split("-")[1];
                            privateChat(userName, strToOne);
                            continue;
                        }
                        /**
                         * 如果字符串以A:开头,表示匿名聊天,例如:A:xianfeixia-hi,在干嘛呢~~
                         */
                        if (str.startsWith("A:"))
                        {
                            //获取要聊天的人的用户名
                            String userName = str.split(":")[1].split("-")[0];
                            //获取聊天内容
                            String strToOne = str.split(":")[1].split("-")[1];
                            anonymousChat(userName, strToOne);
                            continue;
                        }
                        /**
                         * 用户退出
                         *  先找到对应的用户名,在将其从map中移除
                         */
                        if (str.contains("byebye"))
                        {
                            String userName = null;
                            for (String name : map.keySet())
                            {
                                if (map.get(name).equals(client))
                                {
                                    userName = name;
                                    break;
                                }
                            }
                            chatInGroup(userName+"退出群聊~~~", client);//---------------------------
                            System.out.println("userName+\"退出群聊~~~+\r\n+当前群聊人数为"+(map.size()-1));
                            map.remove(userName);
                            continue;
                        }
                        if (true)
                        {
                            PrintStream out = new PrintStream(client.getOutputStream(), true, "UTF-8");
                            out.println("系统提示:聊天格式错误,请重新输入!!!");
                            System.out.println(getName()+"聊天格式错误,请重新输入!!!");
                            continue;
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * 判断这个用户是否已经注册过了,如果已经注册过了,就不能再次注册
         * @param client 客户端Socket对象
         * @return 注册过了返回true,否则返回false
         */
        private boolean ifRegister(Socket client)
        {
            Set<Map.Entry<String, Socket>> entrySet = map.entrySet();
            for (Map.Entry<String, Socket> es : entrySet)
            {
                if(es.getValue().equals(client))
                {
                    System.out.println("此用户已经注册过了,不用再次注册^V^");
                    return true;
                }
            }
            return false;
        }

        /**
         * 用户注册
         * @param userName 用户名
         * @param client 客户端对象
         */
        private void registerUsers (String userName, Socket client)
        {
            if (ifRegister(client))
            {
                try {
                    PrintStream out = new PrintStream(client.getOutputStream(), true, "UTF-8");
                    out.println("此用户已经注册过了,不用再次注册^V^");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            else if (map.keySet().contains(userName))//如果用户名已经被注册
            {
                try {
                    PrintStream out = new PrintStream(client.getOutputStream(), true, "UTF-8");
                    out.println("用户名重复,请重新输入-_-");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            else
            {
                System.out.println(userName+"加入群聊");
                System.out.println("当前聊天人数为:"+(map.size()+1));
                //将信息存入map中
                map.put(userName, client);
                //获取输出流
                chatInGroup(userName+"加入群聊~~", client);
            }
        }

        /**
         * 群聊方法
         * @param strToEveryone 要群发的消息
         * @param client 客户端对象
         */
        private void chatInGroup (String strToEveryone, Socket client)
        {
            String userName = null;
            //取得map中所有Entry遍历发送群消息
            Set<Map.Entry<String, Socket>> mapEntry = map.entrySet();
            //取得当前发消息的人的用户名
            for (Map.Entry<String, Socket> getName : mapEntry)
            {
                if (getName.getValue().equals(client))
                {
                    userName = getName.getKey();
                }
            }
            //给每一个人转发消息,自己发出去的消息不用给自己转发
            for (Map.Entry<String, Socket> entry : mapEntry)
            {
                if (entry.getKey().equals(userName) == false)
                {
                    Socket socket = entry.getValue();
                    //取得每个客户端的输出流
                    try {
                        PrintStream out = new PrintStream(socket.getOutputStream(), true, "UTF-8");
                        out.println(userName+"(群消息):"+strToEveryone);

                    } catch (IOException e) {
                        System.out.println("群聊异常,错误为"+e);
                    }
                }
            }
        }

        /**
         * 获取用户名
         * @return 返回用户名
         */
        private String getName ()
        {
            String name = null;
            //取得map中所有Entry遍历发送群消息
            Set<Map.Entry<String, Socket>> mapEntry = map.entrySet();
            //取得当前发消息的人的用户名
            for (Map.Entry<String, Socket> getName : mapEntry)
            {
                if (getName.getValue().equals(client))
                {
                    name = getName.getKey();
                }
            }
            return name;
        }

        /**
         * 给名为mane的用户发送一条消息
         * @param name 用户名
         * @param msg 发送内容
         **/
        private void sendToName(String name, String msg)
        {
            Socket s = map.get(name);
            PrintStream o = null;
            try {
                o = new PrintStream(s.getOutputStream(), true, "UTF-8");
                o.println(msg);
            } catch (IOException e) {
                System.out.println("私聊提示用户不存在异常,错误为"+e);
            }
        }

        /**
         * 私聊
         * @param userName 用户名
         * @param strToOne 要发送的信息
         */
        private void privateChat (String userName, String strToOne)
        {
            String name = getName();
            //取得要私聊的人的输出流
            Socket socket = map.get(userName);
            if (socket != null)
            {
                try {
                    PrintStream out = new PrintStream(socket.getOutputStream(), true, "UTF-8");
                    out.println(name+"(私聊消息):"+strToOne);
                } catch (IOException e) {
                    System.out.println("私聊异常,错误为"+e);
                }
            }
            else
            {
                sendToName(name, "你输入的用户不存在,请重新输入~~");
                System.out.println("该用户不存在!!!");
            }
        }

        /**
         * 匿名聊天
         * @param userName 用户名
         * @param strToOne 要发送的信息
         */
        private void anonymousChat (String userName, String strToOne)
        {
            Socket socket = map.get(userName);
            try {
                PrintStream out = new PrintStream(socket.getOutputStream(), true, "UTF-8");
                out.println("有人说:"+strToOne);
            } catch (IOException e) {
                System.out.println("匿名聊天异常,错误为"+e);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        ServerSocket serverSocket = new ServerSocket(6666);
        for (int i=0; i<50; i++)
        {
            System.out.println("等待客户端连接...");
            Socket client = serverSocket.accept();
            System.out.println("有新的客户连接,端口号为:"+client.getPort());
            executorService.submit(new Chat(client));
        }
        executorService.shutdown();
        serverSocket.close();
    }
}

注:由于这个不是界面操作,所以群聊私聊都要按正确的格式来,格式如下:
聊天之前先注册用户信息,格式如下:

  • userName:用户名(如:userName:feifei)
    聊天格式:
  • 群聊:G:消息内容(如:G:大家好~~)
  • 私聊:P:私聊对象用户名-消息内容(如:P:feifei-好久不见,最近好么?)
  • 匿名:A:聊天对象用户名-消息内容(如:A:feifei-好久不见,我想你了!)
    (其中的冒号是英文的)

测试结果:
因为我用的是创建固定大小的线程池,最多可以允许有50个客户端线程,所以我测试了创建五十个客户端线程,通过测试,并且可以正常聊天:
在这里插入图片描述
群聊:
![在这里插入图片描述](https://img-在这里插入图片描述
私聊:
在这里插入图片描述
匿名发送消息:
在这里插入图片描述
我也是刚学了JavaSE不久,所以写出来的代码看起来还不是很好,如果有不对的地方,欢迎大家指出…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值