文章目录
黑马全套Java教程(一)
黑马全套Java教程(二)
黑马全套Java教程(三)
黑马全套Java教程(四)
黑马全套Java教程(五)
黑马全套Java教程(六)
黑马全套Java教程(七)
黑马全套Java教程(八)
本博客开始记录黑马教学的Java入门新视频,从d168开始,博客只做代码记录,方便后续快速复习,视频链接
36 多线程
36.1 多线程的创建
- 方式一:继承Thread类
- 方式二:实现Runnable接口
- 方式三:JDK 5.0新增,实现Callable接口
例:方法一
- 继承Thread类
- 重写run方法
- 创建线程对象
- 调用start()方法启动
package d1_create;
/**
* 目标:多线程的创建爱你方式一:继承Thread类实现
* */
public class ThreadDemo1 {
public static void main(String[] args) {
//3. new一个新线程对象
Thread t = new MyThread();
// 4. 调用start方法启动线程(执行的还是run方法)
t.start();
for(int i = 0; i < 5; i++){
System.out.println("主线程执行输出:" + i);
}
}
}
//1. 定义一个线程类继承Thread类
class MyThread extends Thread{
// 2. 重写run方法,里面是定义线程以后要干啥
@Override
public void run(){
for(int i = 0; i < 5; i++){
System.out.println("子线程执行输出:" + i);
}
}
}
为什么调用run,而不是调用start:run会当成单线程执行
优点:代码简单
缺点:存在单继承的局限性,线程类继承Thread后,不能继承其他类,不便于扩展
方法二:实现runnable接口
- 定义一个线程人物类MyRunnable实现接口,重写run()方法
- 创建MyRunnable对象
- 把MyRunnable任务对象交给Thread线程对象处理
- 调用线程对象的start()方法启动线程
package d1_create;
/*
* 目标:学会线程的创建方式二
* */
public class ThreadDemo2 {
public static void main(String[] args) {
// 3. 创建一个任务对象
Runnable target = new MyRunnable();
//4. 把任务对象交给Thread处理
Thread t = new Thread(target);
//5. 启动线程
t.start();
for(int i = 0; i < 10; i++){
System.out.println("主线程执行输出:" + i);
}
}
}
//1. 定义一个线程任务类,实现Runnable接口
class MyRunnable implements Runnable{
// 2. 重写run方法,定义线程的执行任务
@Override
public void run(){
for(int i = 0; i < 10; i++){
System.out.println("子线程执行输出:" + i);
}
}
}
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的
package d1_create;
/*
* 目标:学会线程的创建方式二(匿名内部类方式实现,语法形式)
* */
public class ThreadDemo2Other {
public static void main(String[] args) {
// 3. 创建一个任务对象
Runnable target = new Runnable(){
@Override
public void run(){
for(int i = 0; i < 10; i++){
System.out.println("子线程执行输出:" + i);
}
}
};
//4. 把任务对象交给Thread处理
Thread t = new Thread(target);
//5. 启动线程
t.start();
for(int i = 0; i < 10; i++){
System.out.println("主线程执行输出:" + i);
}
}
}
其他写法:
package d1_create;
/*
* 目标:学会线程的创建方式二(匿名内部类方式实现,语法形式)
* */
public class ThreadDemo2Other {
public static void main(String[] args) {
// 3. 创建一个任务对象
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程1执行输出:" + i);
}
}
};
Thread t = new Thread(target);
t.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程2执行输出:" + i);
}
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("子线程3执行输出:" + i);
}
}).start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行输出:" + i);
}
}
}
前两种线程创建方式都存在一个问题:
- 它们重写的run方法均不能直接返回结果
- 不适合需要返回线程执行结果的业务场景
方案三:利用Callable、FutureTask接口实现
- 得到任务对象
- 把线程任务对象交给Thread处理
- 调用Thread的start方法启动线程,执行任务
- 线程执行完毕后,通过FutureTask的get方法去获取任务执行的结果
package d1_create;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/*
* 目标:方式三,实现Callable接口,结合FutureTask完成
* */
public class ThreadDemo3 {
public static void main(String[] args) {
// 3. 创建Callable任务对象
Callable<String> call = new MyCallable(100);
// 4. 把Callable任务对象 交给 FutureTask对象
// FutureTask对象的作用1:是Runnable的对象(实现了Runnable接口),可以交给Thread了
// FUtureTask对象的作用2:可以在线程执行完毕之后通过调用其get方法得到线程执行完毕的结果
FutureTask<String> f1 = new FutureTask<>(call);
// 5. 交给线程处理
Thread t1 = new Thread(f1);
// 6. 启动线程
t1.start();
Callable<String> call2 = new MyCallable(200);
FutureTask<String> f2 = new FutureTask<>(call2);
Thread t2 = new Thread(f2);
t2.start();
try {
//如果f1任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果
String rs1 = f1.get();
System.out.println("第一个结果:" + rs1);
} catch (Exception e){
e.printStackTrace();
}
try {
//如果f2任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果
String rs2 = f2.get();
System.out.println("第一个结果:" + rs2);
} catch (Exception e){
e.printStackTrace();
}
}
}
//1. 定义一个人物类,实现Callable接口 应该声明线程任务执行完毕后的结果的数据类型
class MyCallable implements Callable<String>{
private int n;
public MyCallable(int n) {
this.n = n;
}
//2. 重写call方法
@Override
public String call() throws Exception{
int sum = 0;
for (int i = 0; i <= n; i++) {
sum += i;
}
return "子线程的结果是:" + sum;
}
}
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。可以在线程执行完毕之后获取线程执行的结果。
缺点:编程复杂
36.2 Thread的常用方法
MyThread.java
package d1_create;
public class MyThread extends Thread{
@Override
public void run(){
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出:" + i);
}
}
}
ThreadDemo1.java
package d1_create;
/**
* 目标:多线程的创建爱你方式一:继承Thread类实现
* */
public class ThreadDemo1 {
public static void main(String[] args) {
Thread t1 = new MyThread();
//t1.setName("1号");
t1.start();
System.out.println(t1.getName());
Thread t2 = new MyThread();
t2.setName("2号");
t2.start();
System.out.println(t2.getName());
//哪个线程执行它,它就得到哪个线程对象
//主线程的名称叫main
Thread m = Thread.currentThread();
System.out.println(m.getName());
for(int i = 0; i < 5; i++){
System.out.println("main线程输出:" + i);
}
}
}
sleep()
package d1_create;
public class ThreadDemo2 {
public static void main(String[] args) throws Exception{
for (int i = 0; i <= 5; i++) {
System.out.println("输出" + i);
if(i == 3){
Thread.sleep(3000);
}
}
}
}
36.3 线程安全
线程安全问题出现的原因?
- 存在多线程并发
- 同时访问共享资源
- 存在修改共享资源
案例:取钱业务
Account.java
package d3_thread_safe;
public class Account {
private String cardId;
private double money; //账户余额
public Account(){}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
//小红 小明
public void drawMoney(double money){
// 0. 获取是谁来取钱,线程名就是人名
String name = Thread.currentThread().getName();
//1. 判断账户是否够钱
if(this.money >= money){
//2. 取钱
System.out.println(name + "来取钱成功,吐出: " + money);
//3. 更新余额
this.money -= money;
System.out.println(name + "取钱后剩余:" + this.money);
} else{
//4. 余额不足
System.out.println(name + "来取钱,余额不足!");
}
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
DrawThread.java
package d3_thread_safe;
/*
* 取钱的线程类
* */
public class DrawThread extends Thread{
private Account acc;
public DrawThread(Account acc, String name){
super(name);
this.acc = acc;
}
@Override
public void run(){
//小明 小红: 取钱
acc.drawMoney(1000000);
}
}
ThreadDemo.java
package d3_thread_safe;
public class ThreadDemo {
public static void main(String[] args) {
//1. 定义线程类,创建一个共享的账户对象
Account acc = new Account("ICBC-111", 1000000);
//2. 创建2个线程对象,代表小明和小红同时进来了
new DrawThread(acc, "小明").start();
new DrawThread(acc, "小红").start();
}
}
结果出现线程安全问题:
36.4 线程同步
加锁: 把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。
方式一:同步代码块。把出现线程安全问题的核心代码给上锁;每次只能一个线程进入,执行完毕后自动解锁,其他线程才可进来执行
选中代码块,Ctrl+Alt+T键
小明来取钱成功,吐出: 1000000.0
小明取钱后剩余:0.0
小红来取钱,余额不足!
锁对象的规范要求:
- 规范上,建议使用共享资源作为锁对象
- 对于实例方法建议使用this作为锁对象
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象
方式二:同步方法。把出现线程安全的核心方法给上锁;每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
//小红 小明
public synchronized void drawMoney(double money){ //锁加在方法上
// 0. 获取是谁来取钱,线程名就是人名
String name = Thread.currentThread().getName();
//1. 判断账户是否够钱
if(this.money >= money){
//2. 取钱
System.out.println(name + "来取钱成功,吐出: " + money);
//3. 更新余额
this.money -= money;
System.out.println(name + "取钱后剩余:" + this.money);
} else{
//4. 余额不足
System.out.println(name + "来取钱,余额不足!");
}
}
方法三:lock锁
//final修饰后:锁对象是唯一不可替换的
private final Lock lock = new ReentrantLock();
public void drawMoney(double money){
// 0. 获取是谁来取钱,线程名就是人名
String name = Thread.currentThread().getName();
lock.lock();
try {
//1. 判断账户是否够钱
if(this.money >= money){
//2. 取钱
System.out.println(name + "来取钱成功,吐出: " + money);
//3. 更新余额
this.money -= money;
System.out.println(name + "取钱后剩余:" + this.money);
} else{
//4. 余额不足
System.out.println(name + "来取钱,余额不足!");
}
} finally {
lock.unlock();
}
}
36.5 线程通信
线程通信就是线程间相互发送数据,线程通信通常通过共享一个数据的方式实现。
线程间会根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做
Account.java
package d7_thread_comunication;
public class Account {
private String cardId;
private double money; //账户余额
public Account(){}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public synchronized void drawMoney(double money){
try {
String name = Thread.currentThread().getName();
if(this.money >= money){
this.money -= money;
System.out.println(name + "来取钱" + money + "成功!余额是:" + this.money);
//没钱了
this.notifyAll(); //唤醒所有线程
this.wait(); //锁对象,让当前线程进入等待
}else{
//钱不够
this.notifyAll(); //唤醒所有线程
this.wait(); //锁对象,让当前线程进入等待
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void deposit(double money){
try {
String name = Thread.currentThread().getName();
if(this.money == 0){
this.money += money;
System.out.println(name + "存钱" + money + "成功!存钱后余额是:" + this.money);
//有钱了,唤醒别人,
this.notifyAll();
this.wait();
}else{
//有钱,不存钱
this.notifyAll();
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
DrawThread.java
package d7_thread_comunication;
/*
* 取钱的线程类
* */
public class DrawThread extends Thread{
private Account acc;
public DrawThread(Account acc, String name){
super(name);
this.acc = acc;
}
@Override
public void run(){
while(true){
acc.drawMoney(100000);
try{
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
DepositThread.java
package d7_thread_comunication;
public class DepositThread extends Thread{
private Account acc;
public DepositThread(Account acc, String name){
super(name);
this.acc = acc;
}
@Override
public void run(){
while(true){
acc.deposit(100000);
try{
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
ThreadDemo.java
package d7_thread_comunication;
public class ThreadDemo {
//目标:了解线程通信的流程
public static void main(String[] args) {
// 1. 创建账户对象,代表5个人共同的操作的账户
Account acc = new Account("ICBC-112", 0);
//2. 创建2个取钱线程
new DrawThread(acc, "小明").start();
new DrawThread(acc, "小红").start();
new DepositThread(acc, "亲爹").start();
new DepositThread(acc, "干爹").start();
new DepositThread(acc, "岳父").start();
}
}
36.6 线程池
线程池就是一个可以复用线程的技术。如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。
临时线程什么时候创建:新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
什么时候会开始拒绝任务:核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝
ThreadPoolDemo1.java
package d8_threadpool;
import java.util.concurrent.*;
//目标:自定义一个线程池对象,并测试其特性
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//1. 创建线程池对象
/*
* public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
* */
ExecutorService pool = new ThreadPoolExecutor(3, 5,
6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// 2. 给任务线程池处理
Runnable target = new MyRunnable();
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
//创建临时线程
pool.execute(target);
pool.execute(target);
//不创建,拒绝策略被触发!!!
//pool.execute(target);
//pool.execute(target);
//关闭线程池(开发中一般不会使用)
//pool.shutdownNow(); //立即关闭,即使任务没有完成,丢失任务的
pool.shutdown(); //会等待全部任务执行完毕之后再关闭
}
}
MyRunnable.java
package d8_threadpool;
public class MyRunnable implements Runnable{
@Override
public void run(){
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出了:HelloWorld ==> " + i);
}
try{
System.out.println(Thread.currentThread().getName() + "本任务与线程绑定了,线程进入休眠了");
Thread.sleep(10000000);
}catch(Exception e){
e.printStackTrace();
}
}
}
例2:
ThreadPoolDemo2.java
package d8_threadpool;
import java.util.concurrent.*;
//目标:自定义一个线程池对象,并测试其特性
public class ThreadPoolDemo2 {
public static void main(String[] args) throws Exception{
//1. 创建线程池对象
/*
* public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
* */
ExecutorService pool = new ThreadPoolExecutor(3, 5,
6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
// 2. 给任务线程池处理
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
}
}
MyCallable.java
package d8_threadpool;
import java.util.concurrent.Callable;
//1. 定义一个任务类 实现Callable接口 应该声明线程任务执行完毕后的结果的数据类型
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n){
this.n = n;
}
//2.重写call方法(任务方法)
@Override
public String call() throws Exception{
int sum = 0;
for (int i = 0; i <= n ; i++) {
sum += i;
}
return Thread.currentThread().getName() + "执行1-" + n + "的和,结果是:" + sum;
}
}
方法二:Executors创建线程池线程池
ThreadPoolDemo3.java
package d8_threadpool;
import java.util.concurrent.*;
//目标:使用Executors的工具方法直接得到一个线程池对象
public class ThreadPoolDemo3 {
public static void main(String[] args) throws Exception{
//1. 创建固定线程数据的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable()); //已经没有多余线程了
}
}
36.7 定时器
定时器是一种控制任务延时调用,或者周期调用的技术
作用:闹钟、定时邮件发送
定时器的实现方式:
方式一:Timer
方式二:ScheduledExecutorService
方法一:Timer定时器是单线程执行,会有问题
package d9_timer;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo1 {
public static void main(String[] args) {
//1. 创建Timer定时器
Timer timer = new Timer(); //定时器本身就是一个单线程
//2. 调用方法,处理定时任务
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行A " + new Date());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 3000, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行B " + new Date());
}
}, 3000, 2000);
}
}
Timer-0执行A Sat Nov 05 20:47:33 CST 2022
Timer-0执行B Sat Nov 05 20:47:38 CST 2022
Timer-0执行A Sat Nov 05 20:47:38 CST 2022
Timer-0执行A Sat Nov 05 20:47:43 CST 2022
Timer-0执行B Sat Nov 05 20:47:48 CST 2022
Timer-0执行A Sat Nov 05 20:47:48 CST 2022
方法二:ScheduledExecutorService定时器
例:线程池做定时器
package d9_timer;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class TimerDemo2 {
public static void main(String[] args) {
//1. 创建ScheduledExecutorService线程池,做定时器
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
//2. 开启定时任务
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行输出:AAA " + new Date());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 0, 2, TimeUnit.SECONDS); //2秒
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行输出:BBB " + new Date());
}
}, 0, 2, TimeUnit.SECONDS); //2秒
}
}
36.8 并发并行、生命周期
并发剑法
37 网络编程
网络编程可以让程序与网络上的其他设备中的程序进行数据交互
常见的通信模式:B/S,C/S
37.1 网络通信的三要素
IP地址:设备在网络中的地址,是唯一的标识
端口:应用程序在设备中唯一的标识
协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议
要素一:IP地址
package d1_inetAddress;
import java.net.InetAddress;
/*
*目标:InetAddress类概述
* 一个该类的对象就代表一个IP地址对象
*
*
* InetAddress类的方法:
* static InetAddress getLocalHost()
* * 获取本地主机IP地址对象
* static InetAddress getByName(String host)
* * 根据IP地址字符串或主机名获得对应的IP地址对象
* String getHostName()
* * 获得主机名
* String getHostAddress()
* * 获得IP地址字符串
*/
public class InetAddressDemo01 {
public static void main(String[] args) throws Exception{
//1. 获取本机地址对象
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName()); //主机名
System.out.println(ip1.getHostAddress()); //ip
//2. 获取域名ip对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
//3. 获取公网IP对象
InetAddress ip3 = InetAddress.getByName("110.242.68.4");
System.out.println(ip3.getHostName());
System.out.println(ip3.getHostAddress());
//4. 判断是否能通:ping 5s之前测试是否可通
System.out.println(ip3.isReachable(5000));
}
}
LAPTOP-7NLRI4B2
192.168.111.1
www.baidu.com
110.242.68.3
110.242.68.4
110.242.68.4
true
要素二:端口号
要素三:协议
37.2 UDP通信
- UDP是一种无连接、不可靠传输的协议
- 将数据源IP、目的地IP和端口封装成数据包,不需要建立连接
- 每个数据包的大小限制在64KB内
- 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
- 可以广播发送,发送数据结束时无需释放资源,开销小,速度快
- 使用场景:语音通话,视频会话
例:UDP实现一发一收
ServerDemo2.java
package d2_udp1;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
//接收端
public class ServerDemo2 {
public static void main(String[] args) throws Exception{
System.out.println("=====服务端启动=====");
//1. 创建接收端对象,注册端口
DatagramSocket socket = new DatagramSocket(8888);
//2. 创建一个数据包对象接收数据(韭菜盘子)
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
//3. 等待,接受数据
socket.receive(packet);
//4. 取出数据即可
//读取多少倒出多少
int len = packet.getLength();
String rs = new String(buffer, 0, len);
System.out.println("收到了:" + rs);
//获取发送端的ip和端口
String ip = packet.getSocketAddress().toString();
System.out.println("对方地址:" + ip);
int port = packet.getPort();
System.out.println("对方端口:" + port);
socket.close();
}
}
ClientDemo1.java
package d2_udp1;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
//发送端 一发一收
public class ClientDemo1 {
public static void main(String[] args) throws Exception{
System.out.println("=====客户端启动=====");
//1. 创建发送端对象 发送端自带默认的端口号
DatagramSocket socket = new DatagramSocket(6666); //可指定可不指定端口
//2. 创建一个数据报对象封装数据
/*
* public DatagramPacket(byte buf[], int length,
* InetAddress address, int port)
* 参数一:封装要发送的数据
*参数二:发送的数据大小
* 参数三:服务端的主机IP地址
* 参数四:服务端的端口
* */
byte[] buffer = "我是一个快乐的韭菜".getBytes();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getLocalHost(), 8888);
//3. 发送数据出去
socket.send(packet);
socket.close();
}
}
例:UDP实现多发多收
ServerDemo2.java
package d3_udp2;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
//接收端
public class ServerDemo2 {
public static void main(String[] args) throws Exception{
System.out.println("=====服务端启动=====");
//1. 创建接收端对象,注册端口
DatagramSocket socket = new DatagramSocket(8888);
//2. 创建一个数据包对象接收数据(韭菜盘子)
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
//3. 等待,接受数据
socket.receive(packet);
//4. 取出数据即可
//读取多少倒出多少
int len = packet.getLength();
String rs = new String(buffer, 0, len);
System.out.println("收到了来自:" + packet.getAddress() + ",对方端口:" + packet.getPort() + "的消息:" + rs);
}
}
}
ClientDemo1.java
package d3_udp2;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
//发送端 多发多收
public class ClientDemo1 {
public static void main(String[] args) throws Exception{
System.out.println("=====客户端启动=====");
//1. 创建发送端对象 发送端自带默认的端口号
DatagramSocket socket = new DatagramSocket(6666);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
if("exit".equals(msg)){
System.out.println("离线成功!");
socket.close();
break;
}
//2. 创建一个数据报对象封装数据
byte[] buffer = msg.getBytes();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getLocalHost(), 8888);
//3. 发送数据出去
socket.send(packet);
}
}
}
UDP实现广播:
- 发送端发送的数据包的目的地写的是广播地址,且指定端口。(255.255.255.255,9999)
- 本机所在网段的其他主机的程序只要匹配端口成功就可以收到消息了。(9999)
ClientDemo1.java
package d4_udp3_broadcast;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
//发送端 多发多收
public class ClientDemo1 {
public static void main(String[] args) throws Exception{
System.out.println("=====客户端启动=====");
//1. 创建发送端对象 发送端自带默认的端口号
DatagramSocket socket = new DatagramSocket(6666);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
if("exit".equals(msg)){
System.out.println("离线成功!");
socket.close();
break;
}
//2. 创建一个数据报对象封装数据
byte[] buffer = msg.getBytes();
//填入接收方的IP和端口
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("255.255.255.255"), 9999);
//3. 发送数据出去
socket.send(packet);
}
}
}
ServerDemo2.java
package d4_udp3_broadcast;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
//接收端
public class ServerDemo2 {
public static void main(String[] args) throws Exception{
System.out.println("=====服务端启动=====");
//1. 创建接收端对象,注册端口
DatagramSocket socket = new DatagramSocket(9999);
//2. 创建一个数据包对象接收数据(韭菜盘子)
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
//3. 等待,接受数据
socket.receive(packet);
//4. 取出数据即可
//读取多少倒出多少
int len = packet.getLength();
String rs = new String(buffer, 0, len);
System.out.println("收到了来自:" + packet.getAddress() + ",对方端口:" + packet.getPort() + "的消息:" + rs);
}
}
}
UDP实现组播:
- 发送端的数据包的目的地是组播IP。(例如:224.0.1.1,端口:9999)
- 接收端必须绑定该IP(224.0.1.1),端口还要对应发送端的目的端口9999,这样即可接收该组播消息
- DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP
ServerDemo2.java
package d5_udp4_multicast;
import java.net.*;
//接收端
public class ServerDemo2 {
public static void main(String[] args) throws Exception{
System.out.println("=====服务端启动=====");
//1. 创建接收端对象,注册端口
MulticastSocket socket = new MulticastSocket(9999);
//把当前接收端加入到一个组播组当中,绑定对应的组播消息的组播IP
socket.joinGroup(InetAddress.getByName("224.0.1.1")); //两个方法都可以
//Socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.1.1"), 9999),
// NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));
//2. 创建一个数据包对象接收数据(韭菜盘子)
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
//3. 等待,接受数据
socket.receive(packet);
//4. 取出数据即可
//读取多少倒出多少
int len = packet.getLength();
String rs = new String(buffer, 0, len);
System.out.println("收到了来自:" + packet.getAddress() + ",对方端口:" + packet.getPort() + "的消息:" + rs);
}
}
}
ClientDemo1.java
package d5_udp4_multicast;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
//发送端 多发多收
public class ClientDemo1 {
public static void main(String[] args) throws Exception{
System.out.println("=====客户端启动=====");
//1. 创建发送端对象 发送端自带默认的端口号
DatagramSocket socket = new DatagramSocket(6666);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
if("exit".equals(msg)){
System.out.println("离线成功!");
socket.close();
break;
}
//2. 创建一个数据报对象封装数据
byte[] buffer = msg.getBytes();
//填入接收方的IP和端口
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("224.0.1.1"), 9999);
//3. 发送数据出去
socket.send(packet);
}
}
}
37.3 TCP通信
例:TCP实现一发一收
ServerDemo2.java
package d5_socket1;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
//目标:开发socket网络编程入门的艾玛的服务端,实现接收消息
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("=====服务端启动成功=====");
//1. 注册端口
ServerSocket serverSocket = new ServerSocket(7777);
//2. 必须调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
//3. 从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
//4. 把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//5. 按照行读取消息
String msg;
if((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ClientDemo1.java
package d5_socket1;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
//目标:完成Socket网络编程入门案例的客户端开发,实现1发1收
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("=====客户端启动成功=====");
//1. 创建Socket通信管道请求有服务端的连接
//public Socket(String host, int port)
//参数一:服务器的IP
//参数二:服务端的端口
Socket socket = new Socket("127.0.0.1",7777);
//2. 从socket通信管道中得到一个字节输出流,负责发送数据
OutputStream os = socket.getOutputStream();
//3. 把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
//4. 发送消息
ps.println("我是TCP的客户端");
ps.flush();
//关闭资源
//socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
例:TCP实现多发多收
ServerDemo2.java
package d6_socket2;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
//目标:开发socket网络编程入门的艾玛的服务端,实现接收消息
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("=====服务端启动成功=====");
//1. 注册端口
ServerSocket serverSocket = new ServerSocket(7777);
//2. 必须调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
//3. 从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
//4. 把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//5. 按照行读取消息
String msg;
while((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ClientDemo1.java
package d6_socket2;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
//目标:完成Socket网络编程入门案例的客户端开发,实现多发多收
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("=====客户端启动成功=====");
//1. 创建Socket通信管道请求有服务端的连接
//public Socket(String host, int port)
//参数一:服务器的IP
//参数二:服务端的端口
Socket socket = new Socket("127.0.0.1",7777);
//2. 从socket通信管道中得到一个字节输出流,负责发送数据
OutputStream os = socket.getOutputStream();
//3. 把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
//4. 发送消息
System.out.println("请说:");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
例:实现服务端接收多个客户端
ServerDemo2.java
package d7_socket3;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
//目标:实现服务端可以同时处理多个客户端的消息
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("=====服务端启动成功=====");
//1. 注册端口
ServerSocket serverSocket = new ServerSocket(7777);
//定义一个死循环由主线程负责不断的接收客户端的Socket管道连接
while (true) {
//2. 每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + ":它来了,上线了!");
//3. 开始创建独立线程处理哦socket
new ServerReaderThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ClientDemo1.java
package d7_socket3;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
//目标:完成Socket网络编程入门案例的客户端开发,实现多发多收
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("=====客户端启动成功=====");
//1. 创建Socket通信管道请求有服务端的连接
//public Socket(String host, int port)
//参数一:服务器的IP
//参数二:服务端的端口
Socket socket = new Socket("127.0.0.1",7777);
//2. 从socket通信管道中得到一个字节输出流,负责发送数据
OutputStream os = socket.getOutputStream();
//3. 把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
//4. 发送消息
System.out.println("请说:");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ServerReaderThread.java
package d7_socket3;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run(){
try {
//3. 从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
//4. 把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//5. 按照行读取消息
String msg;
while((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
}
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + ":下线了!");
}
}
}
37.4 线程池优化
ServerDemo2.java
package d8_socket4;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
//目标:实现服务端可以同时处理多个客户端的消息
public class ServerDemo2 {
//使用静态变量记住一个线程池对象
private static ExecutorService pool = new ThreadPoolExecutor(3,5,6,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
System.out.println("=====服务端启动成功=====");
//1. 注册端口
ServerSocket serverSocket = new ServerSocket(6666);
//定义一个死循环由主线程负责不断的接收客户端的Socket管道连接
while (true) {
//2. 每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + ":它来了,上线了!");
//任务对象负责读取消息
Runnable target = new ServerReaderRunnable(socket);
pool.execute(target);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ClientDemo1.java
package d8_socket4;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
//目标:使用线程池优化,实现通信
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("=====客户端启动成功=====");
//1. 创建Socket通信管道请求有服务端的连接
//public Socket(String host, int port)
//参数一:服务器的IP
//参数二:服务端的端口
Socket socket = new Socket("127.0.0.1",6666);
//2. 从socket通信管道中得到一个字节输出流,负责发送数据
OutputStream os = socket.getOutputStream();
//3. 把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
//4. 发送消息
System.out.println("请说:");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
ServerReaderRunnable.java
package d8_socket4;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run(){
try {
//3. 从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
//4. 把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//5. 按照行读取消息
String msg;
while((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
}
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + ":下线了!");
}
}
}
案例:即时通信
即时通信的含义,要怎么设计:
- 即时通信,是指一个客户端的消息发出去,其他客户端可以接收到
- 即时通信需要进行端口转发的设计思想
- 服务端需要把在线的Socket管道存储起来
- 一旦收到一个消息要推送给其他管道
ServerReaderThread.java
package d9_tcp_sms;
import d8_socket4.ServerReaderRunnable;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
//目标:实现服务端可以同时处理多个客户端的消息
public class ServerDemo2 {
//定义一个静态的List集合存储当前全部在线的socket管道
public static List<Socket> allOnlineSockets = new ArrayList<>();
public static void main(String[] args) throws Exception{
System.out.println("=====服务端启动成功=====");
//1. 注册端口
ServerSocket serverSocket = new ServerSocket(6666);
//定义一个死循环由主线程负责不断的接收客户端的Socket管道连接
while (true) {
//2. 每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + ":上线了!");
//任务对象负责读取消息
Runnable target = new ServerReaderRunnable(socket);
allOnlineSockets.add(socket); //上线完成
//3. 创建一个独立的线程来单独处理这个socket管道
new ServerReaderThread(socket).start();
}
}
}
class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//3. 从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
//4. 把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//5. 按照行读取消息
String msg;
while ((msg = br.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress() + "发来了:" + msg);
//把这个消息进行端口转发给全部客户端socket管道
sendMsgToAll(msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + ":下线了!");
ServerDemo2.allOnlineSockets.remove(socket);
}
}
private void sendMsgToAll(String msg) throws Exception{
for(Socket socket : ServerDemo2.allOnlineSockets){
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println(msg);
ps.flush();
}
}
}
ClientDemo1.java
package d9_tcp_sms;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class ClientDemo1 {
public static void main(String[] args) throws Exception {
System.out.println("=====客户端启动成功=====");
//1. 创建Socket通信管道请求有服务端的连接
Socket socket = new Socket("127.0.0.1", 6666);
//创建一个独立的线程专门负责这个客户端的读消息(服务端随时可能转发消息过来)
new ClientReaderThread(socket).start();
//2. 从socket通信管道中得到一个字节输出流,负责发送数据
OutputStream os = socket.getOutputStream();
//3. 把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
//4. 发送消息
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
}
}
class ClientReaderThread extends Thread {
private Socket socket;
public ClientReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//3. 从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
//4. 把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//5. 按照行读取消息
String msg;
while ((msg = br.readLine()) != null) {
System.out.println("收到消息:" + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + ":服务端把你踢出去了!");
}
}
}
案例:模拟BS系统
package d10_bs;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
//目标:实现服务端可以同时处理多个客户端的消息
public class BSserverDemo {
public static void main(String[] args) throws Exception{
//1. 注册端口
ServerSocket serverSocket = new ServerSocket(8080);
//2. 创建一个循环接收多个客户端的请求
while (true) {
Socket socket = serverSocket.accept();
//3. 创建一个独立的线程来单独处理这个socket管道
new ServerReaderThread(socket).start();
}
}
}
class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//浏览器 已经与本线程建立了Socket管道
//响应消息给浏览器显示
PrintStream ps = new PrintStream(socket.getOutputStream());
//必须响应HTTP协议格式数据,否则浏览器不认识消息
ps.println("HTTP/1.1 200 OK"); //协议类型和版本 响应成功的消息!
ps.println("Content-Type:text/html;charset=UTF-8"); //响应的数据类型:文本/网页
ps.println(); //必须发送一个空行
//才可以响应数据回去给浏览器
ps.println("<span style='color:red;font-size:90px'>《Java大法好》 </span>");
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
例2:利用线程池优化
package d10_bs;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
//目标:实现服务端可以同时处理多个客户端的消息
public class BSserverDemo {
//使用静态变量记住一个线程池对象
private static ExecutorService pool = new ThreadPoolExecutor(3,5,6,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) throws Exception{
//1. 注册端口
ServerSocket serverSocket = new ServerSocket(8080);
//2. 创建一个循环接收多个客户端的请求
while (true) {
Socket socket = serverSocket.accept();
//3. 创建一个独立的线程来单独处理这个socket管道
pool.execute(new ServerReaderThread(socket));
}
}
}
class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//浏览器 已经与本线程建立了Socket管道
//响应消息给浏览器显示
PrintStream ps = new PrintStream(socket.getOutputStream());
//必须响应HTTP协议格式数据,否则浏览器不认识消息
ps.println("HTTP/1.1 200 OK"); //协议类型和版本 响应成功的消息!
ps.println("Content-Type:text/html;charset=UTF-8"); //响应的数据类型:文本/网页
ps.println(); //必须发送一个空行
//才可以响应数据回去给浏览器
ps.println("<span style='color:red;font-size:90px'>《Java大法好》 </span>");
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}