java -网络编程socket-聊天室-02

news2025/1/20 14:51:16

 完整版代码

java -聊天室的代码: 用于存放聊天室的项目的代码和思路导图icon-default.png?t=N7T8https://gitee.com/to-uphold-justice-for-others/java---code-for-chat-rooms.git

先引入线程的正统解释

线程(Thread)是程序执行流的最小单元。线程是操作系统分配CPU时间片的基本单位,每个线程都拥有独立的程序计数器、栈、本地方法栈等,共享进程的资源,如代码段、数据段、堆等。Java通过线程实现并发编程,提高了程序的执行效率。

 线程的常见有两种方式

继承Thread类:通过继承Thread类并重写其run()方法,可以创建线程。然后,通过调用线程的start()方法来启动线程。

public class MyThread extends Thread {  
    @Override  
    public void run() {  
        // 线程执行的代码  
        System.out.println("MyThread is running.");  
    }  
  
    public static void main(String[] args) {  
        MyThread thread = new MyThread();  
        thread.start(); // 启动线程  
    }  
}

实现Runnable接口:通过实现Runnable接口的run()方法,也可以创建线程。这种方式更加灵活,因为Java不支持多继承,但可以实现多个接口。通常,我们将线程的任务逻辑放在Runnable实现类中,然后将其传递给Thread对象来启动线程。

public class MyRunnable implements Runnable {  
    @Override  
    public void run() {  
        // 线程执行的代码  
        System.out.println("MyRunnable is running.");  
    }  
  
    public static void main(String[] args) {  
        Thread thread = new Thread(new MyRunnable());  
        thread.start(); // 启动线程  
    }  
}

如果线程要多个线程调用一个类对象的时候常用Runnable接口,单一线程调用类对象个多方法可以使用继承比较方便

这边因为是服务端去获取多个客户端的消息并进行读取操作,线程在服务端创建,即一个客户端连接到服务器时候,服务端会接受客户端的插口,并新建一个线程去处理客户端的写入服务端的数据.

这边是服务端的封装私有的内部类一个线程的类来专门处理来自客户端的数据

私有的内部类的好处,在服务的执行业务,外部类不能直接访问,保证业务安全性,因为是内部类不用向外部类中建立一个引用变量实例来访问其对应的方法和属性

引用变量

引用变量的定义通常包括变量类型(即对象类型)和变量名

// 定义一个引用变量,其类型为String  
String myString;  
  
// 为引用变量分配一个String对象  
myString = new String("Hello, World!");  
//这里的mystring就是个引用变量

 服务端

之前在在单线程获取用户的昵称和ip地址这边需要封装成私有属性,保护用户的数据私密性

客户端需要ip是可以服务端接受的插口获取的,所以在有参构造中需要将ip赋值

全局变量相比局部变量 全局变量可以被其内部类和内部方法访问到,也容易因其遭到数据修改

局部变量可以保证在自己的类或者自己的方法中使用.

常常相比的是如果一个类中有成员变量,其非静态方法可以访问其成员变量,静态方法只能访问静态变量,静态变量是由static修饰的,常称为类的全局变量,如果其中还有内部类,类中的成员变量常称为局部实例变量,如果类中方法有变量常称为局部变量

private class ClientHandler implements Runnable{
        private Socket socket;
        private String ip;//记录当前客户端的IP地址
        private String nickname;//记录当前客户端的昵称

        public ClientHandler(Socket socket){
            this.socket = socket;
            //通过socket获取远端计算机的IP地址
            ip = socket.getInetAddress().getHostAddress();
        }

        public void run(){
            PrintWriter pw = null;
            try {
                /*
                    Socket的方法:
                    InputStream getInputStream()
                    通过socket获取一个字节输入流,读取该流就可以读取到远端计算机发送过来的数据
                 */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);
                nickname = br.readLine();
 } catch (IOException e) {
                //可以添加处理客户端异常断开的操作

            } finally {
  try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
}

同时线程也需要启动,这边使用的Runnable接口实现的所以需要新建一个线程实例化对象,

实现实例化对象.start启动线程

这要写在class Server类中

              //实例化线程任务
                ClientHandler handler = new ClientHandler(socket);
                //实例化线程
                Thread t = new Thread(handler);
                //启动线程
                t.start();

问题 服务端连接了多个客户端,虽然每个客户端可以给服务端发送消息,但是日常的情况,常需要客户端与客户端聊天

解决 因为客户端都是将信息传递给客户端,我们可以在服务端整合客户端的输给服务端的数据,这边的话使用集合的思想,集合是可以不固定的其长度,其内部是有初始的长度,会根据添加的数据进行扩容.这边推荐ArrayList ,是因为服务端是整合客户端数据发送,而ArrayList 相比与LinkedList查询数据快.

public class Server {
private Collection<PrintWrite> allOut =  new ArrayList<>();
}

这边需要定义在服务端一个输出流,而在客户端是输入流

服务端

这边服务端封装类一个内部类处理客户端的数据,

public class Server {
 private class ClientHandler implements Runnable{
public void run(){ 
 PrintWriter pw = null;
            try {
            OutputStream out =  socket.getOutputStream();
                OutputStreamWriter osw = new 
                OutputStreamWriter(out,StandardCharsets.UTF_8);
                BufferedWriter bw = new BufferedWriter(osw);
                pw = new PrintWriter(bw, true);
                allOut.add(pw); 

                String message;
                while ((message = br.readLine()) != null) {
                   
                         for(PrintWriter o : allOut){
                    o.println(nickname + "[" + ip + "]说:" + message);
                }
                   
                }

               
}
}

客户端

同样的这边需要封装一个私有的内部类利用线程来处理服务端发送的数据

 private class ServerHandler implements Runnable{
        @Override
        public void run() {
            try {
                //通过socket获取输入流,用于读取服务端发送过来的消息
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);
                String line;
                while((line = br.readLine())!=null){
                    System.out.println(line);
                }

            } catch (IOException e) {

            }
        }
    }

 同时线程也需要启动,这边使用的Runnable接口实现的所以需要新建一个线程实例化对象,

这边需要注意的是客户端输入昵称在客户端接受服务端数据之前

       ServerHandler handler = new ServerHandler();
            Thread t = new Thread(handler);
            t.setDaemon(true);
            t.start();

线程实例化对象.setDaemon(true);是将普通线程变成守护线程 

问题这边客户端接受服务端数据为什么使用守护线程

守护线程定义 

守护线程(Daemon Thread)是Java中的一种特殊线程类型,它的存在主要是为了服务其他的线程,特别是用户自定义的线程。当所有的非守护线程(即用户线程)执行完成或退出时,即使守护线程仍在运行,Java虚拟机(JVM)也会直接退出。因此,守护线程通常用于执行一些辅助工作或后台任务,例如垃圾回收、自动保存数据、心跳检测、日志记录、清理临时文件以及监控系统状态等。

守护线程的特点包括:

  1. 随主线程结束而结束:当主线程(即非守护线程)结束时,守护线程会随之被终止,不管它是否执行完毕。
  2. 不执行finally块:如果守护线程中执行的代码块中有finally块,当守护线程被终止时,finally块不会被执行。
  3. 不能持有程序运行的关键资源:由于守护线程在所有用户线程结束时可能被中断,如果持有关键资源,可能会导致数据不一致或资源泄漏。
  4. 不能用于执行必须完成的任务:由于守护线程可能随时被中断,它不适合执行必须完成的任务,例如文件写入等。

在Java中,可以通过Thread类的setDaemon(true)方法将一个线程设置为守护线程,而setDaemon(false)方法则用于取消守护线程的设置。

总之,守护线程在Java中扮演着重要的角色,用于在后台执行一些必要的辅助任务,以确保程序的正常运行和资源的有效管理。

原因

  1. 不阻塞主线程:客户端的主线程通常负责用户界面的更新、用户交互的处理等重要任务。如果接收服务端数据的操作是同步的并且需要花费较长时间,那么它可能会阻塞主线程,导致用户界面无响应或响应缓慢。通过使用守护线程来执行这项任务,主线程可以继续处理其他重要工作,从而保持用户界面的流畅性。

  2. 后台处理:接收服务端数据通常是一个后台任务,它不需要与用户进行实时交互。守护线程非常适合执行这类任务,因为它们可以在后台默默运行,而不需要用户或主线程的特别关注。

问题 客户端想要获取在线人数,以便后续的私聊(这个之后会讲) ,同时也想知道客户端下线之后人数,

解决 这边我们发现我们需要重复获取集合中的信息,而集合的长度会根据在线人数变,我们这边可以将集合信息写一个方法以减少其代码量

 public void sendMessage(String message){
            System.out.println(message);//先在服务端控制台上输出一下
            //遍历要和增删互斥,迭代器要求遍历过程中不可以通过集合方法增删元素
         
//                for (PrintWriter o : allOut) {//发送给所有客户端
                for(PrintWriter o : allOut){
                    o.println(message);
                }
            }
 

之前的可以简写了

集合对象.size()可以获取集合长度,正好可以统计在线人数,

这个需要在集合添加数据之后调用,业务逻辑

sendMessage(nickname+"上线了,当前在线人数:"+allOut.size());

 因为之前只考虑了将服务端整合客户端的数据集体发送,没考虑客户端下线的情况

这边如果客户端下线(包括异常断开),关闭其接口以及将其移除(昵称)

finally是保证程序一定会执行,常用与关闭文件流

finally {
                //处理客户端断开链接后的操作
                //将该客户端的输出流从共享集合allOut中删除
              
//                    allOut.remove(pw);
                    allOut.remove(nickname);
                
                sendMessage(nickname+"下线了,当前在线人数:"+allOut.size());
                //将socket关闭,释放底层资源
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }

问题,线程真的安全吗,这边需要对服务端的中存储数据的集合进行增减,服务端的集合需要同时面对多个客户端输入的数据,不会引起资源抢占吗

解决这个涉及到多线程并发的问题,这个需要运用到线程锁的概念,这边暂时还在总结,我也是一个新手🤣🤣🤣

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1571755.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(2024,超分辨率,膨胀卷积和低通滤波,SD)FouriScale:免训练高分辨率图像合成的频率视角

FouriScale: A Frequency Perspective on Training-Free High-Resolution Image Synthesis 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 2. 相关工作 2.2 通过扩散模型进行…

DotNetBar的SlidePanel和metroTilePanel使用笔记

一、前言 界面组件DotNetBar2中的2个控件属性SlidePanel和metroTitlePanel的使用方法&#xff0c;网上相关资源较少&#xff0c;就一些属性的使用学习记录如下&#xff1a; SlideSideDevComponents.DotNetBar.Controls.eSlideSide.Top/Bottom/Right/Left 及 metroTilePanel和m…

蓝桥杯每日一题:公约数(gcd)

题目描述&#xff1a; 给定两个正整数 a 和 b。 你需要回答 q 个询问。 每个询问给定两个整数 l,r&#xff0c;你需要找到最大的整数 x&#xff0c;满足&#xff1a; x 是 a和 b 的公约数。l≤x≤r。 输入格式 第一行包含两个整数 a,b。 第二行包含一个整数 q。 接下来…

Python人工智能应用---中文分词词频统计

目录 1.中文分词 2.循环分别处理列表 &#xff08;1&#xff09;分析 &#xff08;2&#xff09;代码解决 3.词袋模型的构建 &#xff08;1&#xff09;分析需求 &#xff08;2&#xff09;处理分析 1.先实现字符串的连接 2.字符串放到新的列表里面 4.提取高频词语 &…

WordPress建站教程:10步快速搭建个人网站

WordPress是一个广泛使用的内容管理系统&#xff08;CMS&#xff09;&#xff0c;凭借其用户友好的界面和大量可定制的主题和插件&#xff0c;为WordPress 提供了多功能性和灵活性&#xff0c;可用于创建各种类型的网站&#xff0c;包括个人博客、B2B企业网站、B2C外贸网站等&a…

【HTML】CSS样式(二)

上一篇我们学习了CSS基本样式和选择器&#xff0c;相信大家对于样式的使用有了初步认知。 本篇我们继续来学习CSS中的扩展选择器及CSS继承性&#xff0c;如何使用这些扩展选择器更好的帮助我们美化页面。 下一篇我们将会学习CSS中常用的属性。 喜欢的 【点赞】【关注】【收藏】…

非关系型数据库(缓存数据库)redis的性能管理

目录 一.Redis性能管理 1.Info Memory——查看Redis内存使用 2.内存碎片率 3. 内存使用率 4.内存回收key 二.缓存的穿透&#xff0c;击穿和雪崩 1.缓存的穿透 1.1 问题描述 1.2 缓存穿透发生的条件 1.3 缓存穿透发生的原因 1.4 解决方案 2 缓存的击穿 2.1 问题描…

Redis各个方面入门详解

目录 一、Redis介绍 二、分布式缓存常见的技术选型方案 三、Redis 和 Memcached 的区别和共同点 四、缓存数据的处理流程 五、Redis作为缓存的好处 六、Redis 常见数据结构以及使用场景 七、Redis单线程模型 八、Redis 给缓存数据设置过期时间 九、Redis判断数据过期的…

isc-dhcp-server DNS配置

我遇到一个有趣的问题&#xff0c;我先在一台Ubuntu服务器上使用isc-dhcp-server在其其中一个网口运行DHCP服务&#xff0c;然后我自己的笔记本电脑直连到这个网口&#xff0c;来上网。 本来直接就应该能上网&#xff0c;但是我的电脑只有在打开Clash时才能访问互联网&#xf…

基于多模态单细胞数据构建共表达网络-MuSeGNN

本篇来自于MuSe-GNN: Learning Unified Gene Representation From Multimodal Biological Graph Data的补充材料。主要目的是从多模态数据中构建共表达网络。作者概述了使用CS-CORE&#xff0c;scTransform和SPARK-X进行预处理步骤和网络构建的算法细节。 目前存在大量用于图谱…

卫星遥感影像统计农业产量、作物分类及面积

卫星遥感技术的广泛应用为农业领域带来了巨大的变革&#xff0c;其中&#xff0c;卫星遥感影像在农业产量估算方面的应用正成为一项关键技术。通过高分辨率的遥感数据&#xff0c;农业生产者可以更准确、及时地了解农田状况&#xff0c;实现精准农业管理&#xff0c;提高产量和…

软件杯 深度学习乳腺癌分类

文章目录 1 前言2 前言3 数据集3.1 良性样本3.2 病变样本 4 开发环境5 代码实现5.1 实现流程5.2 部分代码实现5.2.1 导入库5.2.2 图像加载5.2.3 标记5.2.4 分组5.2.5 构建模型训练 6 分析指标6.1 精度&#xff0c;召回率和F1度量6.2 混淆矩阵 7 结果和结论8 最后 1 前言 &…

TiDB单机版安装和连接访问

TiDB单机版安装和连接访问 1、下载 $wget http://download.pingcap.org/tidb-latest-linux-amd64.tar.gz 2、解压缩 $tar -zxvf tidb-latest-linux-amd64.tar.gz 3、启动TiDB 启动PD $./bin/pd-server --data-dirpd --log-filepd.log 启动tikv $./bin/tikv-server --pd…

路由器拨号失败解决方法

目录 一、遇到问题 二、测试 三、解决方法 &#xff08;一&#xff09;路由器先单插wan口设置 &#xff08;二&#xff09;mac地址替换 &#xff08;三&#xff09;更改路由器DNS 一、遇到问题 1 .在光猫使用桥接模式&#xff0c;由路由器进行拨号的时候&#xff0c;出现…

关于 QSound播放wav音频文件,播放失败“using null output device, none available” 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/137264493 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

【C语言】_文件内容操作:随机读写

目录 1. fseek 1.1 随机读文件 1.2 随机写文件 2. ftell 3. rewind 当以读方式打开一个存在且存有内容的文件时&#xff0c;文件指针会默认指向第一个元素。以在test4.txt文件中存储abcdef为例&#xff1a; int main() {//打开文件FILE* pf fopen("E:\\C_文件操作…

AI复活:商业新风口还是情感禁区?

随着人工智能技术的飞速发展&#xff0c;AI已经渗透到我们生活的方方面面&#xff0c;其中&#xff0c;“AI复活”服务作为新兴的技术应用&#xff0c;正逐渐走进大众视野。然而&#xff0c;这一技术带来的不仅是商业机会&#xff0c;更伴随着伦理和情感的争议。 “AI复活”服务…

NoSQL概述

NoSQL概述 目录 一、为什么用NoSQL 二、什么是NoSQL 三、经典应用分析 四、N o S Q L 数 据 模 型 简 介 五、NoSQL四大分类 六、CAP BASE 一、为什么用NoSQL 1、单机MySQL的美好年代 在90年代&#xff0c;一个网站的访问量一般不大&#xff0c;用单个数据库完全可以轻松应…

webrtcP2P通话流程

文章目录 webrtcP2P通话流程webrtc多对多 mesh方案webrtc多对多 mcu方案webrtc多对多 sfu方案webrtc案例测试getUserMediagetUserMedia基础示例-打开摄像头getUserMedia canvas - 截图 打开共享屏幕 webrtcP2P通话流程 在这里&#xff0c;stun服务器包括stun服务和turn转发服…

比nestjs更优雅的ts控制反转策略-依赖查找

一、Cabloy5.0内测预告 Cabloy5.0采用TS对整个全栈框架进行了脱胎换骨般的大重构&#xff0c;并且提供了更加优雅的ts控制反转策略&#xff0c;让我们的业务开发更加快捷顺畅 1. 新旧技术栈对比&#xff1a; 后端前端旧版js、egg2.0、mysqljs、vue2、framework7新版ts、egg3…