java Socket 简单实现客户端与服务器间通信(仿聊天室)

2023-11-18

java Socket TCP协议简单实现客户端与服务器间的通信

执行效果:

  1. 启动服务器和3个客户端
    服务器端
    在这里插入图片描述
  2. 进行群聊和私聊
    1号客户端

2号客户端

执行过程:

  • 服务端
    首先创建服务器套接字ServerSocket对象并绑定端口(启动服务器),然后ServerSocket调用accept方法阻塞式等待接收一个客户端的请求,每接收一个请求就返回一个该客户端的Socket对象(有了Socket对象就可以获得该客户端的输入输出流然后进行消息传送),开启 新一个线程去处理该客户端的消息,比如客户端上线消息,客户端下线消息,群发消息,私聊消息再进行相应的处理。

注意:Socket对象获得该客户端的输入输出流也是阻塞式的,比如readline方法读取 到“\n”才会算读取结束否则程序一直阻塞在那,但read方法却不知道什么时候读取结束因为它不知道发送方什么时候发送消息完毕,除非发送方关闭输出流,然后read读取到-1它才会读取结束,但发送方关闭输出流后无法再向接收方发送消息了,这里没有涉及但就接收方什么时候读取消息结束是个值得考虑的问题。

  • 客户端
    首先根据服务器地址和端口创建客户端套接字Socket对象,同上有了Socket对象就可以获得对服务器的输入输出流然后进行消息读写,然后再创建一个线程监听处理服务器的消息,之后获取用户输入再发送给服务器即可(这里输入exit消息代表退出服务器,输入@xxx:message格式消息代表私聊发送message给xxx客户端,其他消息统一为群聊消息,服务器接收后直接转发给其他所有客户端)

服务端代码

public class Server {
    private int port = 5210;
    private ServerSocket server_socket;
    private Map<String,Socket> clients;   //存放所有访问服务器的客户端和其用户名

    public Server() throws Exception {
        //1-初始化
        server_socket = new ServerSocket(this.port);   //创建服务器
        clients = new HashMap<>();

        //2-每次接收一个客户端请求连接时都启用一个线程处理
        while(true) {
            //accept方法一直阻塞直到接收到一个客户端的请求 并返回该客户端的套接字socket
            Socket client_socket = server_socket.accept();
    		//开启新线程处理客户端的请求
            new HandleClientThread(client_socket).start();
        }
    }

    /** ----------------------------------------------------------------------------------------------------
     *      启用一个线程处理一个客户端的请求
     */
    private class HandleClientThread extends Thread{
        private Socket client_socket;    //
        private BufferedReader server_in;
        private PrintWriter server_out;
        private String client_Name;

        public HandleClientThread(Socket client_socket) {
            try {
                //初始化
                this.client_socket = client_socket;
                server_in = new BufferedReader(new InputStreamReader(this.client_socket.getInputStream()));
                server_out = new PrintWriter(this.client_socket.getOutputStream(),true);
                
                //通知刚连上的客户端输入用户名
                server_out.println("您已成功连接上聊天服务器!请输入你的用户名");

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


        //处理客户端消息
        public void run(){
            try {
                int falg = 0;     //判断该客户端是否第一次访问
                String fromClientData;

                //循环接收该客户端发送的数据
                while((fromClientData = server_in.readLine()) != null){
                    //该客户端请求关闭服务器的连接
                    if("exit".equals(fromClientData)){
                        Server.this.offline(this.client_Name);
                        break;
                    }

                    //判断该客户端是否第一次访问
                    if(falg++ == 0){
                        this.client_Name = fromClientData;
                        clients.put(this.client_Name,this.client_socket);  
                        sendtoAllClient("欢迎:"+this.client_Name+"进入聊天室");
                        Server.this.showUserList();
                        continue;
                    }

                    //处理私聊    格式  @接收客户端的名字:对其说的话
                    if(fromClientData.startsWith("@")){
                        String receiveName = fromClientData.substring(1,fromClientData.indexOf(":"));
                        String message = fromClientData.substring(fromClientData.indexOf(":")+1,fromClientData.length());

                        sendtoUser(this.client_Name,receiveName,message);

                    }else {
                        //处理群发群发
                        sendtoAllClient(this.client_Name+"说:"+fromClientData);
                    }

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

    //显示所有用户列表
    private synchronized  void showUserList(){
        System.out.println("\n*****用户列表****");
        System.out.println("在线人数:"+clients.size());
        for (Map.Entry<String,Socket> user:clients.entrySet()){
            System.out.println("---"+user.getKey());
        }
    }

    //广播消息message给所有客户端
    private synchronized  void sendtoAllClient(String message){
        try {
            //获取所有客户端套接字socket的输出流
            for (Map.Entry<String,Socket> user:clients.entrySet()) {
               PrintWriter server_out = new PrintWriter(user.getValue().getOutputStream(),true);
                server_out.println(message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //转发消息给某个特定的客户端  
    private synchronized void sendtoUser(String senduser,String receiveuser,String message){
        try {
            PrintWriter out = new PrintWriter( clients.get(receiveuser).getOutputStream(),true);
            out.println(senduser+"对你说:"+message);

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


    //某个客户端退出服务器
    private synchronized void offline(String username){
            clients.remove(username);
            if(username !=  null)
            sendtoAllClient(username+"已下线!");
            Server.this.showUserList();
    }

    public static void main(String[] args) throws Exception {
        new Server();

    }
}

客户端

public class Client {
    private String ip_adress = "127.0.0.1";
    private final int port = 5210;

    private PrintWriter client_out;   //发送消息给服务器的输出流
    private BufferedReader client_in;  //接收服务器消息的输入流
    private Socket client_socket;   //客户端套接字

    public  Client() throws IOException {
        //1-初始化
        client_socket = new Socket(ip_adress,port); //根据ip,端口连接服务器
        client_in = new BufferedReader(new InputStreamReader(client_socket.getInputStream()));
        client_out = new PrintWriter(client_socket.getOutputStream(),true);
        
        //2-开启新线程处理监听服务器端发来的消息
        new ClientThread().start();

        //3-客户端循环发送群聊,私聊消息
        while(true){
            //获取客户端的输入
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            String input = br.readLine();
            client_out.println(input);  //发送消息给服务器

            //4-客户端退出服务器
            if(input.equals("exit")){
                client_out.close();
                client_in.close();
                client_socket.close();
                System.exit(0);
            }
        }
    }

   //-----------------------------------------------------------------
    /**
     *          监听服务器端发来的消息
     */
    private class ClientThread extends Thread{
        public void run(){
            try {
            		//接收服务器消息
                    String fromServer_data;
                    while((fromServer_data=client_in.readLine()) != null){
                         System.out.println(fromServer_data);
                    }
            } catch (IOException e) {
            }
        }
    }

    
	 //启动客户端
    public static void main(String[] args) throws IOException {
            new Client();  
    }
}

打赏

如果觉得文章有用,你可鼓励下作者

在这里插入图片描述


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java Socket 简单实现客户端与服务器间通信(仿聊天室) 的相关文章

  • 如何像java中的make一样程序化生成塞尔达传说

    我将如何用java制作程序生成的地图 游戏本身就像塞尔达传说是程序生成的 有帮助吗 不久前的 塞尔达传说 地图使用等距平铺视图 您需要做的第一件事是将等距图块集加载到您的程序中 我确信您可以找到塞尔达图块集 然后 您需要决定如何按程序生成地
  • 同步不经常更新的哈希图的最佳方式

    我有一个在应用程序中使用的 HashMap 数据是在应用程序初始加载期间从数据库填充的 然后它始终只是读取并且从不更新 会有多个线程不断地读取数据 由于数据永远不会更新 因此我们目前不使用任何同步 仅使用 HashMap 我们现在定义的方式
  • ModelMapper:匹配多个源属性层次结构

    我无法解决 modelMapper 错误 您知道问题出在哪里吗 注意 鉴于 java sql Time 没有无参构造函数 我没有找到比编写转换器更好的方法 org modelmapper ConfigurationException Mod
  • 是否可以使用 Kafka Streams 访问消息头?

    随着添加Headers http apache spinellicreations com kafka 0 11 0 0 javadoc org apache kafka common header Header html到记录 生产者记录
  • Android 2.x 天城文 unicode 问题

    我正在尝试使用以下代码支持 android 2 x 的梵文字体 即使 android 2 x 无法渲染梵文字体 除了 raswa 和 dirga 存在一些问题之外 代码工作正常 是否有可能在 android 2 x 中获得正确的梵文表示形式
  • Spring MVC 3 中的表单提交 - 说明

    我在理解 Spring 3 MVC 中的表单提交如何工作时遇到问题 我想做的是创建一个控制器 它将获取用户的名字并将其显示给他 不知怎的 我已经做到了 但我不太明白它是如何工作的 所以 我有一个看起来像这样的表格
  • springdoc-openapi:如何添加POST请求的示例?

    Controller有以下方法 ApiResponses value ApiResponse responseCode 200 GetMapping value API URI PREFIX PRODUCTS URI produces Me
  • 为什么 Eclipse 要求我在 java 代码中设置(任意)括号?

    我目前正在尝试弄清楚如何使用 Eclipse 在 java 中对 Escape 模型进行编程 我对 Escape 和 Eclipse 很陌生 自从我用 java 编程以来已经有一段时间了 所以如果这是一个愚蠢的问题 请原谅 基本上 我一直被
  • S3 Java 客户端经常失败,并出现“内容长度分隔消息正文过早结束”或“java.net.SocketException 套接字已关闭”

    我有一个在 S3 上做很多工作的应用程序 主要是从中下载文件 我看到很多此类错误 我想知道这是否是我的代码中的问题 或者服务是否真的像这样不可靠 我用来从 S3 对象流读取的代码如下 public static final void wri
  • 如何保护 Hibernate QBE 查询的安全

    目前 我知道四种使用 hibernate 进行事务处理的方式 使用对象 使用HQL 使用特定于数据库的 SQL 使用标准 QBE 好吧 关于它们对注射的抵抗力有多强 我认为是这些 如果我错了 请纠正我 安全 因为内部 SQL 调用是参数化的
  • alter Windows 文件中的 krb5.ini 文件哪里去了?

    至少在 Windows XP 之前 如果您加入具有 Kerberos 领域特定设置的域 就会有一个 krb5 ini 文件 从 Vista 或 7 开始 不再需要此文件 我试图找到有关此的更多信息 但陷入困境 krb5 ini 文件中的设置
  • 通过两个标准比较两个对象[重复]

    这个问题在这里已经有答案了 我有一个包含布尔字段的类用户 我想对用户列表进行排序 我希望布尔字段等于 true 的用户位于列表的顶部 并且我想按名称对它们进行排序 这是我的课程 public class User int id String
  • 查看两个对象是否具有相同的类型

    假设我有一个类 A 并且 B C D 都是从 A 派生的 如果我想知道引用的对象的类型是什么 我可以声明 pseudo code if obj instanceof B lt is B gt else if obj instanceof C
  • 如何在运行时获取类名,但仅获取类名?

    如何在运行时获取类名 但仅获取实际的类名而不是整个 com xyz etc 我的意思只是最后一个句点之后的名字部分 您必须将以下代码片段用于对象 yourObject getClass getSimpleName 或供课堂使用 yourCl
  • 将 xml 传递给 jquery 脚本时出现问题

    我正在尝试使用 jsp 中的 bufferedReader 从本地路径读取 xml 并尝试将 xml 传递给 jquery 脚本 如下所示
  • 一个在 Java 中进行 URL 查询字符串操作的好库 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我需要用 Java 进行一些非常简单的 URL 操作 就像获取查询中参数的值或更新它一样 我希望在 commons lang 包中找到一个
  • Java FTPS 无法检索文件列表(FileZilla 客户端工作正常)

    我正在使用 Apache Commons Net v3 5 和 Java 8 连接到远程 FTPS 站点 即在互联网上 我可以轻松连接 Windows 10 计算机上的 FileZilla 客户端 但我的 Java 程序无法完成相同的步骤
  • 压缩 Log4j 文件

    是否可以压缩日志文件 我通过 RollingFileAppender 进行 log4j 附加功能 http logging apache org log4j extras 对此表示支持 只需将以下内容添加到您的RollingFileAppe
  • 在Java中,如何在每次进入或退出给定对象的监视器时记录一条消息?

    我正在尝试调试一些使用一些自定义引用计数 锁定的 C Java 绑定 我想让 JVM 在每次给定对象进入或退出其监视器时打印一条消息 有什么办法可以做到这一点吗 基本上 我想要这个 synchronized lock System out
  • Java 全屏模式对话框

    如何创建一个可用作内部对话框的自定义模式 JDialog 用于全屏独占模式 我有一个 JScrollPane 带有一个巨大的滚动条 里面充满了巨大的按钮 如下所示 FOO BAR BIZ

随机推荐