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

 完整版代码

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{@Overridepublic 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.rhkb.cn/news/298528.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【HTML】制作一个简单的三角形动态图形

目录 前言 开始 HTML部分 CSS部分 效果图 总结 前言 无需多言&#xff0c;本文将详细介绍一段HTML和CSS代码&#xff0c;具体内容如下&#xff1a; 开始 首先新建文件夹&#xff0c;创建两个文本文档&#xff0c;其中HTML的文件名改为[index.html]&#xff0c;CSS的文件名…

Day83:服务攻防-开发组件安全JacksonFastJson各版本XStreamCVE环境复现

目录 J2EE-组件Jackson-本地demo&CVE 代码执行 (CVE-2020-8840) 代码执行 (CVE-2020-35728&#xff09; J2EE-组件FastJson-本地demo&CVE FastJson < 1.2.24 FastJson < 1.2.47 FastJson < 1.2.80 (利用条件比较苛刻) J2EE-组件XStream-靶场&CVE …

【JAVA】postman import certificates in project 导入证书pfx

1. 打开这个按钮 2. File ->Settings 3. 打开“certificates”, Add certificates 添加证书 4. 输入证书地址&#xff0c;然后选择证书文件pfx , 输入证书密码。点击添加就可以了。 特别提醒&#xff1a; 推荐本地自己证书验证软件&#xff0c;“KeyStore” 这个软件可以…

Revit模型进入虚幻引擎UE5教程

一、背景 小伙伴们是否有Revit进入虚幻引擎交互的需求呢&#xff1f; 二、实现功能 1.Revit进入虚幻UE5,包含模型属性&#xff0c;材质等 2.实现BIM构件点选&#xff0c;高亮&#xff0c;属性展示 3.实现BIM模型分层显示&#xff0c;爆炸等效果 三、教程地址 教程&#x…

软考 系统架构设计师系列知识点之数据库基本概念(1)

所属章节&#xff1a; 第6章. 数据库设计基础知识 第1节 数据库基本概念 数据&#xff08;Data&#xff09;是描述事务的符号记录&#xff0c;它具有多种表现形式&#xff0c;可以是文字、图形、图像、声音和语言等。信息&#xff08;Information&#xff09;是现实世界事物的…

力扣---删除链表的倒数第 N 个结点

给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[]示例 3&#xff1a…

IP归属地在互联网行业中的应用

摘要&#xff1a;IP&#xff08;Internet Protocol&#xff09;地址归属地是指互联网上某个IP地址所对应的地理位置信息。在互联网行业中&#xff0c;IP归属地具有重要的应用价值&#xff0c;包括网络安全、广告定向、用户定位等方面。IP数据云将探讨IP归属地在互联网行业中的应…

物联网实战--驱动篇之(一)EEPROM存储器(AT24C64)

目录 一、驱动概述 二、AT24C64简介 三、驱动编写 四、驱动应用 一、驱动概述 这是驱动篇的第一篇&#xff0c;所以先说明下驱动篇的作用和书写计划。之前的净化器项目已有提及&#xff0c;向ESP8266、SHT30这些都属于驱动设备&#xff0c;主芯片STM32是核心&#xff0c;相…

传输层 --- TCP (下篇)

目录 1. 超时重传 1.1. 数据段丢包 1.2. 接收方发送的ACK丢包 1.3. 超时重传的超时时间如何设置 2. 流量控制 3. 滑动窗口 3.1. 初步理解滑动窗口 3.2. 滑动窗口的完善理解 3.3. 关于快重传的补充 3.4. 快重传和超时重传的区别 4. 拥塞控制 4.1. 拥塞控制的宏观认识…

【MySQL】如何判断一个数据库是否出问题

在实际的应用中&#xff0c;其实大多数是主从结构。而采用主备&#xff0c;一般都需要一定的费用。 对于主备&#xff0c;如果主机故障&#xff0c;那么只需要直接将流量打到备机就可以&#xff0c;但是对于一主多从&#xff0c;还需要将从库连接到主库上。 对于切换的操作&a…

MySQL常见锁探究

MySQL常见锁探究 1. 各种锁类型1.1 全局锁1.2 表级锁1.2.1 表锁1.2.2 元数据锁&#xff08;MDL&#xff09;1.2.3 意向锁1.2.4 AUTO-INC 锁 1.3 行级锁1.3.1 Record Lock1.3.2 Gap Lock1.3.3 Next-Key Lock 2. MySQL是如何加锁的&#xff1f;2.1 什么 SQL 语句会加行级锁&#…

SketchUp Pro 2024 for mac 草图大师 专业的3D建模软件

SketchUp Pro 2024 for Mac是一款功能强大的三维建模软件&#xff0c;适用于Mac电脑。其简洁易用的界面和强大的工具集使得用户可以轻松创建复杂的3D模型。 软件下载&#xff1a;SketchUp Pro 2024 for mac v24.0.483 激活版下载 SketchUp Pro 2024 for Mac支持导入和导出多种文…

LangChain-08 Query SQL DB 通过GPT自动查询SQL

我们需要下载一个 LangChain 官方提供的本地小数据库。 安装依赖 SQL: https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql Shell: pip install --upgrade --quiet langchain-core langchain-community la…

[C#]OpenCvSharp使用帧差法或者三帧差法检测移动物体

关于C版本帧差法可以参考博客 [C]OpenCV基于帧差法的运动检测-CSDN博客https://blog.csdn.net/FL1768317420/article/details/137397811?spm1001.2014.3001.5501 我们将参考C版本转成opencvsharp版本。 帧差法&#xff0c;也叫做帧间差分法&#xff0c;这里引用百度百科上的…

【Linux】线程概念及线程互斥

目录 线程概念 线程优点 线程缺点 线程异常 线程系统编程接口 线程创建及终止 线程等待 使用线程系统接口封装一个小型的C线程库并实现一个抢票逻辑 线程互斥 互斥量的接口 线程互斥实现原理 使用系统加锁接口封装LockGuard 实现自动化加锁 线程安全和可重入函数 …

前端与后端协同:实现Excel导入导出功能

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

c# wpf template itemtemplate+dataGrid

1.概要 2.代码 <Window x:Class"WpfApp2.Window8"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expression/blend…

解析Apache Kafka:在大数据体系中的基本概念和核心组件

关联阅读博客文章&#xff1a;探讨在大数据体系中API的通信机制与工作原理 关联阅读博客文章&#xff1a;深入解析大数据体系中的ETL工作原理及常见组件 关联阅读博客文章&#xff1a;深度剖析&#xff1a;计算机集群在大数据体系中的关键角色和技术要点 关联阅读博客文章&a…

图像处理入门 3(how to get the pixel pitch / 如何获得单个像素的尺寸)

在这里一节里面&#xff0c;将记录如何获得一个相机传感器中单个像素点的尺寸&#xff0c;为了实现不同相机照片之间的匹配。 如果我们知道了相机传感器的尺寸和分辨率的大小&#xff0c;自然就可以求出单个像素的大小。 在这里插入图片描述&#xff1a; 如何获得相机传感器的…

【GEE实践应用】GEE下载遥感数据以及下载后在ArcGIS中的常见显示问题处理(以下载哨兵2号数据为例)

本期内容我们使用GEE进行遥感数据的下载&#xff0c;使用的相关代码如下所示&#xff0c;其中table是我们提前导入的下载遥感数据的研究区域的矢量边界数据。 var district table;var dsize district.size(); print(dsize);var district_geometry district.geometry();Map.…