【JavaSE学习】04-3Java高级(多线程、网络编程)

JavaSE(B站黑马)学习笔记

01Java入门
02数组、方法
03面向对象&Java语法
04-1Java高级(Stream流、异常处理、日志技术)
04-2Java高级(文件处理-IO流)
04-3Java高级(多线程、网络编程)
04-4Java高级(单元测试、反射、注解、动态代理、XML)
05-1常用API
05-2常用API(集合)



前言

JavaSE(B站黑马)学习笔记 04-3Java高级(多线程、网络编程)


04-3Java高级(多线程、网络编程)

多线程

什么是线程?

  • 线程(thread)是一个程序内部的一条执行路径。

  • 我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径。

  • 程序中如果只有一条执行路径,那么这个程序就是单线程的程序。

多线程是什么?

  • 多线程是指从软硬件上实现多条执行流程的技术。

多线程用在哪里,有什么好处

常见的多线程例如12306购票,会使用多线程来处理多个用户的购票请求。包括百度网盘,上传文件的同时还可以下载文件,这时系统就会使用多线程来处理。

  • 再例如:消息通信、淘宝、京东系统都离不开多线程技术。

关于多线程需要学会什么

多线程的创建

方式一:继承Thread类

Thread类

  • Java是通过java.lang.Thread 类来代表线程的。
  • 按照面向对象的思想,Thread类提供了实现多线程的方式。

步骤:

  1. 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
  2. 创建MyThread类的对象
  3. 调用线程对象的start()方法启动线程(启动后还是执行run方法的)

一般称main方法为主线程,其余创建的线程称为子线程。当执行时因为是多线程,所以两条线程是同时跑的,有可能子线程跑得快先执行完子线程,有可能主线程跑得快先执行完主线程,也有可能一下跑主线程一下跑子线程。在时间维度上它们是同时运行的,但在后台输出上肯定会有先输出后输出,所以会看到先打印谁也不一定

1、在案例中为什么不直接调用了run方法,而是调用start启动线程?

  • 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
  • 只有调用start方法才是启动一个新的线程执行,就是告诉系统我这开了一条新线程。

2、如果把主线程任务放在子线程之前会怎样。

  • 这样主线程一直是先跑完的,相当于是一个单线程的效果了

方式一优缺点:

  • 优点:编码简单
  • 缺点:线程类已经继承Thread,无法继承其他类,不利于扩展。

方式二:实现Runnable接口

常规方法实现方式二

步骤:

  1. 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
  2. 创建MyRunnable任务对象
  3. 把MyRunnable任务对象交给Thread处理。
  4. 调用线程对象的start()方法启动线程

Thread的构造器

方式二优缺点:

  • 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
  • 缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。
使用匿名内部类的形式实现方式二

步骤:

  1. 可以创建Runnable的匿名内部类对象。
  2. 交给Thread处理。
  3. 调用线程对象的start()启动线程。

方式三:JDK5.0新增,实现Callable接口,结合FutureTask完成

1、前2种线程创建方式都存在一个问题:

  • 他们重写的run方法均不能直接返回结果。
  • 不适合需要返回线程执行结果的业务场景。

2、怎么解决这个问题呢?

  • JDK 5.0提供了Callable和FutureTask来实现。
  • 这种方式的优点是:可以得到线程执行的结果。

步骤:

  1. 得到任务对象

    a. 定义类实现Callable接口,重写call方法,封装要做的事情。

    b. 用FutureTask把Callable对象封装成线程任务对象。

  2. 把线程任务对象交给Thread处理。

  3. 调用Thread的start方法启动线程,执行任务

  4. 线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果。

FutureTask的API



方式三的优缺点:

  • 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
  • 可以在线程执行完毕后去获取线程执行的结果。
  • 缺点:编码复杂一点。

三种方式对比

Thread的常用方法

Thread常用API说明

  • Thread常用方法:获取线程名称getName()(有默认值)、设置名称setName()、获取当前线程对象currentThread()。
  • 至于Thread类提供的诸如:yield、join、interrupt、不推荐的方法 stop 、守护线程、线程优先级等线程的控制方法,在开发中很少使用,这些方法会在高级篇以及后续需要用到的时候再为大家讲解。

1. 当有很多线程在执行的时候,我们怎么去区分这些线程呢?

  • 此时需要使用Thread的常用方法:getName()、setName()、currentThread()等。

Thread获取和设置线程名称

Thread类获得当前线程的对象

注意:

  • 此方法是Thread类的静态方法,可以直接使用Thread类调用。
  • 这个方法是在哪个线程执行中调用的,就会得到哪个线程对象。

Thread的构造器

Thread类的线程休眠方法

线程安全

线程安全问题是什么、发生的原因

线程安全问题

  • 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。

取钱模型演示

  • 需求:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元。
  • 如果小明和小红同时来取钱,而且2人都要取钱10万元,可能出现什么问题呢?

取钱案例线程安全问题演示

线程安全问题案例模拟

同一个账户对象交给两个线程,线程来取钱时调用账户对象的取钱方法,判断账户金额是否大于等于取钱金额,是则提示取钱成功并让账户金额减去,否则提示余额不足。当两个线程同时运行时,小明线程判断金额大于等于取钱金额会允许取钱,小明线程还没来得及减去金额小红线程就来判断金额也大于等于取钱金额也允许取钱,这时就造成了线程安全问题。

线程同步

同步思想概述

1、取钱案例出现问题的原因?

  • 多个线程同时执行,发现账户都是够钱的。

2、如何才能保证线程安全呢?

  • 让多个线程实现先后依次访问共享资源,这样就解决了安全问题

线程同步的核心思想

  • 加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。(锁对象要唯一)

方式一:同步代码块

  • 作用:把出现线程安全问题的核心代码给上锁。
  • 原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。

锁对象要求

  • 理论上:锁对象只要对于当前同时执行的线程来说是同一个对象即可。

对会发生线程安全问题的代码使用同步代码块进行加锁(选中后按快捷键ctrl+alt+T选择synchronized),在括号内给一个唯一的同步锁对象,例如甚至可以直接给一个字符串对象"gdit",它是一个常量,只有唯一的一个对象。不管时小明线程还是小红线程,进来后拿到的都是同一给对象,所以它满足锁的唯一性。它没有意义,只是一个代表,说白了就是一个flag标志。两个线程同时进来取钱,加锁后锁会随机让一个线程先进来取钱,例如当小明线程先进来取钱小红线程就会在后面排队,当小明线程取完钱金额减去后就自动解锁,小红线程进来后就会判断为余额不足。

锁对象用任意唯一的对象好不好呢?

  • 不好,会影响其他无关线程的执行。

例如有另一个账户对象ICBC-222,小黑对这个账户对象取钱,对于小明 小红 小黑来说这个锁都是唯一的(整个系统都是唯一的),这就造成了小明小红要排队,小黑也要参加排队,虽然它们是两个不同的账户对象,但当它们三个同时取钱时用的锁对象都是同一个,小明小红取钱就会干扰到小黑取钱。(相当于一把锁锁住了全部人),这时就有了锁对象的规范要求

锁对象的规范要求

  • 规范上:建议使用共享资源作为锁对象。(例如这个案例中账户就是共享资源,对于小明小红来说ICBC-111账户是它们的共享资源,所以这个锁只会对小明小红生效)
  • 对于实例方法建议使用this作为锁对象。
  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象。

方式二:同步方法

  • 作用:把出现线程安全问题的核心方法给上锁。
  • 原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。


同步方法底层原理

  • 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
  • 如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!
  • 如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

是同步代码块好还是同步方法好一点?

  • 同步代码块锁的范围更小,同步方法锁的范围更大。

(形象点就是公共厕所,用同步代码块就像把坑锁起来了,让每个人在坑外面排队,同步方法是把整个厕所锁起来,让每个人在厕所外面排队)

方式三:Lock锁

Lock锁

  • 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。
  • Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
  • Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。

Lock的API


线程通信(了解)

什么是线程通信、如何实现?

  • 所谓线程通信就是线程间相互发送数据,线程间共享一个资源即可实现线程通信。

线程通信常见形式

  • 通过共享一个数据的方式实现。
  • 根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做。

线程通信实际应用场景

  • 生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费生产者产生的数据。
  • 要求:生产者线程生产完数据后唤醒消费者,然后等待自己,消费者消费完该数据后唤醒生产者,然后等待自己。(PV操作)

线程通信案例模拟

  • 加入有这样一个场景,小明和小红有三个爸爸,爸爸们负责存钱,小明和小红负责取钱,必须一存、一取。
  • 线程通信的前提:线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,且要保证线程安全。

Object类的等待和唤醒方法:

注意:上述方法应该使用当前同步锁对象进行调用。(只有锁对象知道谁在用我,我要等待谁)

如果方法是实例方法:默认用this作为的锁对象。但是代码要高度面向对象!

如果方法是静态方法:默认用类名.class作为的锁对象。

运行程序后,有五条线程同时运行(小明、小红、亲爹、干爹、岳父)都操作的是同一个账户对象,所以锁对象都是同一个。任何一个线程都有抢到锁的可能性,有一个人抢到锁其它人就要排队,假设当亲爹抢到锁,进来判断发现没钱就进行存钱操作,然后把其他人唤醒,自己等待并解除锁的占用,让其他人来抢锁自己不参与。下一个人抢到锁的可以是除亲爹任何一个人,假设干爹抢到锁进来发现有钱就什么也不操作,任何唤醒别人,自己等待不参与下一次抢锁。假设这时小红抢到锁,发现里面有钱就取钱,然后唤醒其它人,自己等待不参与下一次抢锁。剩下四个人又抢锁,一直循环,三个爸爸进来判断没钱就存钱,存完唤醒其他人自己等待,有钱就什么也不操作,唤醒其他人自己等待。小明小红也是同一个逻辑,进来判断有钱就取,取完唤醒其他人自己等待,没钱就什么也不操作,唤醒其他人自己等待。因为有锁的存在,每次都会有人抢到锁然后其他人排队,根据遇到不同类型的线程执行什么样的操作。(这就是线程通信,生产消费生产消费生产消费,唤醒等待唤醒等待唤醒等待—PV操作)

弹幕解释:

1.这里五个线程任何一个都有可能被锁选中,选中之后就进入存钱或者取钱的流程——jump

2.假如是一个爸爸被选中,那就看有没有钱,没有就存钱,有就休眠,把其他四个唤醒——jump

3.爸爸休眠之后,如果来的又是爸爸,那就继续休眠,唤醒其他人——jump

4.直到锁选中小明或者小红,ta取了钱,然后休眠,唤醒另外四个——jump

5.接下来又继续选,直到选中一个爸爸存钱,然后休眠再选,直到选中一个人取钱——jump

6.如果在else里加一条输出语句,就会发现,输出了这条语句的线程比存取钱的线程要多,因为可能重复遇上爸爸或小红小明——jump

7.总之,它不是有序的一个存一个人取,而是需要看何时遇到不同类型的线程,才进入下一个环节——jump

8.那个唤醒实际上只对上一个休眠的线程有用,因为其他线程此时都在锁外等待,没有休眠——jump

线程池

线程池概述

什么是线程池?

  • 线程池就是一个可以复用线程的技术

不使用线程池的问题

  • 如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。

线程池的工作原理

假设线程池里有三个固定线程,每来一个任务就分配一个线程去处理它,当第四个任务来了让线程先把前面分配的任务处理完再来处理第四个任务,这样就可以使线程复用。一般长久存活的线程称为工作线程或核心线程,需要处理人任务叫任务队列。

线程池实现的API、参数说明

谁代表线程池?

  • JDK 5.0起提供了代表线程池的接口:ExecutorService

如何得到线程池对象

  • 方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
  • 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象

ThreadPoolExecutor构造器的参数说明

举个例子就是,假设在KTV里,每来一个客人分配一个服务员,如果突然来500个客人难道招500个服务员吗,显然不现实。我们可以招3个正式员工,这3个正式员工可以一直招待客人,假如KTV最大支持10个员工工作,如果三个正式员工正在工作,门口等待的位置也坐满了,3个正式工忙不过来那么这时我们就可以再招7个临时工,忙的时候让它们过来工作,不忙直接开掉临时工让3个正式工继续工作,然后我们也可以规定临时工工作多少天后开掉。参数五任务队列就相当于门口的座位,等里面的顾客离开后再进去。线程工厂就相当于人力资源专门帮我们招人的(创建线程的)。最后一个参数是指当10个员工都在忙,门口的座位都坐满了又来了新客人该怎么办,要抛异常还是一脚把他踹出去,要告诉它应该怎么做的操作

线程池常见面试题

临时线程什么时候创建啊?

  • 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

什么时候会开始拒绝任务?

  • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。

线程池处理Runnable任务

ThreadPoolExecutor创建线程池对象示例

ExecutorService的常用方法

新任务拒绝策略

线程池处理Callable任务

ExecutorService的常用方法

Executors工具类实现线程池

Executors得到线程池对象的常用方法

  • Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。

注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的。

Executors使用可能存在的陷阱

  • 大型并发系统环境中使用Executors如果不注意可能会出现系统风险。

看newFixedThreadPool()和newSingleThreadExecutor()源码就知道它其实还是用ThreadPoolExecutor的方法创建线程池,只不过给了固定线程而已,它并没有设定任务队列数量,就是说可以无线加任务数量没有抛弃策略之类的。如果固定线程一直在忙任务又不断加就会出现内存溢出的现象。

  • 大型并发系统环境中使用Executors如果不注意可能会出现系统风险。

所以还是自己用ThreadPoolExecutor的方法创建线程池吧。

补充知识:定时器

  • 定时器是一种控制任务延时调用,或者周期调用的技术。
  • 作用:闹钟、定时邮件发送。

定时器的实现方式

  • 方式一:Timer
  • 方式二: ScheduledExecutorService

Timer定时器

还有很多方法,工具需要去查文档,这里讲常用的。schedule(TimerTask task, long delay, long period),参数一:定时任务,其实现了Runnable接口、参数二:延时几秒执行、参数三:隔几秒循环执行

Timer定时器的特点和存在的问题

  • Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。
  • 可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行。

由于Timer是单线程,只有一个线程在执行任务,当处理多任务时,如果有某个任务执行时间过长就会影响其它任务设定的定时器时间有初入。如图,执行BBB本应该每2秒执行一次的,由于执行AAA耗时5秒,所以BBB就要等AAA执行完才能执行。

由于Timer是单线程,只有一个线程在执行任务,当处理多任务时,如果有某个任务执行出错,程序就挂掉了,其它定时任务也就不会执行了。

ScheduledExecutorService定时器

  • ScheduledExecutorService是 jdk1.5中引入了并发包,目的是为了弥补Timer的缺陷, ScheduledExecutorService内部为线程池。

ScheduledExecutorService的优点

  • 基于线程池,某个任务的执行情况不会影响其他定时任务的执行。

如图,执行AAA耗时5秒并不影响执行CCC的循环时间,每隔2秒CCC照样运行,BBB出错就它的线程挂掉,AAA和CCC还是可以运行。

补充知识:并发、并行

并发与并行

  • 正在运行的程序(软件)就是一个独立的进程, 线程是属于进程的,多个线程其实是并发与并行同时进行的。

补充:线程与进程的区别

  • 进程是资源分配的最小单位,线程是资源调度的最小单位。
  • 线程是在进程下运行的。一个进程可以包含多个线程。

并发的理解:

  • CPU同时处理线程的数量有限。
  • CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。

就像闪电侠发糖,100个人,闪电侠跑得特别快,1秒内就给100个人发完糖了,因为闪电侠的速度很快就像给每个人同时发糖一样(其实还是轮流发的),这就是并发。

并行的理解:

  • 在同一个时刻上,同时有多个线程在被CPU处理并执行。

就像有4个闪电侠同时发糖

补充知识:线程的生命周期

状态

线程的状态

  • 线程的状态:也就是线程从生到死的过程,以及中间经历的各种状态及状态转换。
  • 理解线程的状态有利于提升并发编程的理解能力。

Java线程的状态

  • Java总共定义了6种状态
  • 6种状态都定义在Thread类的内部枚举类中。

线程的6种状态互相转换

线程的6种状态总结

网络编程

(下面讲的东西涉及到计算机网络的知识,这里很多都是基础入门知识,深入了解要专门学计算机网络这门课)

什么是网络编程?

  • 网络编程可以让程序与网络上的其他设备中的程序进行数据交互。

网络通信基本模式

  • 常见的通信模式有如下2种形式:Client-Server(CS) 、 Browser/Server(BS)

关于网络编程需要学会什么

网络通信三要素

实现网络编程关键的三要素

  • IP地址:设备在网络中的地址,是唯一的标识。
  • 端口:应用程序在设备中唯一的标识。
  • 协议: 数据在网络中传输的规则,常见的协议有UDP协议和TCP协议。

要素一:IP地址

IP地址

  • IP(Internet Protocol):全称”互联网协议地址”,是分配给上网设备的唯一标志。
  • 常见的IP分类为:IPv4和IPv6
  • IPv6:128位(16个字节),号称可以为地球每一粒沙子编号。
  • IPv6分成8个整数,每个整数用四个十六进制位表示, 数之间用冒号(:)分开。

IP地址基本寻路

IP地址形式:

  • 公网地址、和私有地址(局域网使用)。
  • 192.168. 开头的就是常见的局域网地址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用。

IP常用命令(cmd命令):

  • ipconfig:查看本机IP地址
  • ping IP地址:检查网络是否连通

特殊IP地址:

  • 本机IP: 127.0.0.1或者localhost:称为回送地址也可称本地回环地址,只会寻找当前所在本机。

IP地址操作类-InetAddress

InetAddress 的使用

  • 此类表示Internet协议(IP)地址。

InetAddress API如下

要素二:端口号

端口号

  • 端口号:标识正在计算机设备上运行的进程(程序),被规定为一个 16 位的二进制,范围是 0~65535。

端口类型

  • 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用 80,FTP占用21)
  • 注册端口:1024~49151,分配给用户进程或某些应用程序。(如:Tomcat占 用8080,MySQL占用3306)
  • 动态端口:49152到65535,之所以称为动态端口,是因为它 一般不固定分配某种进程,而是动态分配。

注意:我们自己开发的程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。

要素三:协议

通信协议

  • 连接和通信数据的规则被称为网络通信协议

网络通信协议有两套参考模型

  • OSI参考模型:世界互联协议标准,全球通信规范,由于此模型过于理想化,未能在因特网上进行广泛推广。
  • TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。

传输层的2个常见协议

  • TCP(Transmission Control Protocol) :传输控制协议
  • UDP(User Datagram Protocol):用户数据报协议

TCP协议特点

  • 使用TCP协议,必须双方先建立连接,它是一种面向连接可靠通信协议。
  • 传输前,采用“三次握手”方式建立连接,所以是可靠的 。
  • 在连接中可进行大数据量的传输 。
  • 连接、发送数据都需要确认,且传输完毕后,还需释放已建立的连接,通信效率较低。

TCP协议通信场景

  • 对信息安全要求较高的场景,例如:文件下载、金融等数据通信。

TCP三次握手确立连接

在吗? 在! 好的

TCP四次挥手断开连接

UDP协议:

  • UDP是一种无连接、不可靠传输的协议。
  • 将数据源IP、目的地IP和端口封装成数据包,不需要建立连接
  • 每个数据包的大小限制在64KB内
  • 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
  • 可以广播发送 ,发送数据结束时无需释放资源,开销小,速度快。

UDP协议通信场景

  • 语音通话,视频会话等。

UDP通信

UDP通信:快速入门

UDP协议的特点

  • UDP是一种无连接、不可靠传输的协议。(只要发了我就收)
  • 将数据源IP、目的地IP和端口以及数据封装成数据包,大小限制在64KB内,直接发送出去即可。

UDP协议通信模型演示

DatagramPacket:数据包对象(韭菜盘子)

DatagramPacket常用方法

DatagramSocket:发送端和接收端对象(人)

DatagramSocket类成员方法

测试运行先启动服务端(接收端)再启动客户端(发送端),不然客户端发完服务端还没启动就没收到了。服务端启动后会一直等待客户端发送数据。

UDP通信:多发多收

可以启动多个客户端发送信息,服务端可以一直接收,但需要注意的是IDEA默认不允许程序多开运行,我可以修改配置让程序多开运行。

如图:再次运行客户端会提示先关闭再运行

修改为允许多开

但允许后运行发现报错(这是一个经典错误:端口已经被使用了,6666端口第一次运行以及使用了,再运行就要换端口了)

修改端口后再运行就能多个客户端发送信息给服务端了

UDP通信-广播、组播

UDP的三种通信方式

  • 单播:单台主机与单台主机之间的通信。
  • 广播:当前主机与所在网络中的所有主机通信。
  • 组播:当前主机与选定的一组主机的通信。

UDP如何实现广播

  • 使用广播地址:255.255.255.255

  • 具体操作:

    • 发送端发送的数据包的目的地写的是广播地址、且指定端口。(255.255.255.255 , 9999)
    • 本机所在网段的其他主机的程序只要注册对应端口就可以收到消息了。(9999)

UDP如何实现组播

  • 使用组播地址:224.0.0.0 ~ 239.255.255.255

  • 具体操作:

    • 发送端的数据包的目的地是组播IP (例如:225.0.1.1, 端口:9999)
    • 接收端必须绑定该组播IP(225.0.1.1),端口还要注册发送端的目的端口9999,这样即可接收该组播消息。
    • DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP。

MulticastSocket绑定组播的方法:

public void joinGroup(InetAddress mcastaddr) 该方法在JDK14开始就被弃用了,企业还没用这么新的版本,所以还是可以用的。

public void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf) 此方法为当前推荐用法,绑定组播IP和网段 参数一:绑定组播IP 参数二:绑定网段,这个网段一般用默认的(比如在局域网内就用局域网的网段)

TCP通信-快速入门

TCP协议

  • TCP是一种面向连接,安全、可靠的传输数据的协议
  • 传输前,采用“三次握手”方式,点对点通信,是可靠的
  • 在连接中可进行大数据量的传输

TCP通信模式演示:

socket翻译过来就是端的意思,就是端对端通信(点对点通信)只要把管道打通,剩下的就是IO流传输数据

注意:在java中只要是使用java.net.Socket类实现通信,底层即是使用了TCP协议

编写客户端代码

Socket

Socket类成员方法

发送完数据应该关闭管道资源,从资源的角度来说应该关闭,但不建议,因为Socket建立的是长连接(TCP连接),它不像UDP发完就不管了,UDP下次再发就重启就好。但Socket管道建立后是可以一直用的,如果发完就关闭就容易出现bug,它消息正在发突然就把管道关了,它关闭管道的消息是要告诉对方的,它是端对端的连接,一方断了另一方也立马断,关闭的信号是一个很小的数据,而传递的数据可能很大,数据传一半关闭的信号先来了就会导致对方收不到的数据极端情况出现。

编写服务端代码、原理分析

ServerSocket(服务端)

ServerSocket类成员方法

调用ServerSocket对象的accept()方法等待客户端连接返回的是Socket管道对象,然后调用getInputStream()方法得到字节输入流,而字符缓冲输入流是高效的读取方式,但它的参数只接收字符输入流,我们可以使用字符输入转换流把字节流转换成字符流。字符输入转换流详看04-2Java高级(文件处理-IO流)—字符输入转换流

测试运行:

运行服务端等客户端发送消息,发现报错了,原因是我们在服务端是读取一行数据,而客户端发送数据没有用ps.println()或"\n"换行符,服务端会认为数据没发完一直等,而客户端发完数据后程序结束就断开了,因为是面向连接的端对端连接(TCP),服务端也断了,导致服务端还在循环读一行数据,就会报错。

客户端发送数据加上换行后,服务端会读取一行数据,但又报错了,原因也是一样的,客户端发完数据后程序结束就断开了,因为是面向连接的端对端连接(TCP),服务端也断了,而服务端还在循环读一行数据,所以就会报错

改进为if判断就行,一发一收,发完之后客户端程序结束断开,服务端也断开,但if就判断一次不会循环判断就不会报错了。所以只实现了一发一收,还只能收一行数据

总结:

TCP通信的基本原理?

  • 客户端怎么发,服务端就应该怎么收。
  • 客户端如果没有消息,服务端会进入阻塞等待。
  • Socket一方关闭或者出现异常、对方Socket也会失效或者出错。(严格按照面向连接的 点对点 端对端的通信)

TCP通信-多发多收消息

本案例实现了多发多收,那么是否可以同时接收多个客户端的消息?

  • 不可以的。
  • 因为服务端现在只有一个线程,只能与一个客户端进行通信。

TCP通信-同时接受多个客户端消息

之前我们的通信是否可以同时与多个客户端通信,为什么?

  • 不可以的,单线程每次只能处理一个客户端的Socket通信

如何才可以让服务端可以处理多个客户端的通信需求?

  • 引入多线程。

同时处理多个客户端消息

  • 主线程定义了循环负责接收客户端Socket管道连接
  • 每接收到一个Socket通信管道后分配一个独立的线程负责处理它。

客户端还是一样,一直发消息不用修改,主要修改服务端。

测试运行(将客户端程序设置为允许多开,模拟多个客户端发消息给一个服务端)

TCP通信-使用线程池优化

目前的通信架构模型

目前的通信架构存在什么问题?

  • 客户端与服务端的线程模型是: N-N的关系。(10个客户端发消息就要10个服务端来收)
  • 客户端并发越多,系统瘫痪的越快。

引入线程池处理多个客户端消息

客户端还是一样,一直发消息不用修改,主要修改服务端。

每接收一个socket对象就封装成一个任务对象,然后把任务交给线程池分配线程去排队处理它,这样就不用担心线程过多的情况发生了。

服务端

任务对象读取消息

测试运行(将客户端程序设置为允许多开)

前三个客户端会有三个核心线程(正式工)去处理,到第四 五个客户端就会进到任务队列里排队,到第六个客户端因为核心线程还在处理,任务队列也满了,就启动临时线程(临时工)处理,在四五六中随机挑一个进行处理,到八个客户端就会启用抛弃策略(三个核心线程再忙,两个临时线程也在忙,任务队列也满了),如果想要任务队列里的被处理,就要断开其中的核心线程或者临时线程。

使用线程池的优势在哪里?

  • 服务端可以复用线程处理多个客户端,可以避免系统瘫痪。
  • 适合客户端通信时长较短的场景。

TCP通信实战案例-即时通信

即时通信是什么含义,要实现怎么样的设计?

  • 即时通信,是指一个客户端的消息发出去,其他客户端可以接收到。
  • 之前我们的消息都是发给服务端的。
  • 即时通信需要进行端口转发的设计思想。
  • 服务端需要把在线的Socket管道存储起来
  • 一旦收到一个消息要推送给其他管道

例如微信发消息,不可能一个客户端直接发给另一个客户端,那相当于一个客户端就是一个服务器,没这么厉害,只能是客户端发消息给服务器,然后服务器把消息转发到另一个客户端来实现。

即时通信-端口转发

客户端:客户端需要两个线程,一个主线程负责发送消息,一个子线程负责接收服务端转发过来的消息,如果没消息过来子线程就会在那一直等。

服务端:需要一个静态集合来存储客户端socket管道,有管道来连接就加入到管道集合内,使用子线程读取消息,并将消息转发给给全部客户端

测试运行:(将客户端程序设置为允许多开)

客户端发送消息后会先到服务端,服务端将客户端socket端口加入到集合内,启用子线程将消息打印并将循环socket集合将消息转发到每一个客户端,由于客户端没有排除自己的端口,所以也会接收到自己发的消息。这实现的是相当于群发消息,如果要私法消息选择指定socket端口发送即可。

TCP通信实战案例-模拟BS系统

1、之前的客户端都是什么样的

  • 其实就是CS架构,客户端实需要我们自己开发实现的。

2、BS结构是什么样的,需要开发客户端吗?

  • 浏览器访问服务端,不需要开发客户端。

实现BS开发

注意:服务器必须给浏览器响应HTTP协议格式的数据,否则浏览器不识别。

HTTP响应数据的协议格式:就是给浏览器显示的网页信息


测试运行,运行服务端即可,B/S无需开发客户端,浏览器输入IP和端口连接到服务端


注:

该内容是根据B站黑马程序员学习时所记,相关资料可在B站查询:Java入门基础视频教程,java零基础自学就选黑马程序员Java入门教程(含Java项目和Java真题)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值