B站韩顺平学Java课程——个人笔记(20章~22章)

20章——坦克大战5.0和6.0

5.0

package TankGame5;

import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Scanner;

/**
 * @author Albert
 * @version 5.0
 * @date 2023/11/25-21:23
 * @describe
 */
public class tankGame5 extends JFrame {
    //定义MyPanel
    private MyPanel mp = null;
    public static void main(String[] args) {

        new tankGame5();
    }

    public tankGame5(){
        Scanner scanner = new Scanner(System.in);
        String key = "";
        System.out.println("请输入你的选择的序号:1.新游戏  2.继续上局游戏");
        key = scanner.next();
        mp = new MyPanel(key);
        Thread thread = new Thread(mp);
        thread.start();
        this.add(mp);
        this.setSize(1300, 750);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
        this.addKeyListener(mp);

        //在JFrame中增加相应关闭窗口的处理
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.out.println("监听到关闭窗口了");
                Recorder.keepRecord();
                System.exit(0);
            }
        });
    }
}
package TankGame5;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/9-17:00
 * @describe 坦克类
 */
public class Tank {
    private int x;//x坐标
    private int y;//y坐标
    private int direct;//坦克的方向(0:向上 1:向右 2:向下 3:向左)
    boolean isLive = true;

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    private int speed = 1;//坦克的速度

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getDirect() {
        return direct;
    }

    public void setDirect(int direct) {
        this.direct = direct;
    }

    public Tank(int x, int y) {
        this.x = x;
        this.y = y;
    }

    //上下左右移动方法
    public void moveUp(){
        direct = 0;
        y -= speed;
    }
    public void moveRight(){
        direct = 1;
        x += speed;
    }
    public void moveDown(){
        direct = 2;
        y += speed;
    }
    public void moveLeft(){
        direct = 3;
        x -= speed;
    }
}
package TankGame5;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/13-18:50
 * @describe 射击子弹
 */
public class Shot implements Runnable{
    int x;//子弹的x坐标
    int y;//子弹的y坐标
    int direct;//子弹的方向
    int speed = 8;//子弹的速度

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    boolean isLive = true;//子弹是否存活

    public Shot(int x, int y, int direct) {
        this.x = x;
        this.y = y;
        this.direct = direct;
    }

    @Override
    public void run() {
        while(true){

            try {
                Thread.sleep(50);//休眠50毫秒,免得看不见子弹
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //direct 表示方向(0:向上 1:向右 2:向下 3:向左)
            switch (direct) {
                case 0:
                    y -= speed;
                    break;
                case 1:
                    x += speed;
                    break;
                case 2:
                    y += speed;
                    break;
                case 3:
                    x -= speed;
                    break;
            }
            //测试子弹坐标
            System.out.println("子弹的x坐标:" + x + " y坐标:" + y);
            //当子弹移动到面板的边界时,应该销毁(把启动的子弹线程销毁)
            if(!(x >= 0 && x <= 1000 && y >= 0 && y <= 750 && isLive)){
                isLive = false;
                break;
            }
        }
    }
}
package TankGame5;

import java.io.*;
import java.util.Vector;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/26-11:01
 * @describe 该类用于定义相关信息和文件交互
 */
public class Recorder {
    //定义变量,记录我方坦克击毁敌人坦克数量
    private static int allEnemyTankNum = 0;
    //定义IO对象,准备写数据到文件中
    private static BufferedWriter bw = null;
    private static BufferedReader br = null;
    private static String recordFile = "src\\myRecord.txt";
    //定义敌人的坦克,放到Vector中去,因为Vector是线程安全的
    private static Vector<EnemyTank> enemyTanks = null;
    //定义一个Node的Vector,用于保存敌人的信息node
    private static Vector<Node> nodes = new Vector<>();

    //写一个方法恢复敌人坦克数据
    //该方法在继续上局游戏时使用即可
    public static Vector<Node> getNodesAndEnemyTankRec() {
        try {
            br = new BufferedReader(new FileReader(recordFile));
            allEnemyTankNum = Integer.parseInt(br.readLine());
            //循环读取,生成node集合
            String line = "";
            while ((line = br.readLine()) != null) {
                String[] xyd = line.split(" ");
                Node node = new Node(
                        Integer.parseInt(xyd[0]),
                        Integer.parseInt(xyd[1]),
                        Integer.parseInt(xyd[2])
                );
                nodes.add(node);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return nodes;
    }

    public static void setEnemyTanks(Vector<EnemyTank> enemyTanks) {
        Recorder.enemyTanks = enemyTanks;
    }

    //增加一个方法,当游戏退出时,我们将allEnemyTankNum保存到recordFile
    //接下来对keepRecord进行升级,记录敌人坦克的坐标与方向
    public static void keepRecord() {
        try {
            bw = new BufferedWriter(new FileWriter(recordFile));
            bw.write(allEnemyTankNum + "\r\n");

            //遍历敌人坦克的Vector,然后根据情况保存即可
            //根据OOP,我们应该在当前类定义一个敌人坦克Vector属性,然后通过setXXX得到
            for (int i = 0; i < enemyTanks.size(); i++) {
                //取出敌人坦克
                EnemyTank enemyTank = enemyTanks.get(i);
                if (enemyTank.isLive) {
                    //保存该坦克信息
                    String record = enemyTank.getX() + " " + enemyTank.getY() + " " + enemyTank.getDirect();
                    //写入到文件
                    bw.write(record + "\r\n");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bw != null) {
                    bw.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static int getAllEnemyTankNum() {
        return allEnemyTankNum;
    }

    public static void setAllEnemyTankNum(int allEnemyTankNum) {
        Recorder.allEnemyTankNum = allEnemyTankNum;
    }

    //每当击毁一个敌方坦克,该值自增1
    public static void addAllEnemyTankNum() {
        Recorder.allEnemyTankNum++;
    }
}
package TankGame5;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/26-11:46
 * @describe 一个Node对象表示一个敌人坦克信息
 */
public class Node {
    private int x;
    private int y;

    private int direct;

    public Node(int x, int y, int direct) {
        this.x = x;
        this.y = y;
        this.direct = direct;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getDirect() {
        return direct;
    }

    public void setDirect(int direct) {
        this.direct = direct;
    }
}
package TankGame5;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/9-17:00
 * @describe 坦克大战绘图区
 */
public class MyPanel extends JPanel implements KeyListener, Runnable{
    //定义我的坦克
    Hero hero = null;

    //定义敌人的坦克,放到Vector中去,因为Vector是线程安全的
    Vector<EnemyTank> enemyTanks = new Vector<>();
    //定义一个存放Node对象的Vector,用于恢复敌人坦克坐标与方向
    Vector<Node> nodes = new Vector<>();
    int enemySize = 4;//敌人坦克数量

    //定义一个Vector,用于存放炸弹,当子弹集中坦克时,加入一个Boom对象到booms
    Vector<Bomb> bombs = new Vector<>();
    //定义三张炸弹图片,用于显示爆炸效果
    Image image1 = null;
    Image image2 = null;
    Image image3 = null;
    public MyPanel(String key) {
        nodes = Recorder.getNodesAndEnemyTankRec();
        hero = new Hero(500, 100);
        hero.setSpeed(6);
        Recorder.setEnemyTanks(enemyTanks);//给Recorder类记录
        switch (key){
            case "1":
                Recorder.setAllEnemyTankNum(0);
                for(int i = 0; i < enemySize;i++){
                    EnemyTank enemyTank = new EnemyTank(100 * (i + 1), 0);
                    enemyTank.setEnemyTanks(enemyTanks);
                    enemyTanks.add(enemyTank);
                    //设置方向
                    enemyTanks.get(i).setDirect(2);
                    //启动线程
                    new Thread(enemyTank).start();
                    //给该敌方坦克加入一颗子弹
                    Shot shot = new Shot(enemyTanks.get(i).getX() + 20, enemyTanks.get(i).getY() + 60, enemyTanks.get(i).getDirect());
                    enemyTanks.get(i).shots.add(shot);
                    new Thread(shot).start();
                }
                break;
            case "2":
                for(int i = 0; i < nodes.size();i++){
                    Node node = nodes.get(i);
                    EnemyTank enemyTank = new EnemyTank(node.getX(), node.getY());
                    enemyTank.setEnemyTanks(enemyTanks);
                    enemyTanks.add(enemyTank);
                    //设置方向
                    enemyTanks.get(i).setDirect(node.getDirect());
                    //启动线程
                    new Thread(enemyTank).start();
                    //给该敌方坦克加入一颗子弹
                    Shot shot = new Shot(enemyTanks.get(i).getX() + 20, enemyTanks.get(i).getY() + 60, enemyTanks.get(i).getDirect());
                    enemyTanks.get(i).shots.add(shot);
                    new Thread(shot).start();
                }
                break;
            default:
                System.out.println("你的输入有误!");
        }

        //初始化图片对象
        image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/爆炸3.gif"));
        image2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/爆炸2.gif"));
        image3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/爆炸1.gif"));
    }

    //编写方法,展示我方坦克击毁敌方坦克信息
    public void showInfo(Graphics g){
        //画出玩家总成绩
        g.setColor(Color.BLACK);
        Font font = new Font("宋体", Font.BOLD, 25);
        g.setFont(font);

        g.drawString("您累计击毁敌方坦克", 1020, 30);
        drawTank(1020, 60, g, 0, 0);//画出一个地方坦克
        g.setColor(Color.BLACK);//重新设置画笔颜色为黑色
        g.drawString(Recorder.getAllEnemyTankNum() + "", 1080, 100);
    }

    public void paint(Graphics g) {
        super.paint(g);

        g.fillRect(0, 0, 1000, 750);//填充矩形,默认黑色
        showInfo(g);

        //画出我方坦克
        if(hero != null && hero.isLive) {
            drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 1);
        }
        //画出hero射出的子弹
        if (hero.shot != null && hero.shot.isLive) {
            g.drawRect(hero.shot.x, hero.shot.y, 1, 1);
        }
        //画出hero射出的多颗子弹
//        for(int i = 0;i < hero.shots.size();i++) {
//            Shot shot = hero.shots.get(i);
//            if (shot != null && shot.isLive) {
//                g.drawRect(shot.x, shot.y, 1, 1);
//            }else{//如果该对象已经无效,从集合中拿掉该对象
//                hero.shots.remove(shot);
//            }
//        }

        //如果booms集合中有对象,就画出
        try {//加这个休眠是为了解决第一炮没有动画效果的问题,给点时间加载图片
            Thread.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        for(int i = 0; i < bombs.size(); i++){
            //取出炸弹
            Bomb bomb = bombs.get(i);
            //根据当前这个Boom对象的life值去画出对应的图片
            if(bomb.life > 6){
                g.drawImage(image1, bomb.x, bomb.y, 60, 60, this);
            }else if(bomb.life > 3){
                g.drawImage(image2, bomb.x, bomb.y, 60, 60, this);
            }else{
                g.drawImage(image3, bomb.x, bomb.y, 60, 60, this);
            }
            //让生命值减少
            bomb.lifeDown();
            //如果bomb.life为0,就从bombs集合中删除
            if(bomb.life == 0){
                bombs.remove(bomb);
            }
        }
        for(int i = 0; i < enemyTanks.size();i++){
            EnemyTank t = enemyTanks.get(i);
            if(t.isLive) {
                drawTank(t.getX(), t.getY(), g, t.getDirect(), 0);//画敌方坦克
                //画出敌方坦克的所有子弹
                for (int j = 0; j < t.shots.size(); j++) {
                    //取出子弹
                    Shot shotTemp = t.shots.get(j);
                    //绘制
                    if (shotTemp.isLive) {
                        g.drawRect(shotTemp.x, shotTemp.y, 1, 1);
                    } else {
                        t.shots.remove(shotTemp);
                    }
                }
            }
        }

    }

    /**
     * @param x      坦克的左上角x坐标
     * @param y      坦克的左上角y坐标
     * @param g      画笔
     * @param direct 坦克的方向(上下左右)
     * @param type   坦克类型(敌还是友,1是友,0是敌)
     */
    public void drawTank(int x, int y, Graphics g, int direct, int type) {
        //根据不同的坦克设置不同的颜色
        switch (type) {
            case 0://敌人的坦克
                g.setColor(Color.cyan);
                break;
            case 1://我们的坦克
                g.setColor(Color.yellow);
                break;
        }
        //根据坦克的方向来画坦克
        //direct 表示方向(0:向上 1:向右 2:向下 3:向左)
        switch (direct) {
            case 0:
                g.fill3DRect(x, y, 10, 60, false);//左边轮子
                g.fill3DRect(x + 30, y, 10, 60, false);//右边轮子
                g.fill3DRect(x + 10, y + 10, 20, 40, false);//坦克盖子
                g.fillOval(x + 10, y + 20, 20, 20);//圆形盖子
                g.drawLine(x + 20, y + 30, x + 20, y);//炮筒
                break;
            case 1:
                g.fill3DRect(x, y, 60, 10, false);//上边轮子
                g.fill3DRect(x, y + 30, 60, 10, false);//下边轮子
                g.fill3DRect(x + 10, y + 10, 40, 20, false);//坦克盖子
                g.fillOval(x + 20, y + 10, 20, 20);//圆形盖子
                g.drawLine(x + 30, y + 20, x + 60, y + 20);//炮筒
                break;
            case 2:
                g.fill3DRect(x, y, 10, 60, false);//左边轮子
                g.fill3DRect(x + 30, y, 10, 60, false);//右边轮子
                g.fill3DRect(x + 10, y + 10, 20, 40, false);//坦克盖子
                g.fillOval(x + 10, y + 20, 20, 20);//圆形盖子
                g.drawLine(x + 20, y + 30, x + 20, y + 60);//炮筒
                break;
            case 3:
                g.fill3DRect(x, y, 60, 10, false);//上边轮子
                g.fill3DRect(x, y + 30, 60, 10, false);//下边轮子
                g.fill3DRect(x + 10, y + 10, 40, 20, false);//坦克盖子
                g.fillOval(x + 20, y + 10, 20, 20);//圆形盖子
                g.drawLine(x + 30, y + 20, x, y + 20);//炮筒
                break;
            default:
                System.out.println("暂时没有处理");
        }
    }

    @Override
    public void keyTyped(KeyEvent e) {

    }

    //处理wasd键按下的情况
    @Override
    public void keyPressed(KeyEvent e) {
        if(e.getKeyCode() == KeyEvent.VK_W){
            if(hero.getY() > 0) {
                hero.moveUp();
            }
        }
        if(e.getKeyCode() == KeyEvent.VK_D){
            if(hero.getX() + 60 < 1000) {
                hero.moveRight();
            }
        }
        if(e.getKeyCode() == KeyEvent.VK_S){
            if(hero.getY() + 60 < 748) {
                hero.moveDown();
            }
        }
        if(e.getKeyCode() == KeyEvent.VK_A){
            if(hero.getX() > 0) {
                hero.moveLeft();
            }
        }
        if(e.getKeyCode() == KeyEvent.VK_J){//只能发射一颗子弹
            if(hero.shot == null || !hero.shot.isLive) {
                hero.shotEnemyTank();
            }
        }

        //发射多颗子弹
//        if(e.getKeyCode() == KeyEvent.VK_J) {//加上“ && hero.shots.size() < 5”可以控制坦克只发五颗子弹
//            hero.shotEnemyTank();
//        }

        this.repaint();
    }

    @Override
    public void keyReleased(KeyEvent e) {

    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //判断是否击中了敌人坦克
            hitEnemyTank();

            //判断多颗子弹是否打中敌方坦克
            //hitEnemyTank();

            //判断我方坦克是否被击中
            hitHero();
            this.repaint();
        }
    }

    //判断我方坦克多颗子弹是否打中敌方坦克
//    public void hitEnemyTank(){
//        //遍历我方坦克子弹
//        for(int j = 0; j < hero.shots.size(); j++) {
//            Shot shot = hero.shots.get(j);
//            //判断是否击中了敌人坦克
//            if (shot != null && shot.isLive) {
//                //遍历敌人所有的坦克
//                for (int i = 0; i < enemyTanks.size(); i++) {
//                    EnemyTank enemyTank = enemyTanks.get(i);
//                    hitTank(hero.shot, enemyTank);
//                }
//            }
//        }
//    }

    //判断我方坦克单颗子弹是否打中敌方坦克
    public void hitEnemyTank(){
        if (hero.shot != null && hero.shot.isLive) {
            //遍历敌人所有的坦克
            for (int i = 0; i < enemyTanks.size(); i++) {
                EnemyTank enemyTank = enemyTanks.get(i);
                if (hitTank(hero.shot, enemyTank)){
                    Recorder.addAllEnemyTankNum();
                }
            }
        }
    }

    //判断敌方子弹是否打击到我方坦克
    public void hitHero(){
        //遍历敌人所有坦克
        for(int i = 0; i < enemyTanks.size(); i++){
            //取出敌人坦克
            EnemyTank enemyTank = enemyTanks.get(i);
            //遍历该敌人坦克的所有子弹
            for(int j = 0; j < enemyTank.shots.size(); j++){
                //取出子弹
                Shot shot = enemyTank.shots.get(j);
                if(hero.isLive && shot.isLive){
                    hitTank(shot, hero);
                }
            }
        }
    }
    public boolean hitTank(Shot shot, Tank tank){
        //判断子弹s击中坦克enemyTank
        switch (tank.getDirect()){
            case 0:
            case 2:
                if(shot.x > tank.getX() && shot.x < tank.getX() + 40
                && shot.y > tank.getY() && shot.y < tank.getY() + 60){
                    shot.isLive = false;
                    tank.isLive = false;
                    //当我的子弹击中坦克后,将enemyTank从Vector中拿掉
                    enemyTanks.remove(tank);
                    //创建Boom对象,加入到booms集合中
                    Bomb bomb = new Bomb(tank.getX(), tank.getY());
                    bombs.add(bomb);
                    return true;
                }
                break;
            case 1:
            case 3:
                if(shot.x > tank.getX() && shot.x < tank.getX() + 60
                        && shot.y > tank.getY() && shot.y < tank.getY() + 40){
                    shot.isLive = false;
                    tank.isLive = false;
                    //当我的子弹击中坦克后,将enemyTank从Vector中拿掉
                    enemyTanks.remove(tank);
                    //创建Boom对象,加入到booms集合中
                    Bomb bomb = new Bomb(tank.getX(), tank.getY());
                    bombs.add(bomb);
                    return true;
                }
                break;
        }
        return false;
    }
}
package TankGame5;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/9-17:00
 * @describe 自己的坦克
 */
public class Hero extends Tank {
    Shot shot = null;//表示一个射击行为
    //可以发射多颗子弹
    //Vector<Shot> shots = new Vector<>();
    public Hero(int x, int y) {
        super(x, y);
    }

    public void shotEnemyTank(){
//        if(shots.size() == 5){//控制坦克的子弹不大于5
//            return;
//        }
        //如果我方坦克阵亡,无法发射子弹
        if(!isLive){
            return;
        }
        switch (getDirect()){
            case 0:
                shot = new Shot(getX() + 20, getY(), 0);
                break;
            case 1:
                shot = new Shot(getX() + 60, getY() + 20, 1);
                break;
            case 2:
                shot = new Shot(getX() + 20, getY() + 60, 2);
                break;
            case 3:
                shot = new Shot(getX(), getY() + 20, 3);
                break;
        }
        //把子弹加入到shots里面去
        //shots.add(shot);
        //启动线程
        new Thread(shot).start();
    }
}
package TankGame5;

import java.util.Vector;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/9-19:02
 * @describe
 */
public class EnemyTank extends Tank implements Runnable {
    //在敌人坦克类,使用Vector保存多个Shot
    Vector<Shot> shots = new Vector<>();
    //boolean isLive = true;

    Vector<EnemyTank> enemyTanks = new Vector<>();

    public boolean isTouchEnemyTank(){
        //判断当前敌人坦克(this)方向
        switch (this.getDirect()){
            case 0://上
                //让当前敌人坦克和其他所有的敌人坦克比较
                for(int i = 0; i < enemyTanks.size(); i++){
                    //从vector中取出一个敌人坦克
                    EnemyTank enemyTank = enemyTanks.get(i);
                    //不和自己比较
                    if(enemyTank != this){
                        //如果敌人坦克是上/下
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左上角点
                            if(this.getX() >= enemyTank.getX()
                                    && this.getX() <= enemyTank.getX() + 40
                                    && this.getY() >= enemyTank.getY()
                                    && this.getY() <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右上角点
                            if(this.getX() + 40 >= enemyTank.getX()
                                    && this.getX() + 40 <= enemyTank.getX() + 40
                                    && this.getY() >= enemyTank.getY()
                                    && this.getY() <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        //如果敌人坦克是右或左
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左上角点
                                if(this.getX() >= enemyTank.getX()
                                        && this.getX() <= enemyTank.getX() + 60
                                        && this.getY() >= enemyTank.getY()
                                        && this.getY() <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右上角点
                                if(this.getX() + 40 >= enemyTank.getX()
                                        && this.getX() + 40 <= enemyTank.getX() + 60
                                        && this.getY() >= enemyTank.getY()
                                        && this.getY() <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                    }
                }
                break;
            case 1://右
                //让当前敌人坦克和其他所有的敌人坦克比较
                for(int i = 0; i < enemyTanks.size(); i++){
                    //从vector中取出一个敌人坦克
                    EnemyTank enemyTank = enemyTanks.get(i);
                    //不和自己比较
                    if(enemyTank != this){
                        //如果敌人坦克是上/下
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右上角点
                            if(this.getX() + 60 >= enemyTank.getX()
                                    && this.getX() + 60 <= enemyTank.getX() + 40
                                    && this.getY() >= enemyTank.getY()
                                    && this.getY() <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右下角点
                            if(this.getX() + 60 >= enemyTank.getX()
                                    && this.getX() + 60 <= enemyTank.getX() + 40
                                    && this.getY() + 40 >= enemyTank.getY()
                                    && this.getY() + 40 <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        //如果敌人坦克是右或左
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右上角点
                                if(this.getX() + 60 >= enemyTank.getX()
                                        && this.getX() + 60 <= enemyTank.getX() + 60
                                        && this.getY() >= enemyTank.getY()
                                        && this.getY() <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右下角点
                                if(this.getX() + 60 >= enemyTank.getX()
                                        && this.getX() + 60 <= enemyTank.getX() + 60
                                        && this.getY() + 40 >= enemyTank.getY()
                                        && this.getY() + 40 <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                    }
                }
                break;
            case 2://下
                //让当前敌人坦克和其他所有的敌人坦克比较
                for(int i = 0; i < enemyTanks.size(); i++){
                    //从vector中取出一个敌人坦克
                    EnemyTank enemyTank = enemyTanks.get(i);
                    //不和自己比较
                    if(enemyTank != this){
                        //如果敌人坦克是上/下
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左下角点
                            if(this.getX() >= enemyTank.getX()
                                    && this.getX() <= enemyTank.getX() + 40
                                    && this.getY() + 60 >= enemyTank.getY()
                                    && this.getY() + 60 <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右下角点
                            if(this.getX() + 40 >= enemyTank.getX()
                                    && this.getX() + 40 <= enemyTank.getX() + 40
                                    && this.getY() + 60 >= enemyTank.getY()
                                    && this.getY() + 60 <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        //如果敌人坦克是右或左
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左下角点
                                if(this.getX() >= enemyTank.getX()
                                        && this.getX() <= enemyTank.getX() + 60
                                        && this.getY() + 60 >= enemyTank.getY()
                                        && this.getY() + 60 <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右下角点
                                if(this.getX() + 40 >= enemyTank.getX()
                                        && this.getX() + 40 <= enemyTank.getX() + 60
                                        && this.getY() + 60 >= enemyTank.getY()
                                        && this.getY() + 60 <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                    }
                }
                break;
            case 3://左
                //让当前敌人坦克和其他所有的敌人坦克比较
                for(int i = 0; i < enemyTanks.size(); i++){
                    //从vector中取出一个敌人坦克
                    EnemyTank enemyTank = enemyTanks.get(i);
                    //不和自己比较
                    if(enemyTank != this){
                        //如果敌人坦克是上/下
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左上角点
                            if(this.getX() >= enemyTank.getX()
                                    && this.getX() <= enemyTank.getX() + 40
                                    && this.getY() >= enemyTank.getY()
                                    && this.getY() <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左下角点
                            if(this.getX() >= enemyTank.getX()
                                    && this.getX() <= enemyTank.getX() + 40
                                    && this.getY() + 40 >= enemyTank.getY()
                                    && this.getY() + 40 <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        //如果敌人坦克是右或左
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左上角点
                                if(this.getX() >= enemyTank.getX()
                                        && this.getX() <= enemyTank.getX() + 60
                                        && this.getY() >= enemyTank.getY()
                                        && this.getY() <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左下角点
                                if(this.getX() >= enemyTank.getX()
                                        && this.getX() <= enemyTank.getX() + 60
                                        && this.getY() + 40 >= enemyTank.getY()
                                        && this.getY() + 40 <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                    }
                }
                break;
        }
        return false;

    }
    public void setEnemyTanks(Vector<EnemyTank> enemyTanks) {
        this.enemyTanks = enemyTanks;
    }

    public EnemyTank(int x, int y) {
        super(x, y);
    }

    @Override
    public void run() {
        while (true) {
            //这里我们判断如果shots.size()==0,创建一颗子弹,放入到shots集合中,并启动
            if(isLive && shots.size() <= 3){
                Shot s = null;

                //判断坦克的方向,创建对应的子弹
                switch (getDirect()){
                    case 0:
                        s = new Shot(getX() + 20, getY(), 0);
                        break;
                    case 1:
                        s = new Shot(getX() + 60, getY() + 20, 1);
                        break;
                    case 2:
                        s = new Shot(getX() + 20, getY() + 60, 2);
                        break;
                    case 3:
                        s = new Shot(getX(), getY() + 20, 3);
                        break;
                }
                shots.add(s);
                new Thread(s).start();
            }
            //根据坦克的方向来继续移动
            switch (getDirect()) {
                case 0://向上
                    for (int i = 0; i < 10; i++) {
                        if (getY() > 0 && !isTouchEnemyTank()) {
                            moveUp();
                        }
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    break;
                case 1://向右
                    for (int i = 0; i < 10; i++) {
                        if (getX() + 60 < 940 && !isTouchEnemyTank()) {
                            moveRight();
                        }
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    break;
                case 2://向下
                    for (int i = 0; i < 10; i++) {
                        if(getY() + 60 < 748 && !isTouchEnemyTank()) {
                            moveDown();
                        }
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    break;
                case 3://向左
                    for (int i = 0; i < 10; i++) {
                        if(getX() > 0 && !isTouchEnemyTank()) {
                            moveLeft();
                        }
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    break;
            }

            //休眠50毫秒
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            //然后随机改变坦克方向
            setDirect((int) (Math.random() * 4));

            //写并发程序,一定要考虑清楚线程的退出时机
            if (!isLive) {
                break;
            }
        }
    }
}
package TankGame5;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/22-10:53
 * @describe 炸弹效果
 */
public class Bomb {
    int x , y;//炸弹的坐标
    int life = 9;//炸弹的生命周期
    boolean isLive = true;//是否还存活

    public Bomb(int x, int y) {
        this.x = x;
        this.y = y;
    }

    //减少生命值
    public void lifeDown(){//配合出现图片的爆炸效果
        if(life > 0){
            life--;
        }else{
            isLive = false;
        }
    }
}

6.0

package TankGame6;

import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Scanner;

/**
 * @author Albert
 * @version 6.0
 * @date 2023/11/26-15:58
 * @describe
 */
public class tankGame6 extends JFrame {
    //定义MyPanel
    private MyPanel mp = null;

    public static void main(String[] args) {
        new tankGame6();
    }

    public tankGame6() {
        Scanner scanner = new Scanner(System.in);
        String key = "";
        System.out.println("请输入你的选择的序号:1.新游戏  2.继续上局游戏");
        key = scanner.next();
        mp = new MyPanel(key);
        Thread thread = new Thread(mp);
        thread.start();
        this.add(mp);
        this.setSize(1300, 750);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
        this.addKeyListener(mp);

        //在JFrame中增加相应关闭窗口的处理
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.out.println("监听到关闭窗口了");
                Recorder.keepRecord();
                System.exit(0);
            }
        });
    }
}
package TankGame6;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/9-17:00
 * @describe 坦克类
 */
public class Tank {
    private int x;//x坐标
    private int y;//y坐标
    private int direct;//坦克的方向(0:向上 1:向右 2:向下 3:向左)
    boolean isLive = true;

    public int getSpeed() {
        return speed;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    private int speed = 1;//坦克的速度

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getDirect() {
        return direct;
    }

    public void setDirect(int direct) {
        this.direct = direct;
    }

    public Tank(int x, int y) {
        this.x = x;
        this.y = y;
    }

    //上下左右移动方法
    public void moveUp(){
        direct = 0;
        y -= speed;
    }
    public void moveRight(){
        direct = 1;
        x += speed;
    }
    public void moveDown(){
        direct = 2;
        y += speed;
    }
    public void moveLeft(){
        direct = 3;
        x -= speed;
    }
}
package TankGame6;

/**
 * @author Albert
 * @version 2.0
 * @date 2023/11/13-18:50
 * @describe 射击子弹
 */
public class Shot implements Runnable{
    int x;//子弹的x坐标
    int y;//子弹的y坐标
    int direct;//子弹的方向
    int speed = 8;//子弹的速度

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    boolean isLive = true;//子弹是否存活

    public Shot(int x, int y, int direct) {
        this.x = x;
        this.y = y;
        this.direct = direct;
    }

    @Override
    public void run() {
        while(true){

            try {
                Thread.sleep(50);//休眠50毫秒,免得看不见子弹
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //direct 表示方向(0:向上 1:向右 2:向下 3:向左)
            switch (direct) {
                case 0:
                    y -= speed;
                    break;
                case 1:
                    x += speed;
                    break;
                case 2:
                    y += speed;
                    break;
                case 3:
                    x -= speed;
                    break;
            }
            //测试子弹坐标
            //System.out.println("子弹的x坐标:" + x + " y坐标:" + y);
            //当子弹移动到面板的边界时,应该销毁(把启动的子弹线程销毁)
            if(!(x >= 0 && x <= 1000 && y >= 0 && y <= 750 && isLive)){
                isLive = false;
                break;
            }
        }
    }
}
package TankGame6;

import java.io.*;
import java.util.Vector;

/**
 * @author Albert
 * @version 2.0
 * @date 2023/11/26-11:01
 * @describe 该类用于定义相关信息和文件交互
 */
public class Recorder {
    //定义变量,记录我方坦克击毁敌人坦克数量
    private static int allEnemyTankNum = 0;
    //定义IO对象,准备写数据到文件中
    private static BufferedWriter bw = null;
    private static BufferedReader br = null;
    private static String recordFile = "src\\myRecord.txt";
    //定义敌人的坦克,放到Vector中去,因为Vector是线程安全的
    private static Vector<EnemyTank> enemyTanks = null;
    //定义一个Node的Vector,用于保存敌人的信息node
    private static Vector<Node> nodes = new Vector<>();

    //返回记录文件的目录
    public static String getRecordFile(){
        return recordFile;
    }
    //写一个方法恢复敌人坦克数据
    //该方法在继续上局游戏时使用即可
    public static Vector<Node> getNodesAndEnemyTankRec() {
        try {
            br = new BufferedReader(new FileReader(recordFile));
            allEnemyTankNum = Integer.parseInt(br.readLine());
            //循环读取,生成node集合
            String line = "";
            while ((line = br.readLine()) != null) {
                String[] xyd = line.split(" ");
                Node node = new Node(
                        Integer.parseInt(xyd[0]),
                        Integer.parseInt(xyd[1]),
                        Integer.parseInt(xyd[2])
                );
                nodes.add(node);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return nodes;
    }

    public static void setEnemyTanks(Vector<EnemyTank> enemyTanks) {
        Recorder.enemyTanks = enemyTanks;
    }

    //增加一个方法,当游戏退出时,我们将allEnemyTankNum保存到recordFile
    //接下来对keepRecord进行升级,记录敌人坦克的坐标与方向
    public static void keepRecord() {
        try {
            bw = new BufferedWriter(new FileWriter(recordFile));
            bw.write(allEnemyTankNum + "\r\n");

            //遍历敌人坦克的Vector,然后根据情况保存即可
            //根据OOP,我们应该在当前类定义一个敌人坦克Vector属性,然后通过setXXX得到
            for (int i = 0; i < enemyTanks.size(); i++) {
                //取出敌人坦克
                EnemyTank enemyTank = enemyTanks.get(i);
                if (enemyTank.isLive) {
                    //保存该坦克信息
                    String record = enemyTank.getX() + " " + enemyTank.getY() + " " + enemyTank.getDirect();
                    //写入到文件
                    bw.write(record + "\r\n");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bw != null) {
                    bw.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static int getAllEnemyTankNum() {
        return allEnemyTankNum;
    }

    public static void setAllEnemyTankNum(int allEnemyTankNum) {
        Recorder.allEnemyTankNum = allEnemyTankNum;
    }

    //每当击毁一个敌方坦克,该值自增1
    public static void addAllEnemyTankNum() {
        Recorder.allEnemyTankNum++;
    }
}
package TankGame6;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/26-11:46
 * @describe 一个Node对象表示一个敌人坦克信息
 */
public class Node {
    private int x;
    private int y;

    private int direct;

    public Node(int x, int y, int direct) {
        this.x = x;
        this.y = y;
        this.direct = direct;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getDirect() {
        return direct;
    }

    public void setDirect(int direct) {
        this.direct = direct;
    }
}
package TankGame6;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.util.Vector;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/9-17:00
 * @describe 坦克大战绘图区
 */
public class MyPanel extends JPanel implements KeyListener, Runnable {
    //定义我的坦克
    Hero hero = null;

    //定义敌人的坦克,放到Vector中去,因为Vector是线程安全的
    Vector<EnemyTank> enemyTanks = new Vector<>();
    //定义一个存放Node对象的Vector,用于恢复敌人坦克坐标与方向
    Vector<Node> nodes = new Vector<>();
    int enemySize = 4;//敌人坦克数量

    //定义一个Vector,用于存放炸弹,当子弹集中坦克时,加入一个Boom对象到booms
    Vector<Bomb> bombs = new Vector<>();
    //定义三张炸弹图片,用于显示爆炸效果
    Image image1 = null;
    Image image2 = null;
    Image image3 = null;

    public MyPanel(String key) {
        //先判断记录文件是否存在
        //如果存在,就正常执行,如果文件不存在,提示“只能开启新游戏”,key=1
        File file = new File(Recorder.getRecordFile());
        if (file.exists()) {
            nodes = Recorder.getNodesAndEnemyTankRec();
        } else {
            System.out.println("文件不存在,默认开启新游戏!");
            key = "1";
        }

        hero = new Hero(500, 100);
        hero.setSpeed(6);
        Recorder.setEnemyTanks(enemyTanks);//给Recorder类记录
        switch (key) {
            case "1":
                Recorder.setAllEnemyTankNum(0);
                for (int i = 0; i < enemySize; i++) {
                    EnemyTank enemyTank = new EnemyTank(100 * (i + 1), 0);
                    enemyTank.setEnemyTanks(enemyTanks);
                    enemyTanks.add(enemyTank);
                    //设置方向
                    enemyTanks.get(i).setDirect(2);
                    //启动线程
                    new Thread(enemyTank).start();
                    //给该敌方坦克加入一颗子弹
                    Shot shot = new Shot(enemyTanks.get(i).getX() + 20, enemyTanks.get(i).getY() + 60, enemyTanks.get(i).getDirect());
                    enemyTanks.get(i).shots.add(shot);
                    new Thread(shot).start();
                }
                break;
            case "2":
                for (int i = 0; i < nodes.size(); i++) {
                    Node node = nodes.get(i);
                    EnemyTank enemyTank = new EnemyTank(node.getX(), node.getY());
                    enemyTank.setEnemyTanks(enemyTanks);
                    enemyTanks.add(enemyTank);
                    //设置方向
                    enemyTanks.get(i).setDirect(node.getDirect());
                    //启动线程
                    new Thread(enemyTank).start();
                    //给该敌方坦克加入一颗子弹
                    Shot shot = new Shot(enemyTanks.get(i).getX() + 20, enemyTanks.get(i).getY() + 60, enemyTanks.get(i).getDirect());
                    enemyTanks.get(i).shots.add(shot);
                    new Thread(shot).start();
                }
                break;
            default:
                System.out.println("你的输入有误!");
        }

        //初始化图片对象
        image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/爆炸3.gif"));
        image2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/爆炸2.gif"));
        image3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/爆炸1.gif"));

        //开始游戏时播放音乐
        new AePlayWave("src\\111.wav").start();
    }

    //编写方法,展示我方坦克击毁敌方坦克信息
    public void showInfo(Graphics g) {
        //画出玩家总成绩
        g.setColor(Color.BLACK);
        Font font = new Font("宋体", Font.BOLD, 25);
        g.setFont(font);

        g.drawString("您累计击毁敌方坦克", 1020, 30);
        drawTank(1020, 60, g, 0, 0);//画出一个地方坦克
        g.setColor(Color.BLACK);//重新设置画笔颜色为黑色
        g.drawString(Recorder.getAllEnemyTankNum() + "", 1080, 100);
    }

    public void paint(Graphics g) {
        super.paint(g);

        g.fillRect(0, 0, 1000, 750);//填充矩形,默认黑色
        showInfo(g);

        //画出我方坦克
        if (hero != null && hero.isLive) {
            drawTank(hero.getX(), hero.getY(), g, hero.getDirect(), 1);
        }
        //画出hero射出的子弹
        if (hero.shot != null && hero.shot.isLive) {
            g.drawRect(hero.shot.x, hero.shot.y, 1, 1);
        }
        //画出hero射出的多颗子弹
//        for(int i = 0;i < hero.shots.size();i++) {
//            Shot shot = hero.shots.get(i);
//            if (shot != null && shot.isLive) {
//                g.drawRect(shot.x, shot.y, 1, 1);
//            }else{//如果该对象已经无效,从集合中拿掉该对象
//                hero.shots.remove(shot);
//            }
//        }

        //如果booms集合中有对象,就画出
        try {//加这个休眠是为了解决第一炮没有动画效果的问题,给点时间加载图片
            Thread.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        for (int i = 0; i < bombs.size(); i++) {
            //取出炸弹
            Bomb bomb = bombs.get(i);
            //根据当前这个Boom对象的life值去画出对应的图片
            if (bomb.life > 6) {
                g.drawImage(image1, bomb.x, bomb.y, 60, 60, this);
            } else if (bomb.life > 3) {
                g.drawImage(image2, bomb.x, bomb.y, 60, 60, this);
            } else {
                g.drawImage(image3, bomb.x, bomb.y, 60, 60, this);
            }
            //让生命值减少
            bomb.lifeDown();
            //如果bomb.life为0,就从bombs集合中删除
            if (bomb.life == 0) {
                bombs.remove(bomb);
            }
        }
        for (int i = 0; i < enemyTanks.size(); i++) {
            EnemyTank t = enemyTanks.get(i);
            if (t.isLive) {
                drawTank(t.getX(), t.getY(), g, t.getDirect(), 0);//画敌方坦克
                //画出敌方坦克的所有子弹
                for (int j = 0; j < t.shots.size(); j++) {
                    //取出子弹
                    Shot shotTemp = t.shots.get(j);
                    //绘制
                    if (shotTemp.isLive) {
                        g.drawRect(shotTemp.x, shotTemp.y, 1, 1);
                    } else {
                        t.shots.remove(shotTemp);
                    }
                }
            }
        }

    }

    /**
     * @param x      坦克的左上角x坐标
     * @param y      坦克的左上角y坐标
     * @param g      画笔
     * @param direct 坦克的方向(上下左右)
     * @param type   坦克类型(敌还是友,1是友,0是敌)
     */
    public void drawTank(int x, int y, Graphics g, int direct, int type) {
        //根据不同的坦克设置不同的颜色
        switch (type) {
            case 0://敌人的坦克
                g.setColor(Color.cyan);
                break;
            case 1://我们的坦克
                g.setColor(Color.yellow);
                break;
        }
        //根据坦克的方向来画坦克
        //direct 表示方向(0:向上 1:向右 2:向下 3:向左)
        switch (direct) {
            case 0:
                g.fill3DRect(x, y, 10, 60, false);//左边轮子
                g.fill3DRect(x + 30, y, 10, 60, false);//右边轮子
                g.fill3DRect(x + 10, y + 10, 20, 40, false);//坦克盖子
                g.fillOval(x + 10, y + 20, 20, 20);//圆形盖子
                g.drawLine(x + 20, y + 30, x + 20, y);//炮筒
                break;
            case 1:
                g.fill3DRect(x, y, 60, 10, false);//上边轮子
                g.fill3DRect(x, y + 30, 60, 10, false);//下边轮子
                g.fill3DRect(x + 10, y + 10, 40, 20, false);//坦克盖子
                g.fillOval(x + 20, y + 10, 20, 20);//圆形盖子
                g.drawLine(x + 30, y + 20, x + 60, y + 20);//炮筒
                break;
            case 2:
                g.fill3DRect(x, y, 10, 60, false);//左边轮子
                g.fill3DRect(x + 30, y, 10, 60, false);//右边轮子
                g.fill3DRect(x + 10, y + 10, 20, 40, false);//坦克盖子
                g.fillOval(x + 10, y + 20, 20, 20);//圆形盖子
                g.drawLine(x + 20, y + 30, x + 20, y + 60);//炮筒
                break;
            case 3:
                g.fill3DRect(x, y, 60, 10, false);//上边轮子
                g.fill3DRect(x, y + 30, 60, 10, false);//下边轮子
                g.fill3DRect(x + 10, y + 10, 40, 20, false);//坦克盖子
                g.fillOval(x + 20, y + 10, 20, 20);//圆形盖子
                g.drawLine(x + 30, y + 20, x, y + 20);//炮筒
                break;
            default:
                System.out.println("暂时没有处理");
        }
    }

    @Override
    public void keyTyped(KeyEvent e) {

    }

    //处理wasd键按下的情况
    @Override
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_W) {
            if (hero.getY() > 0) {
                hero.moveUp();
            }
        }
        if (e.getKeyCode() == KeyEvent.VK_D) {
            if (hero.getX() + 60 < 1000) {
                hero.moveRight();
            }
        }
        if (e.getKeyCode() == KeyEvent.VK_S) {
            if (hero.getY() + 60 < 748) {
                hero.moveDown();
            }
        }
        if (e.getKeyCode() == KeyEvent.VK_A) {
            if (hero.getX() > 0) {
                hero.moveLeft();
            }
        }
        if (e.getKeyCode() == KeyEvent.VK_J) {//只能发射一颗子弹
            if (hero.shot == null || !hero.shot.isLive) {
                hero.shotEnemyTank();
            }
        }

        //发射多颗子弹
//        if(e.getKeyCode() == KeyEvent.VK_J) {//加上“ && hero.shots.size() < 5”可以控制坦克只发五颗子弹
//            hero.shotEnemyTank();
//        }

        this.repaint();
    }

    @Override
    public void keyReleased(KeyEvent e) {

    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //判断是否击中了敌人坦克
            hitEnemyTank();

            //判断多颗子弹是否打中敌方坦克
            //hitEnemyTank();

            //判断我方坦克是否被击中
            hitHero();
            this.repaint();
        }
    }

    //判断我方坦克多颗子弹是否打中敌方坦克
//    public void hitEnemyTank(){
//        //遍历我方坦克子弹
//        for(int j = 0; j < hero.shots.size(); j++) {
//            Shot shot = hero.shots.get(j);
//            //判断是否击中了敌人坦克
//            if (shot != null && shot.isLive) {
//                //遍历敌人所有的坦克
//                for (int i = 0; i < enemyTanks.size(); i++) {
//                    EnemyTank enemyTank = enemyTanks.get(i);
//                    hitTank(hero.shot, enemyTank);
//                }
//            }
//        }
//    }

    //判断我方坦克单颗子弹是否打中敌方坦克
    public void hitEnemyTank() {
        if (hero.shot != null && hero.shot.isLive) {
            //遍历敌人所有的坦克
            for (int i = 0; i < enemyTanks.size(); i++) {
                EnemyTank enemyTank = enemyTanks.get(i);
                if (hitTank(hero.shot, enemyTank)) {
                    Recorder.addAllEnemyTankNum();
                }
            }
        }
    }

    //判断敌方子弹是否打击到我方坦克
    public void hitHero() {
        //遍历敌人所有坦克
        for (int i = 0; i < enemyTanks.size(); i++) {
            //取出敌人坦克
            EnemyTank enemyTank = enemyTanks.get(i);
            //遍历该敌人坦克的所有子弹
            for (int j = 0; j < enemyTank.shots.size(); j++) {
                //取出子弹
                Shot shot = enemyTank.shots.get(j);
                if (hero.isLive && shot.isLive) {
                    hitTank(shot, hero);
                }
            }
        }
    }

    public boolean hitTank(Shot shot, Tank tank) {
        //判断子弹s击中坦克enemyTank
        switch (tank.getDirect()) {
            case 0:
            case 2:
                if (shot.x > tank.getX() && shot.x < tank.getX() + 40
                        && shot.y > tank.getY() && shot.y < tank.getY() + 60) {
                    shot.isLive = false;
                    tank.isLive = false;
                    //当我的子弹击中坦克后,将enemyTank从Vector中拿掉
                    enemyTanks.remove(tank);
                    //创建Boom对象,加入到booms集合中
                    Bomb bomb = new Bomb(tank.getX(), tank.getY());
                    bombs.add(bomb);
                    return true;
                }
                break;
            case 1:
            case 3:
                if (shot.x > tank.getX() && shot.x < tank.getX() + 60
                        && shot.y > tank.getY() && shot.y < tank.getY() + 40) {
                    shot.isLive = false;
                    tank.isLive = false;
                    //当我的子弹击中坦克后,将enemyTank从Vector中拿掉
                    enemyTanks.remove(tank);
                    //创建Boom对象,加入到booms集合中
                    Bomb bomb = new Bomb(tank.getX(), tank.getY());
                    bombs.add(bomb);
                    return true;
                }
                break;
        }
        return false;
    }
}
package TankGame6;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/9-17:00
 * @describe 自己的坦克
 */
public class Hero extends Tank {
    Shot shot = null;//表示一个射击行为
    //可以发射多颗子弹
    //Vector<Shot> shots = new Vector<>();
    public Hero(int x, int y) {
        super(x, y);
    }

    public void shotEnemyTank(){
//        if(shots.size() == 5){//控制坦克的子弹不大于5
//            return;
//        }
        //如果我方坦克阵亡,无法发射子弹
        if(!isLive){
            return;
        }
        switch (getDirect()){
            case 0:
                shot = new Shot(getX() + 20, getY(), 0);
                break;
            case 1:
                shot = new Shot(getX() + 60, getY() + 20, 1);
                break;
            case 2:
                shot = new Shot(getX() + 20, getY() + 60, 2);
                break;
            case 3:
                shot = new Shot(getX(), getY() + 20, 3);
                break;
        }
        //把子弹加入到shots里面去
        //shots.add(shot);
        //启动线程
        new Thread(shot).start();
    }
}
package TankGame6;

import java.util.Vector;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/9-19:02
 * @describe
 */
public class EnemyTank extends Tank implements Runnable {
    //在敌人坦克类,使用Vector保存多个Shot
    Vector<Shot> shots = new Vector<>();
    //boolean isLive = true;

    Vector<EnemyTank> enemyTanks = new Vector<>();

    public boolean isTouchEnemyTank(){
        //判断当前敌人坦克(this)方向
        switch (this.getDirect()){
            case 0://上
                //让当前敌人坦克和其他所有的敌人坦克比较
                for(int i = 0; i < enemyTanks.size(); i++){
                    //从vector中取出一个敌人坦克
                    EnemyTank enemyTank = enemyTanks.get(i);
                    //不和自己比较
                    if(enemyTank != this){
                        //如果敌人坦克是上/下
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左上角点
                            if(this.getX() >= enemyTank.getX()
                                    && this.getX() <= enemyTank.getX() + 40
                                    && this.getY() >= enemyTank.getY()
                                    && this.getY() <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右上角点
                            if(this.getX() + 40 >= enemyTank.getX()
                                    && this.getX() + 40 <= enemyTank.getX() + 40
                                    && this.getY() >= enemyTank.getY()
                                    && this.getY() <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        //如果敌人坦克是右或左
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左上角点
                                if(this.getX() >= enemyTank.getX()
                                        && this.getX() <= enemyTank.getX() + 60
                                        && this.getY() >= enemyTank.getY()
                                        && this.getY() <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右上角点
                                if(this.getX() + 40 >= enemyTank.getX()
                                        && this.getX() + 40 <= enemyTank.getX() + 60
                                        && this.getY() >= enemyTank.getY()
                                        && this.getY() <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                    }
                }
                break;
            case 1://右
                //让当前敌人坦克和其他所有的敌人坦克比较
                for(int i = 0; i < enemyTanks.size(); i++){
                    //从vector中取出一个敌人坦克
                    EnemyTank enemyTank = enemyTanks.get(i);
                    //不和自己比较
                    if(enemyTank != this){
                        //如果敌人坦克是上/下
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右上角点
                            if(this.getX() + 60 >= enemyTank.getX()
                                    && this.getX() + 60 <= enemyTank.getX() + 40
                                    && this.getY() >= enemyTank.getY()
                                    && this.getY() <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右下角点
                            if(this.getX() + 60 >= enemyTank.getX()
                                    && this.getX() + 60 <= enemyTank.getX() + 40
                                    && this.getY() + 40 >= enemyTank.getY()
                                    && this.getY() + 40 <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        //如果敌人坦克是右或左
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右上角点
                                if(this.getX() + 60 >= enemyTank.getX()
                                        && this.getX() + 60 <= enemyTank.getX() + 60
                                        && this.getY() >= enemyTank.getY()
                                        && this.getY() <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右下角点
                                if(this.getX() + 60 >= enemyTank.getX()
                                        && this.getX() + 60 <= enemyTank.getX() + 60
                                        && this.getY() + 40 >= enemyTank.getY()
                                        && this.getY() + 40 <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                    }
                }
                break;
            case 2://下
                //让当前敌人坦克和其他所有的敌人坦克比较
                for(int i = 0; i < enemyTanks.size(); i++){
                    //从vector中取出一个敌人坦克
                    EnemyTank enemyTank = enemyTanks.get(i);
                    //不和自己比较
                    if(enemyTank != this){
                        //如果敌人坦克是上/下
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左下角点
                            if(this.getX() >= enemyTank.getX()
                                    && this.getX() <= enemyTank.getX() + 40
                                    && this.getY() + 60 >= enemyTank.getY()
                                    && this.getY() + 60 <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右下角点
                            if(this.getX() + 40 >= enemyTank.getX()
                                    && this.getX() + 40 <= enemyTank.getX() + 40
                                    && this.getY() + 60 >= enemyTank.getY()
                                    && this.getY() + 60 <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        //如果敌人坦克是右或左
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左下角点
                                if(this.getX() >= enemyTank.getX()
                                        && this.getX() <= enemyTank.getX() + 60
                                        && this.getY() + 60 >= enemyTank.getY()
                                        && this.getY() + 60 <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克右下角点
                                if(this.getX() + 40 >= enemyTank.getX()
                                        && this.getX() + 40 <= enemyTank.getX() + 60
                                        && this.getY() + 60 >= enemyTank.getY()
                                        && this.getY() + 60 <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                    }
                }
                break;
            case 3://左
                //让当前敌人坦克和其他所有的敌人坦克比较
                for(int i = 0; i < enemyTanks.size(); i++){
                    //从vector中取出一个敌人坦克
                    EnemyTank enemyTank = enemyTanks.get(i);
                    //不和自己比较
                    if(enemyTank != this){
                        //如果敌人坦克是上/下
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左上角点
                            if(this.getX() >= enemyTank.getX()
                                    && this.getX() <= enemyTank.getX() + 40
                                    && this.getY() >= enemyTank.getY()
                                    && this.getY() <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左下角点
                            if(this.getX() >= enemyTank.getX()
                                    && this.getX() <= enemyTank.getX() + 40
                                    && this.getY() + 40 >= enemyTank.getY()
                                    && this.getY() + 40 <= enemyTank.getY() + 60){
                                return true;
                            }
                        }
                        //如果敌人坦克是右或左
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左上角点
                                if(this.getX() >= enemyTank.getX()
                                        && this.getX() <= enemyTank.getX() + 60
                                        && this.getY() >= enemyTank.getY()
                                        && this.getY() <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                        if(enemyTank.getDirect() == 1 || enemyTank.getDirect() == 3){
                            if(enemyTank.getDirect() == 0 || enemyTank.getDirect() == 2){//自己坦克左下角点
                                if(this.getX() >= enemyTank.getX()
                                        && this.getX() <= enemyTank.getX() + 60
                                        && this.getY() + 40 >= enemyTank.getY()
                                        && this.getY() + 40 <= enemyTank.getY() + 40){
                                    return true;
                                }
                            }
                        }
                    }
                }
                break;
        }
        return false;

    }
    public void setEnemyTanks(Vector<EnemyTank> enemyTanks) {
        this.enemyTanks = enemyTanks;
    }

    public EnemyTank(int x, int y) {
        super(x, y);
    }

    @Override
    public void run() {
        while (true) {
            //这里我们判断如果shots.size()==0,创建一颗子弹,放入到shots集合中,并启动
            if(isLive && shots.size() <= 3){
                Shot s = null;

                //判断坦克的方向,创建对应的子弹
                switch (getDirect()){
                    case 0:
                        s = new Shot(getX() + 20, getY(), 0);
                        break;
                    case 1:
                        s = new Shot(getX() + 60, getY() + 20, 1);
                        break;
                    case 2:
                        s = new Shot(getX() + 20, getY() + 60, 2);
                        break;
                    case 3:
                        s = new Shot(getX(), getY() + 20, 3);
                        break;
                }
                shots.add(s);
                new Thread(s).start();
            }
            //根据坦克的方向来继续移动
            switch (getDirect()) {
                case 0://向上
                    for (int i = 0; i < 10; i++) {
                        if (getY() > 0 && !isTouchEnemyTank()) {
                            moveUp();
                        }
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    break;
                case 1://向右
                    for (int i = 0; i < 10; i++) {
                        if (getX() + 60 < 940 && !isTouchEnemyTank()) {
                            moveRight();
                        }
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    break;
                case 2://向下
                    for (int i = 0; i < 10; i++) {
                        if(getY() + 60 < 748 && !isTouchEnemyTank()) {
                            moveDown();
                        }
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    break;
                case 3://向左
                    for (int i = 0; i < 10; i++) {
                        if(getX() > 0 && !isTouchEnemyTank()) {
                            moveLeft();
                        }
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    break;
            }

            //休眠50毫秒
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            //然后随机改变坦克方向
            setDirect((int) (Math.random() * 4));

            //写并发程序,一定要考虑清楚线程的退出时机
            if (!isLive) {
                break;
            }
        }
    }
}
package TankGame6;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/22-10:53
 * @describe 炸弹效果
 */
public class Bomb {
    int x , y;//炸弹的坐标
    int life = 9;//炸弹的生命周期
    boolean isLive = true;//是否还存活

    public Bomb(int x, int y) {
        this.x = x;
        this.y = y;
    }

    //减少生命值
    public void lifeDown(){//配合出现图片的爆炸效果
        if(life > 0){
            life--;
        }else{
            isLive = false;
        }
    }
}
package TankGame6;

import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class AePlayWave extends Thread {
    private String filename;

    public AePlayWave(String wavfile) {
        filename = wavfile;

    }

    public void run() {

        File soundFile = new File(filename);

        AudioInputStream audioInputStream = null;
        try {
            audioInputStream = AudioSystem.getAudioInputStream(soundFile);
        } catch (Exception e1) {
            e1.printStackTrace();
            return;
        }

        AudioFormat format = audioInputStream.getFormat();
        SourceDataLine auline = null;
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

        try {
            auline = (SourceDataLine) AudioSystem.getLine(info);
            auline.open(format);
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }

        auline.start();
        int nBytesRead = 0;
        //这是缓冲
        byte[] abData = new byte[512];

        try {
            while (nBytesRead != -1) {
                nBytesRead = audioInputStream.read(abData, 0, abData.length);
                if (nBytesRead >= 0)
                    auline.write(abData, 0, nBytesRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
            return;
        } finally {
            auline.drain();
            auline.close();
        }

    }
}

21章——网络编程

网络的相关概念

在这里插入图片描述
在这里插入图片描述

IP地址

在这里插入图片描述
在这里插入图片描述

域名和端口

在这里插入图片描述

网络协议

在这里插入图片描述
在这里插入图片描述

TCP和UDP

在这里插入图片描述

InetAddress类

在这里插入图片描述

package API_;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/26-18:58
 * @describe 演示InetAddress类的使用
 */
public class Api01 {
    public static void main(String[] args) throws UnknownHostException {
        //1.获取本机的InetAddress对象
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost);//LAPTOP-A0AH594T/192.168.182.89

        //2.根据指定主机名获取InetAddress对象
        InetAddress host1 = InetAddress.getByName("LAPTOP-A0AH594T");
        System.out.println(host1);//LAPTOP-A0AH594T/192.168.182.89

        //3.根据域名获取InetAddress对象
        InetAddress host2 = InetAddress.getByName("www.baidu.com");
        System.out.println(host2);//www.baidu.com/183.2.172.185

        //4.通过InetAddress对象获取对应的地址
        String hostAddress = host2.getHostAddress();
        System.out.println(hostAddress);//183.2.172.42

        //5.通过InetAddress对象,获取对应的主机名或者域名
        String hostName = host2.getHostName();
        System.out.println(hostName);//www.baidu.com
    }
}

Socket

在这里插入图片描述
在这里插入图片描述

TCP字节流编程01

在这里插入图片描述
在这里插入图片描述

package Socket_;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/26-19:27
 * @describe 服务端
 */
public class SocketTCP01Sever {
    public static void main(String[] args) throws IOException {
        //1.在本机的 9999 端口监听,等待连接(要求在本机没有其他服务在监听9999)
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端正在9999端口监听......");

        //2.当没有客户端连接9999端口时,程序会阻塞,等待连接。如果有客户端连接,则会返回Socket对象,程序继续执行下去
        Socket socket = serverSocket.accept();
        System.out.println("服务端 Socket = " + socket.getClass());

        //3.通过socket.getInputStream()读取客户端写入数据通道的信息,并显示
        InputStream inputStream = socket.getInputStream();

        //4.IO读取
        byte[] buf = new byte[20];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf, 0, readLen));
        }

        //5.关闭流和socket
        inputStream.close();
        socket.close();
        serverSocket.close();
        System.out.println("服务端关闭......");

    }
}
package Socket_;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/26-19:35
 * @describe 客户端,需要发送“hello,severe”给服务端
 */
public class SocketTCP01Client {
    public static void main(String[] args) throws IOException {
        //1.连接服务端(IP,端口)。例子:连接本机的 9999 端口,如果连接成功,返回Socket对象
        //这个SevereSocket可以通过accept()返回多个Socket[多个客户端连接服务器的并发]
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端 Socket返回 = " + socket.getClass());

        //2.连接上后,生成Socket,通过socket.getOutputStream()得到和socket对象关联的输出流对象
        OutputStream outputStream = socket.getOutputStream();

        //3.通过输出流,写入数据到数据通道
        outputStream.write("hello,sever".getBytes());

        //4.关闭流对象和socket,必须关闭!!!
        outputStream.close();
        socket.close();
        System.out.println("客户端退出......");
    }
}

TCP字节流编程02

在这里插入图片描述
在这里插入图片描述
注意:记得写入结束标记 socket.shutdownOutput();

package Socket_;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/26-20:13
 * @describe 服务端
 */
public class SocketTCP02Sever {
    public static void main(String[] args) throws IOException {
        //1.在本机的 9999 端口监听,等待连接(要求在本机没有其他服务在监听9999)
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端正在9999端口监听......");

        //2.当没有客户端连接9999端口时,程序会阻塞,等待连接。如果有客户端连接,则会返回Socket对象,程序继续执行下去
        Socket socket = serverSocket.accept();
        System.out.println("服务端 Socket = " + socket.getClass());

        //3.通过socket.getInputStream()读取客户端写入数据通道的信息,并显示
        InputStream inputStream = socket.getInputStream();

        //4.IO读取
        byte[] buf = new byte[20];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf, 0, readLen));
        }

        //5.回送问候
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello,client".getBytes());
        //设置结束标记
        socket.shutdownOutput();

        //6.关闭流和socket
        inputStream.close();
        socket.close();
        serverSocket.close();
        outputStream.close();
        System.out.println("服务端关闭......");

    }
}
package Socket_;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/26-20:14
 * @describe 客户端,需要发送“hello,severe”给服务端
 */
public class SocketTCP02Client {
    public static void main(String[] args) throws IOException {
        //1.连接服务端(IP,端口)。例子:连接本机的 9999 端口,如果连接成功,返回Socket对象
        //这个SevereSocket可以通过accept()返回多个Socket[多个客户端连接服务器的并发]
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端 Socket返回 = " + socket.getClass());

        //2.连接上后,生成Socket,通过socket.getOutputStream()得到和socket对象关联的输出流对象
        OutputStream outputStream = socket.getOutputStream();

        //3.通过输出流,写入数据到数据通道
        outputStream.write("hello,sever".getBytes());
        //设置结束标记
        socket.shutdownOutput();

        //4.接收回复
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[20];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf, 0, readLen));
        }

        //4.关闭流对象和socket,必须关闭!!!
        outputStream.close();
        inputStream.close();
        socket.close();
        System.out.println("客户端退出......");
    }
}

TCP字符流编程

在这里插入图片描述
在这里插入图片描述

package Socket_;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/26-20:35
 * @describe 服务端,使用字符流
 */
public class SocketTCP03Sever {
    public static void main(String[] args) throws IOException {
        //1.在本机的 9999 端口监听,等待连接(要求在本机没有其他服务在监听9999)
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端正在9999端口监听......");

        //2.当没有客户端连接9999端口时,程序会阻塞,等待连接。如果有客户端连接,则会返回Socket对象,程序继续执行下去
        Socket socket = serverSocket.accept();
        System.out.println("服务端 Socket = " + socket.getClass());

        //3.通过socket.getInputStream()读取客户端写入数据通道的信息,并显示
        InputStream inputStream = socket.getInputStream();

        //4.IO读取,使用字符流
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
        String line = "";
        line = br.readLine();
        System.out.println(line);


        //5.回送问候
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, "utf-8"));
        bw.write("hello,client 字符流");
        bw.newLine();//通过设置一个换行符来设置结束标记,对方必须使用readLine()才可以读取到该标记
        bw.flush();//如果使用字符流,必须手动刷新,否则数据不会写入数据通道

        //6.关闭流和socket(注意,关闭外层流即可)
        bw.close();
        br.close();
        socket.close();
        serverSocket.close();
        System.out.println("服务端关闭......");

    }
}
package Socket_;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/26-20:36
 * @describe 客户端,需要发送“hello,severe”给服务端,使用字符流
 */
public class SocketTCP03Client {
    public static void main(String[] args) throws IOException {
        //1.连接服务端(IP,端口)。例子:连接本机的 9999 端口,如果连接成功,返回Socket对象
        //这个SevereSocket可以通过accept()返回多个Socket[多个客户端连接服务器的并发]
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端 Socket返回 = " + socket.getClass());

        //2.连接上后,生成Socket,通过socket.getOutputStream()得到和socket对象关联的输出流对象
        OutputStream outputStream = socket.getOutputStream();

        //3.通过字符输出流,写入数据到数据通道
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, "utf-8"));
        bw.write("hello,sever 字符流");
        bw.newLine();//通过设置一个换行符来设置结束标记,对方必须使用readLine()才可以读取到该标记
        bw.flush();//如果使用字符流,必须手动刷新,否则数据不会写入数据通道


        //4.接收回复
        InputStream inputStream = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
        String line = "";
        line = br.readLine();
        System.out.println(line);


        //4.关闭流对象和socket,必须关闭!!!(注意,关闭外层流即可)
        bw.close();
        br.close();
        socket.close();
        System.out.println("客户端退出......");
    }
}

网络上传文件

在这里插入图片描述
在这里插入图片描述

package upload;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/27-14:39
 * @describe 文件上传客户端
 */
public class TCPFileUploadClient {
    public static void main(String[] args) throws Exception {
        //1.客户端连接服务端,获得Socket对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);

        //2.创建读取磁盘文件的输入流
        String filePath = "e:\\Wallpaper.jpg";
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));

        //3.bytes 就是filePath对应的文件字节数组
        byte[] bytes = StreamUtils.streamToByteArray(bis);


        //4.通过Socket获取输出流发给服务端
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        bos.write(bytes);//将文件对应的字节数组内容,写入数据通道
        socket.shutdownOutput();//设置结束标志


        //5.接收服务端的反馈
        String s = StreamUtils.streamToString(socket.getInputStream());
        System.out.println("服务端回复:" + s);


        //6.关闭流和socket(注意:与socket有关的流一定要统一关闭,如果提前关闭一个流,会导致socket关闭一部分,从而后面的输入输出流全部用不了)
        bis.close();
        bos.close();
        socket.close();

    }
}
package upload;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/27-14:39
 * @describe 文件上传的服务端
 */
public class TCPFileUploadServer {
    public static void main(String[] args) throws Exception {
        //1.服务端在本机监听 8888 端口
        ServerSocket serverSocket = new ServerSocket(8888);

        //2.等待客户端的链接
        System.out.println("服务端正在8888端口监听...");
        Socket socket = serverSocket.accept();

        //3.从数据通道读取客户端传送过来的数据
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        byte[] bytes = StreamUtils.streamToByteArray(bis);


        //4.把图片写入src
        String filePath = "src\\Wallpaper.jpg";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
        bos.write(bytes);


        //5.给客户端发送成功接收的信号(既然要写入字符串,那就必须使用字符流!)
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "utf-8"));
        bw.write("I get it!");
        bw.flush();//一定要flush一下,不然不会成功写入
        socket.shutdownOutput();

        //6.关闭流和socket(注意:与socket有关的流一定要统一关闭,如果提前关闭一个流,会导致socket关闭一部分,从而后面的输入输出流全部用不了)
        bw.close();
        bos.close();
        bis.close();
        socket.close();
        serverSocket.close();
    }
}
package upload;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * 此类用于演示关于流的读写方法
 *
 */
public class StreamUtils {
    /**
     * 功能:将输入流转换成byte[], 即可以把文件的内容读入到byte[]
     * @param is
     * @return
     * @throws Exception
     */
    public static byte[] streamToByteArray(InputStream is) throws Exception{
       ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
       byte[] b = new byte[1024];//字节数组
       int len;
       while((len=is.read(b))!=-1){//循环读取
          bos.write(b, 0, len);//把读取到的数据,写入bos   
       }
       byte[] array = bos.toByteArray();//然后将bos 转成字节数组
       bos.close();
       return array;
    }
    /**
     * 功能:将InputStream转换成String
     * @param is
     * @return
     * @throws Exception
     */
    
    public static String streamToString(InputStream is) throws Exception{
       BufferedReader reader = new BufferedReader(new InputStreamReader(is));
       StringBuilder builder= new StringBuilder();
       String line;
       while((line=reader.readLine())!=null){
          builder.append(line+"\r\n");
       }
       return builder.toString();
       
    }

}

netstat指令

在这里插入图片描述

TCP客户端使用的端口是随机的

在这里插入图片描述

UDP网络通讯编程

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

package UDP;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/27-17:29
 * @describe 接收端A
 */
public class UDPReceiverA {
    public static void main(String[] args) throws IOException {
        //1.创建一个 DatagramSocket 对象,准备在 9999 端口接收数据
        DatagramSocket datagramSocket = new DatagramSocket(9999);

        //2.创建一个 DatagramPacket 对象,用来接收数据(记住,UDP一个数据包最大64K)
        byte[] buf = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);

        //3.调用 datagramSocket 的接收方法,当有数据包发送到本机的 9999 端口时,
        // 把接收到的 datagramPacket 对象交给本机的 datagramPacket对象;
        // 如果端口没有收到数据包,则阻塞在这里
        System.out.println("接收端 A 正在等待接收数据..");
        datagramSocket.receive(datagramPacket);

        //4.可以把 datagramPacket 进行拆包,取出数据,并显示
        int length = datagramPacket.getLength();//实际接收到的数据字节长度
        byte[] data = datagramPacket.getData();//接收到的数据
        String s = new String(data, 0, length);
        System.out.println("从 B 端发送过来的消息:" + s);

        //5.关闭资源
        //datagramSocket.close();
        System.out.println("A 端成功接收");

        //6.A 端成为发送端
        data = "不知天上宫阙,今夕是何年".getBytes();
        datagramPacket = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.161.1"), 9998);
        datagramSocket.send(datagramPacket);
        System.out.println("A 端成功发送");
        datagramSocket.close();
    }
}
package UDP;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/27-17:29
 * @describe 发送端B
 */
public class UDPSenderB {
    public static void main(String[] args) throws IOException {
        //1.创建一个 DatagramSocket 对象,准备在 9998 端口接收数据(发送端口号是随机的)
        DatagramSocket datagramSocket = new DatagramSocket(9998);

        //2.将需要发送的数据封装到 DatagramPacket 对象
        byte[] data = "明月几时有,把酒问青天".getBytes();
        //字节数组(数据),字节数组长度, 发送目标主机(使用目标主机IP获得), 目标主机端口号
        DatagramPacket datagramPacket = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.161.1"), 9999);
        datagramSocket.send(datagramPacket);

        //3.关闭资源
        //datagramSocket.close();
        System.out.println("B 端成功发送");

        //4.B成为接收端
        byte[] buf = new byte[1024];
        datagramPacket = new DatagramPacket(buf, buf.length);
        datagramSocket.receive(datagramPacket);
        int length = datagramPacket.getLength();
        data = datagramPacket.getData();
        String s = new String(data, 0, length);
        System.out.println("从 A 端发送过来的信息:" + s);
        datagramSocket.close();
        System.out.println("B 端成功接收");
    }
}

21章作业

作业1
package Homeword;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/27-19:38
 * @describe (1)使用字符流的方式,编写一个客户端程序和服务器端程序
 * *        (2)客户端发送“name”,服务器端接收到后,返回“我是 nova,nova 是你自己的名字
 * *        (3)客户端发送“hobby”,服务器端接收到后,返回“编写java程序”
 * *        (4)不是这两个问题,回复“你说啥呢”
 */
public class H01Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("S 端正在等待...");
        Socket socket = serverSocket.accept();
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
        String line;
        StringBuilder s = new StringBuilder();
        while ((line = br.readLine()) != null) {
            s.append(line);
        }
        String data = "";
        if (s.toString().equals("name")) {
            data = "I am LiBai";
        } else if (s.toString().equals("hobby")) {
            data = "I like drinking";
        } else {
            data = "I don't know what you say.";
        }
        System.out.println("S 端接收到 C 端的信息:" + s);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "utf-8"));
        bw.write(data);
        bw.flush();
        System.out.println("S 端回复成功");
        socket.shutdownOutput();
        br.close();
        bw.close();
        socket.close();
        serverSocket.close();
        System.out.println("S 端关闭");
    }
}
package Homeword;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/27-19:38
 * @describe (1)使用字符流的方式,编写一个客户端程序和服务器端程序
 *           (2)客户端发送“name”,服务器端接收到后,返回“我是 nova,nova 是你自己的名字
 *           (3)客户端发送“hobby”,服务器端接收到后,返回“编写java程序”
 *           (4)不是这两个问题,回复“你说啥呢”
 */
public class H01Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket(InetAddress.getByName("192.168.161.1"), 9999);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "utf-8"));
        String data = "";
        //data = "name";
        //data = "hobby";
        data = "china";
        bw.write(data);
        bw.flush();
        socket.shutdownOutput();
        System.out.println("C 端发送成功");
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
        String line;
        StringBuilder s = new StringBuilder();
        while ((line = br.readLine()) != null) {
            s.append(line);
        }

        System.out.println("C 端接收到 S 端回复信息:" + s);
        bw.close();
        br.close();
        socket.close();
        System.out.println("C 端关闭");
    }
}
作业2
package Homeword;

import java.io.IOException;
import java.net.*;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/27-20:27
 * @describe (1)编写一个接收端A,和一个发送端B, 使用UDP协议完成
 *           (2)接收端在 8888 端口等待接收数据(receive)
 *           (3)发送端向接收端 发送 数据“四大名著是哪些”
 *           (4)接收端接收到 发送端发送的 问题后,返回“四大名著是<<红楼梦>> ...",否则返回what?
 *           (5) 接收端和发送端程序退出
 */
public class H02SenderB {
    public static void main(String[] args) throws IOException {
        DatagramSocket datagramSocket = new DatagramSocket(9998);
        byte[] data = "四大名著是哪些?".getBytes();
        //byte[] data = "唐诗三百首有哪些?".getBytes();
        DatagramPacket datagramPacket = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.161.1"), 9999);
        datagramSocket.send(datagramPacket);
        byte[] buf = new byte[1024];
        datagramPacket = new DatagramPacket(buf, buf.length);
        datagramSocket.receive(datagramPacket);
        data = datagramPacket.getData();
        int length = datagramPacket.getLength();
        String s = new String(data, 0, length);
        System.out.println(s);
        datagramSocket.close();
    }
}
package Homeword;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/27-20:27
 * @describe (1)编写一个接收端A,和一个发送端B, 使用UDP协议完成
 *           (2)接收端在 8888 端口等待接收数据(receive)
 *           (3)发送端向接收端 发送 数据“四大名著是哪些”
 *           (4)接收端接收到 发送端发送的 问题后,返回“四大名著是<<红楼梦>> ...",否则返回what?
 *           (5) 接收端和发送端程序退出
 */
public class H02ReceiverA {
    public static void main(String[] args) throws IOException {
        DatagramSocket datagramSocket = new DatagramSocket(9999);
        byte[] buf = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
        datagramSocket.receive(datagramPacket);
        byte[] data = datagramPacket.getData();
        int length = datagramPacket.getLength();
        String s = new String(data, 0, length);
        String answer = "";
        if(s.equals("四大名著是哪些?")){
            answer = "《红楼梦》、《西游记》、《水浒传》、《三国演义》";
        }else{
            answer = "what?";
        }
        data = answer.getBytes();
        datagramPacket = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.161.1"), 9998);
        datagramSocket.send(datagramPacket);
        datagramSocket.close();
    }
}
作业3
package Homeword;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/27-19:38
 * @describe (1)编写客户端程序和服务器端程序
 *           (2) 客户端可以输入 一个 音乐 文件名,比如 高山流水,服务端 收到音乐名后,可以给客户端 返回这个 音乐文件,如果服务器没有这个文件,返回 一个默认的音乐即可.
 *           (3)客户端收到文件后,保存到本地 e:
 *           (4)提示: 该程序可以使用 StreamUtils.java
 */
public class H03Server {
    public static void main(String[] args) throws IOException, InterruptedException {
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("S 端正在等待...");
        Socket socket = serverSocket.accept();
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
        String line;
        StringBuilder s = new StringBuilder();
        while ((line = br.readLine()) != null) {
            s.append(line);
        }
        String fileName = s.toString();
        String filePath = "src\\" + s + ".wma";
        File file = new File(filePath);
        String destFilePath = "";
        if (file.exists()) {
            System.out.println("文件存在~");
            destFilePath = filePath;
        } else {
            System.out.println("文件不存在~");
            destFilePath = "src\\告白气球.wma";
        }
        System.out.println("S 端接将发送给 C 端的文件:" + filePath);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "utf-8"));
        bw.write(destFilePath);
        bw.newLine();//结束标志,这里不可以使用socket.shutdownOutput();
        bw.flush();

        //Thread.sleep(5000);
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(destFilePath));
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        int length = 0;
        byte[] buf = new byte[1024];
        while((length = bis.read(buf)) != -1){
            bos.write(buf, 0, length);
        }
        bos.flush();
        System.out.println("S 端发送 " + s + " 成功");

        socket.shutdownOutput();
        bw.close();
        br.close();
        bos.close();
        bis.close();
        socket.close();
        serverSocket.close();
        System.out.println("S 端关闭");
    }
}
package Homeword;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/27-19:38
 * @describe (1)编写客户端程序和服务器端程序
 *           (2) 客户端可以输入 一个 音乐 文件名,比如 高山流水,服务端 收到音乐名后,可以给客户端 返回这个 音乐文件,如果服务器没有这个文件,返回 一个默认的音乐即可.
 *           (3)客户端收到文件后,保存到本地 e:
 *           (4)提示: 该程序可以使用 StreamUtils.java
 */
public class H03Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket(InetAddress.getByName("192.168.161.1"), 9999);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "utf-8"));
        String data = "";
        //data = "最伟大的作品";
        data = "青花瓷";
        //data = "告白气球";
        bw.write(data);
        bw.flush();
        socket.shutdownOutput();
        System.out.println("C 端发送请求(下载 " + data + " )成功");
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));

        String s = br.readLine();
        String fileName = s.toString().replace("src", "e:");
        System.out.println(s);
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileName));
        int length = 0;
        byte[] buf = new byte[1024];
        while ((length = bis.read(buf)) != -1) {
            bos.write(buf, 0, length);
        }
        System.out.println("C 端成功接收到 S 端回复的文件");
        bw.close();
        bis.close();
        bos.close();
        socket.close();
        System.out.println("C 端关闭");
    }
}

项目开发流程

在这里插入图片描述

22章——多用户及时通讯系统

需求分析

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

思路分析

客户端与服务端通信分析:

####
客户端退出思路分析:
在这里插入图片描述
私聊思路分析:
在这里插入图片描述
发送文件分析:
在这里插入图片描述
客户端群发新闻思路:
在这里插入图片描述
离线发消息思路:
在这里插入图片描述

源代码

服务端

服务端结构:

在这里插入图片描述

package com.qqframe;

import com.qqserver.service.QQServer;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/29-10:46
 * @describe 该类创建QQServer,启动后台服务
 */
public class QQFrame {
    public static void main(String[] args) {
        new QQServer();
    }
}
package com.qqserver.service;

import java.util.HashMap;
import java.util.Iterator;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-22:07
 * @describe 该类用于管理和客户端通信的线程
 */
public class ManageClientThreads {
    private static HashMap<String, ServerConnectClientThread> hm = new HashMap<>();

    public static HashMap<String, ServerConnectClientThread> getHm() {
        return hm;
    }

    //添加线程对象到hm集合
    public static void addServerConnectClientThread(String userId, ServerConnectClientThread serverConnectClientThread){
        hm.put(userId, serverConnectClientThread);
    }

    //根据用户id获取ServerConnectClientThread对象
    public static ServerConnectClientThread getServerConnectClientThread(String userId){
        return hm.get(userId);
    }

    //这里编写方法,可以返回在线用户列表
    public static String getOnlineUser(){
        //遍历集合的key
        Iterator<String> iterator = hm.keySet().iterator();
        String onlineUserList = "";
        while (iterator.hasNext()) {
            onlineUserList +=  iterator.next().toString() + " ";
        }
        return onlineUserList;
    }

    //将线程从hm集合中移除
    public static void removeServerConnectClientThread(String userId){
        hm.remove(userId);
    }
}
package com.qqserver.service;

import com.qqcommon.Message;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/30-17:51
 * @describe 提供离线信息保存和发送功能
 */
public class OfflineMessageService {
    private static ConcurrentHashMap<String, ArrayList<Message>> offlineMessageDb = null;

    public static void setOfflineMessageDb(ConcurrentHashMap<String, ArrayList<Message>> offlineMessageDb) {
        OfflineMessageService.offlineMessageDb = offlineMessageDb;
    }

    //保存离线用户的信息
    public static void addToOfflineMessageDb(String userId, Message message){
        if(!offlineMessageDb.containsKey(userId)){
            offlineMessageDb.put(userId, new ArrayList<>());
        }
        ArrayList<Message> list = offlineMessageDb.get(userId);
        list.add(message);
    }

    //发送离线信息给重新上线的用户
    public static void sendMessageToUser(String userId){
        if(!offlineMessageDb.containsKey(userId)){
            return;
        }
        ArrayList<Message> list = offlineMessageDb.get(userId);
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Message next = (Message) iterator.next();
            try {
                ObjectOutputStream oos = new ObjectOutputStream(ManageClientThreads.getServerConnectClientThread(userId).getSocket().getOutputStream());
                oos.writeObject(next);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        offlineMessageDb.remove(userId);
    }
}
package com.qqserver.service;

import com.qqcommon.Message;
import com.qqcommon.MessageType;
import com.qqcommon.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-21:21
 * @describe 这是服务器,在监听9999,等待客户端的连接,并保持通信
 */
public class QQServer {
    private ServerSocket ss = null;
    //创建一个集合,存放多个用户,如果是这些用户登录,就认为是合法的
    //这里我们也可以使用 ConcurrentHashMap ,这是可以处理并发的集合
    //HashMap 没有处理线程安全,因此在多线程情况下是不安全的
    //ConcurrentHashMap 处理了线程安全,即线程同步处理,在多线程下是安全的
    private static ConcurrentHashMap<String, User> validUsers = new ConcurrentHashMap<>();
    private static ConcurrentHashMap<String, ArrayList<Message>> offlineMessageDb = new ConcurrentHashMap<>();

    static {//在静态代码块。初始化validUsers
        validUsers.put("100", new User("100", "123456"));
        validUsers.put("200", new User("200", "123456"));
        validUsers.put("300", new User("300", "123456"));
        validUsers.put("李白", new User("李白", "123456"));
        validUsers.put("杜甫", new User("杜甫", "123456"));
        validUsers.put("高适", new User("高适", "123456"));
    }

    //验证用户是否合法
    private boolean checkUser(String userId, String passwd){
        User user = validUsers.get(userId);
        if(user == null){//判断有没有没有此用户
            return false;
        }
        if(!(user.getPasswd().equals(passwd))){//判断密码是否正确
            return false;
        }
        return true;
    }

    public QQServer() {
        System.out.println("服务端正在 9999 端口监听...");
        OfflineMessageService.setOfflineMessageDb(offlineMessageDb);
        new Thread(new SendNewsToAllService()).start();
        try {
            ss = new ServerSocket(9999);

            while(true){ //当和某个客户端连接后,会继续监听,因此while
                Socket socket = ss.accept();//如果没有客户端连接,将会阻塞在这里
                //得到socket关联的对象输入流
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //得到socket关联的对象输出流
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                User u = (User) ois.readObject();//读取客户端发送的User对象
                //创建一个message对象,准备回复客户端
                Message message = new Message();
                //验证
                if(checkUser(u.getUserId(), u.getPasswd())){//登录验证
                    System.out.println("用户:" + u.getUserId() + " 密码:" + u.getPasswd()+ " 验证成功");
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
                    //将message对象回复客户端
                    oos.writeObject(message);
                    //创建一个线程,和客户端保持通信,该线程需要持有socket对象
                    ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket, u.getUserId(), offlineMessageDb);
                    //启动线程
                    serverConnectClientThread.start();
                    //把该线程对象放入一个集合,便于管理
                    ManageClientThreads.addServerConnectClientThread(u.getUserId(), serverConnectClientThread);
                    //发送离线信息给用户
                    OfflineMessageService.sendMessageToUser(u.getUserId());
                } else {//登录失败
                    System.out.println("用户:" + u.getUserId() + " 密码:" + u.getPasswd()+ " 验证失败");
                    message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                    //将message对象回复客户端
                    oos.writeObject(message);
                    //关闭socket
                    socket.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            //退出while循环,说明不再监听,应该关闭ServerSocket
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


}
package com.qqserver.service;

import com.qqcommon.Message;
import com.qqcommon.MessageType;
import com.utils.Utility;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/30-17:19
 * @describe 该类可作为线程单独运行,随时可以同时向全部客户端用户发送消息
 */
public class SendNewsToAllService implements Runnable{
    @Override
    public void run() {
        while(true) {
            System.out.println("请输入服务器要推送的新闻/消息[输入 exit 退出推送服务]");
            String news = Utility.readString(100);
            if(news.equals("exit")){
                System.out.println(">> 成功退出推送服务,服务器仍在运行...");
                break;
            }
            //构建一个Message并群发
            Message message = new Message();
            message.setContent(news);
            message.setMesType(MessageType.MESSAGE_TO_ALL);
            message.setSender("服务器");
            message.setSendTime(new Date().toString());
            System.out.println("服务器推送消息给所有人说:" + news);

            //遍历当前所有的通信线程,得到socket,并发送message
            String[] users = ManageClientThreads.getOnlineUser().split(" ");
            String user = "";
            for (int i = 0; i < users.length; i++) {
                user = users[i];
                message.setGetter(user);
                if(!ManageClientThreads.getHm().containsKey(message.getGetter())){
                    OfflineMessageService.addToOfflineMessageDb(message.getGetter(), message);
                    continue;
                }
                try {
                    ObjectOutputStream oos = new ObjectOutputStream(ManageClientThreads.getServerConnectClientThread(user).getSocket().getOutputStream());
                    oos.writeObject(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}
package com.qqserver.service;

import com.qqcommon.Message;
import com.qqcommon.MessageType;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-21:45
 * @describe 该类的一个对象和某个客户端保持通信
 */
public class ServerConnectClientThread extends Thread{
    private ConcurrentHashMap<String, ArrayList<Message>> offlineMessageDb = null;
    private Socket socket;

    public Socket getSocket() {
        return socket;
    }

    private String userId;//连接到服务器的用户id

    public ServerConnectClientThread(Socket socket, String userId, ConcurrentHashMap<String, ArrayList<Message>> offlineMessageDb) {
        this.offlineMessageDb = offlineMessageDb;
        this.socket = socket;
        this.userId = userId;
    }

    @Override
    public void run() {
        while(true){
            try {
                System.out.println("服务端和客户端" + userId + "保持通信,读取数据...");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                Message message = (Message) ois.readObject();

                //根据message的类型,做相应的业务处理
                if(message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)){
                    //客户端要求返回在线用户信息
                    System.out.println(message.getSender() + " 要求返回在线用户信息");
                    String onlineUser = ManageClientThreads.getOnlineUser();
                    Message message2 = new Message();
                    message2.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
                    message2.setGetter(message.getSender());
                    message2.setContent(onlineUser);
                    //返回给客户端
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message2);
                } else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
                    System.out.println("\n[" + message.getSendTime() + "] " + message.getSender() + " 对 " + message.getGetter() + " 说:" + message.getContent());
                    if(!ManageClientThreads.getHm().containsKey(message.getGetter())){
                        OfflineMessageService.addToOfflineMessageDb(message.getGetter(), message);
                        continue;
                    }
                    //根据消息的接收者,找到对应的socket,通过输出流发送信息给客户端
                    ObjectOutputStream oos = new ObjectOutputStream(ManageClientThreads.getServerConnectClientThread(message.getGetter()).getSocket().getOutputStream());
                    oos.writeObject(message);//转发,如果客户不在线,可以保存到数据库,这样可以实现离线留言

                } else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL)) {
                    System.out.println("\n[" + message.getSendTime() + "] " + message.getSender() + " 对大家说:" + message.getContent() + "\n");
                    String[] users = ManageClientThreads.getOnlineUser().split(" ");
                    String user = "";
                    for (int i = 0; i < users.length; i++){
                        user = users[i];
                        if(user.equals(message.getSender())){
                            break;
                        }
                        message.setGetter(user);
                        if(!ManageClientThreads.getHm().containsKey(message.getGetter())){
                            OfflineMessageService.addToOfflineMessageDb(message.getGetter(), message);
                            continue;
                        }
                        ObjectOutputStream oos = new ObjectOutputStream(ManageClientThreads.getServerConnectClientThread(user).getSocket().getOutputStream());
                        oos.writeObject(message);//转发,如果客户不在线,可以保存到数据库,这样可以实现离线留言
                    }
                } else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {
                    System.out.println("用户 " + message.getSender() + " 退出客户端");
                    //将这个客户端对应的服务端这边的线程从集合中删除
                    ManageClientThreads.removeServerConnectClientThread(message.getSender());
                    socket.close();//关闭连接
                    //退出线程
                    break;
                } else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
                    System.out.println("\n[" + message.getSendTime() + "] " + message.getSender() + " 给 " + message.getGetter() + " 发送文件:" + message.getSrcFileName() + " 到 " + message.getDestFileName() + "\n");
                    if(!ManageClientThreads.getHm().containsKey(message.getGetter())){
                        OfflineMessageService.addToOfflineMessageDb(message.getGetter(), message);
                        continue;
                    }
                    //根据消息的接收者,找到对应的socket,通过输出流发送信息给客户端
                    ObjectOutputStream oos = new ObjectOutputStream(ManageClientThreads.getServerConnectClientThread(message.getGetter()).getSocket().getOutputStream());
                    oos.writeObject(message);//转发,如果客户不在线,可以保存到数据库,这样可以实现离线留言
                } else {
                    System.out.println("是其他类型的Message,暂时不处理......");
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
package com.utils;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * 此类用于演示关于流的读写方法
 *
 */
public class StreamUtils {
    /**
     * 功能:将输入流转换成byte[], 即可以把文件的内容读入到byte[]
     * @param is
     * @return
     * @throws Exception
     */
    public static byte[] streamToByteArray(InputStream is) throws Exception{
       ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
       byte[] b = new byte[1024];//字节数组
       int len;
       while((len=is.read(b))!=-1){//循环读取
          bos.write(b, 0, len);//把读取到的数据,写入bos   
       }
       byte[] array = bos.toByteArray();//然后将bos 转成字节数组
       bos.close();
       return array;
    }
    /**
     * 功能:将InputStream转换成String
     * @param is
     * @return
     * @throws Exception
     */
    
    public static String streamToString(InputStream is) throws Exception{
       BufferedReader reader = new BufferedReader(new InputStreamReader(is));
       StringBuilder builder= new StringBuilder();
       String line;
       while((line=reader.readLine())!=null){
          builder.append(line+"\r\n");
       }
       return builder.toString();
       
    }

}
package com.utils;


/**
    工具类的作用:
    处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。
*/

import java.util.Scanner;

/**

    
*/
public class Utility {
    //静态属性。。。
    private static Scanner scanner = new Scanner(System.in);

    
    /**
     * 功能:读取键盘输入的一个菜单选项,值:1——5的范围
     * @return 1——5
     */
    public static char readMenuSelection() {
        char c;
        for (; ; ) {
            String str = readKeyBoard(1, false);//包含一个字符的字符串
            c = str.charAt(0);//将字符串转换成字符char类型
            if (c != '1' && c != '2' && 
                c != '3' && c != '4' && c != '5') {
                System.out.print("选择错误,请重新输入:");
            } else break;
        }
        return c;
    }

    /**
     * 功能:读取键盘输入的一个字符
     * @return 一个字符
     */
    public static char readChar() {
        String str = readKeyBoard(1, false);//就是一个字符
        return str.charAt(0);
    }
    /**
     * 功能:读取键盘输入的一个字符,如果直接按回车,则返回指定的默认值;否则返回输入的那个字符
     * @param defaultValue 指定的默认值
     * @return 默认值或输入的字符
     */
    
    public static char readChar(char defaultValue) {
        String str = readKeyBoard(1, true);//要么是空字符串,要么是一个字符
        return (str.length() == 0) ? defaultValue : str.charAt(0);
    }
    
    /**
     * 功能:读取键盘输入的整型,长度小于2位
     * @return 整数
     */
    public static int readInt() {
        int n;
        for (; ; ) {
            String str = readKeyBoard(10, false);//一个整数,长度<=10位
            try {
                n = Integer.parseInt(str);//将字符串转换成整数
                break;
            } catch (NumberFormatException e) {
                System.out.print("数字输入错误,请重新输入:");
            }
        }
        return n;
    }
    /**
     * 功能:读取键盘输入的 整数或默认值,如果直接回车,则返回默认值,否则返回输入的整数
     * @param defaultValue 指定的默认值
     * @return 整数或默认值
     */
    public static int readInt(int defaultValue) {
        int n;
        for (; ; ) {
            String str = readKeyBoard(10, true);
            if (str.equals("")) {
                return defaultValue;
            }
          
          //异常处理...
            try {
                n = Integer.parseInt(str);
                break;
            } catch (NumberFormatException e) {
                System.out.print("数字输入错误,请重新输入:");
            }
        }
        return n;
    }

    /**
     * 功能:读取键盘输入的指定长度的字符串
     * @param limit 限制的长度
     * @return 指定长度的字符串
     */

    public static String readString(int limit) {
        return readKeyBoard(limit, false);
    }

    /**
     * 功能:读取键盘输入的指定长度的字符串或默认值,如果直接回车,返回默认值,否则返回字符串
     * @param limit 限制的长度
     * @param defaultValue 指定的默认值
     * @return 指定长度的字符串
     */
    
    public static String readString(int limit, String defaultValue) {
        String str = readKeyBoard(limit, true);
        return str.equals("")? defaultValue : str;
    }


    /**
     * 功能:读取键盘输入的确认选项,Y或N
     * 将小的功能,封装到一个方法中.
     * @return Y或N
     */
    public static char readConfirmSelection() {
        System.out.println("请输入你的选择(Y/N): 请小心选择");
        char c;
        for (; ; ) {//无限循环
            //在这里,将接受到字符,转成了大写字母
            //y => Y n=>N
            String str = readKeyBoard(1, false).toUpperCase();
            c = str.charAt(0);
            if (c == 'Y' || c == 'N') {
                break;
            } else {
                System.out.print("选择错误,请重新输入:");
            }
        }
        return c;
    }

    /**
     * 功能: 读取一个字符串
     * @param limit 读取的长度
     * @param blankReturn 如果为true ,表示 可以读空字符串。 
     *                如果为false表示 不能读空字符串。
     *        
     * 如果输入为空,或者输入大于limit的长度,就会提示重新输入。
     * @return
     */
    private static String readKeyBoard(int limit, boolean blankReturn) {
        
       //定义了字符串
       String line = "";

       //scanner.hasNextLine() 阻塞在这一行,直到有回车,返回true
        while (scanner.hasNextLine()) {
            line = scanner.nextLine();//读取这一行
           
          //如果line.length=0, 即用户没有输入任何内容,直接回车
          if (line.length() == 0) {
                if (blankReturn) return line;//如果blankReturn=true,可以返回空串
                else continue; //如果blankReturn=false,不接受空串,必须输入内容
            }

          //如果用户输入的内容大于了 limit,就提示重写输入  
          //如果用户如的内容 >0 <= limit ,我就接受
            if (line.length() < 1 || line.length() > limit) {
                System.out.print("输入长度(不能大于" + limit + ")错误,请重新输入:");
                continue;
            }
            break;
        }

        return line;
    }
}
package com.qqcommon;

import java.io.Serializable;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-16:10
 * @describe 表示客户端和服务端通讯的一个消息对象
 */
public class Message implements Serializable {
    private  static final long serialVersionUID = 1L;//保证兼容性
    private String sender;//发送者
    private String getter;//接收者
    private String content;//消息内容
    private String sendTime;//发送时间
    private String mesType;//消息类型【可以在接口定义消息类型】
    private String srcFileName;//文件源路径
    private String destFileName;//文件目的路径
    private byte[] bytes;//存放二进制文件字节数组

    public byte[] getBytes() {
        return bytes;
    }

    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }

    public String getSrcFileName() {
        return srcFileName;
    }

    public void setSrcFileName(String srcFileName) {
        this.srcFileName = srcFileName;
    }

    public String getDestFileName() {
        return destFileName;
    }

    public void setDestFileName(String destFileName) {
        this.destFileName = destFileName;
    }

    public Message() {

    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getGetter() {
        return getter;
    }

    public void setGetter(String getter) {
        this.getter = getter;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getSendTime() {
        return sendTime;
    }

    public void setSendTime(String sendTime) {
        this.sendTime = sendTime;
    }

    public String getMesType() {
        return mesType;
    }

    public void setMesType(String mesType) {
        this.mesType = mesType;
    }
}
package com.qqcommon;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-16:19
 * @describe 表示消息类型
 */
public interface MessageType {
    //在接口中定义了一些常量,不同常量的值表示不同的消息类型
    String MESSAGE_LOGIN_SUCCEED = "1";//表示登录成功
    String MESSAGE_LOGIN_FAIL = "2";//表示登录失败
    String MESSAGE_COMM_MES = "3";//普通信息包
    String MESSAGE_GET_ONLINE_FRIEND = "4";//要求得到在线用户列表
    String MESSAGE_RET_ONLINE_FRIEND = "5";//返回在线用户列表
    String MESSAGE_CLIENT_EXIT = "6";//客户端请求退出
    String MESSAGE_TO_ALL = "7";//群发数据报
    String MESSAGE_FILE_MES = "8";//文件数据报
}
package com.qqcommon;

import java.io.Serializable;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-16:10
 * @describe 表示一个用户信息
 */
public class User implements Serializable {
    private  static final long serialVersionUID = 1L;//保证兼容性
    private String userId;//用户Id
    private String passwd;//用户密码

    public User() {
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }

    public User(String userId, String passwd) {
        this.userId = userId;
        this.passwd = passwd;
    }
}
客户端

客户端结构:

在这里插入图片描述

package com.qqclient.service;

import com.qqcommon.Message;
import com.qqcommon.MessageType;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-17:42
 * @describe 客户端与服务端保持连接的线程类
 */
public class ClientConnectServerThread extends Thread{
    //该线程需要持有socket
    private Socket socket;

    //构造器可以接收一个Socket对象
    public ClientConnectServerThread(Socket socket){
        this.socket = socket;
    }

    //为了更加方便得到Socket
    public Socket getSocket() {
        return socket;
    }

    @Override
    public void run() {
        //因为Thread需要在后台和服务器通信,因此我们使用While循环
        while(true){
            try {
                System.out.println("客户端线程,等待读取从服务端发送过来的数据...");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                //如果服务器没有发送Message对象,线程会阻塞在这里
                Message message = (Message) ois.readObject();
                //判断这个Message的类型,然后做相应的处理
                //如果读到的是服务端返回的在线用户列表,那么按照以下代码处理
                if(message.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)){
                    //取出在线用户列表,并显示
                    String[] onlineUsers = message.getContent().split(" ");
                    System.out.println("\n=======当前在线用户列表=======");
                    for(int i = 0;i < onlineUsers.length; i++){
                        System.out.println("用户:" + onlineUsers[i]);
                    }
                } else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {
                    System.out.println("\n[" + message.getSendTime() + "] " + message.getSender() + " 对 " + message.getGetter() + " 说:" + message.getContent() + "\n");
                } else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL)) {
                    System.out.println("\n[" + message.getSendTime() + "] " + message.getSender() + " 对大家说:" + message.getContent() + "\n");
                } else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {
                    System.out.println("\n[" + message.getSendTime() + "] " + message.getSender() + " 给 " + message.getGetter() + " 发送文件:" + message.getSrcFileName() + " 到 " + message.getDestFileName() + "\n");
                    byte[] bytes = message.getBytes();
                    FileOutputStream fileOutputStream = new FileOutputStream(message.getDestFileName());
                    fileOutputStream.write(bytes);
                    fileOutputStream.close();
                } else {
                    System.out.println("是其他类型的Message,暂时不处理......");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
package com.qqclient.service;

import java.util.HashMap;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-20:56
 * @describe 该类管理客户端连接到服务器端的线程的类
 */
public class MangeClientConnectServerThread {
    //我们把多个线程放入一个HashMap集合,key就是用户Id,value就是线程
    private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();

    //将某个线程加入到集合
    public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread) {
        hm.put(userId, clientConnectServerThread);
    }

    //提供userId可以得到对应的线程
    public static ClientConnectServerThread getClientConnectServerThread(String userId) {
        return hm.get(userId);
    }
}
package com.qqclient.service;

import com.qqclient.utils.StreamUtils;
import com.qqcommon.Message;
import com.qqcommon.MessageType;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/29-21:27
 * @describe 该类提供与消息相关的服务方法
 */
public class MessageClientService {
    /**
     * @param content  内容
     * @param senderId 发送者的id
     * @param getterId 接收者的id
     */
    public void sendMessageToOne(String content, String senderId, String getterId) {
        //构建Message
        Message message = new Message();
        message.setSender(senderId);
        message.setGetter(getterId);
        message.setContent(content);
        message.setMesType(MessageType.MESSAGE_COMM_MES);//普通的聊天消息
        message.setSendTime(new Date().toString());//设置发送时间给message对象

        try {
            ObjectOutputStream oos = new ObjectOutputStream(MangeClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
            oos.writeObject(message);
            System.out.println("\n[" + message.getSendTime() + "] " + message.getSender() + " 对 " + message.getGetter() + " 说:" + message.getContent() + "\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 群发消息
     * @param content 群发内容
     * @param senderId 发送者
     */
    public void sendMessageToEveryone(String content, String senderId){
        //构建Message
        Message message = new Message();
        message.setSender(senderId);
        message.setContent(content);
        message.setMesType(MessageType.MESSAGE_TO_ALL);//普通的聊天消息
        message.setSendTime(new Date().toString());//设置发送时间给message对象

        try {
            ObjectOutputStream oos = new ObjectOutputStream(MangeClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
            oos.writeObject(message);
            System.out.println("\n[" + message.getSendTime() + "] " + message.getSender() + " 对大家说:" + message.getContent() + "\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * C to C 文件发送
     * @param senderId 发送者
     * @param getterId 接收者
     * @param srcFileName 发送者的文件路径
     * @param destFileName 接收者的文件路径
     */
    public void sendFileToOne(String senderId, String getterId, String srcFileName, String destFileName){
        File file = new File(srcFileName);
        if(!file.exists()){
            System.out.println(">> 文件不存在!");
            return;
        }
        try {
            byte[] bytes = StreamUtils.streamToByteArray(new FileInputStream(srcFileName));
            //构建Message
            Message message = new Message();
            message.setSender(senderId);
            message.setGetter(getterId);
            message.setMesType(MessageType.MESSAGE_FILE_MES);
            message.setBytes(bytes);
            message.setSrcFileName(srcFileName);
            message.setDestFileName(destFileName);
            message.setSendTime(new Date().toString());//设置发送时间给message对象

            ObjectOutputStream oos = new ObjectOutputStream(MangeClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
            oos.writeObject(message);
            System.out.println("\n[" + message.getSendTime() + "] " + message.getSender() + " 给 " + message.getGetter() + " 发送文件:" + message.getSrcFileName() + " 到 " + message.getDestFileName() + "\n");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
package com.qqclient.service;

import com.qqcommon.Message;
import com.qqcommon.MessageType;
import com.qqcommon.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-17:17
 * @describe 该类提供用户登录验证和用户注册等服务
 */
public class UserClientService {
    //因为可能在其他地方用到user信息,所以把它设为一个属性
    private User u = new User();
    //因为可能在其他地方用到socket信息,所以把它设为一个属性
    private Socket socket;

    //根据userId 和 pwd 到服务器验证该用户是否合法
    public boolean checkUser(String userId, String pwd){
        boolean b = false;
        //创建User对象
        u.setUserId(userId);
        u.setPasswd(pwd);

        try {
            //连接到服务端,发送u对象
            socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);//本机运行
            //socket = new Socket(InetAddress.getByName("120.46.64.148"), 9999);//部署到服务器

            //得到 ObjectOutputStream 对象
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(u);//发送User对象

            //读取服务器发回来的 Message 对象
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Message ms = (Message) ois.readObject();

            if(ms.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)){//登录成功

                //创建一个和服务器保持通信的线程 -> 创建一个类ClientConnectServerThread
                ClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket);
                //启动客户端的线程
                clientConnectServerThread.start();
                //这里为了后面客户端的扩展,我们将线程放入集合管理
                MangeClientConnectServerThread.addClientConnectServerThread(userId, clientConnectServerThread);
                b = true;

            } else {
                //如果登录失败,我们就不可以启动和服务器通信的线程,关闭socket
                socket.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return b;
    }

    //向服务器端请求在线用户列表
    public void onlineFriendList(){
        //发送一个Message,类型MESSAGE_GET_ONLINE_FRIEND
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
        message.setSender(u.getUserId());

        //发给服务器,应该得到当前线程的Socket对应的ObjectOutputStream对象
        try {
            //通过userId得到对应的线程,从线程里面获取socket对象,再从Socket对象里面获取OutputStream对象
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(MangeClientConnectServerThread.getClientConnectServerThread(u.getUserId()).getSocket().getOutputStream());
            objectOutputStream.writeObject(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //编写方法,退出客户端,并给客户端发送一个message对象,告诉服务端自己要退出系统了
    public void logout(){
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
        message.setSender(u.getUserId());//一定要说明自己是哪一个用户

        //发送Message
        try {
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(message);
            System.out.println(u.getUserId() + " --退出系统--");
            System.exit(0);//结束进程,该客户端的线程也会一并关掉
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //自己编写的私聊方法
//    public void PrivateChat(){
//        String friend = "";
//        String data = "";
//        Scanner scanner = new Scanner(System.in);
//        System.out.print(">> 请问您要给谁发送消息:");
//        friend = scanner.next();
//        System.out.print(">> 请输入您想要对他说的话:");
//        data = scanner.next();
//
//        Message message = new Message();
//        message.setMesType(MessageType.MESSAGE_COMM_MES);
//        message.setSender(u.getUserId());//一定要说明自己是哪一个用户
//        message.setGetter(friend);
//        message.setContent(data);
//
//        //发送Message
//        try {
//            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
//            oos.writeObject(message);
//            System.out.println(u.getUserId() + " 对 " + friend + " 说:" + data);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }
}
package com.qqclient.utils;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * 此类用于演示关于流的读写方法
 *
 */
public class StreamUtils {
    /**
     * 功能:将输入流转换成byte[], 即可以把文件的内容读入到byte[]
     * @param is
     * @return
     * @throws Exception
     */
    public static byte[] streamToByteArray(InputStream is) throws Exception{
       ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
       byte[] b = new byte[1024];//字节数组
       int len;
       while((len=is.read(b))!=-1){//循环读取
          bos.write(b, 0, len);//把读取到的数据,写入bos   
       }
       byte[] array = bos.toByteArray();//然后将bos 转成字节数组
       bos.close();
       return array;
    }
    /**
     * 功能:将InputStream转换成String
     * @param is
     * @return
     * @throws Exception
     */
    
    public static String streamToString(InputStream is) throws Exception{
       BufferedReader reader = new BufferedReader(new InputStreamReader(is));
       StringBuilder builder= new StringBuilder();
       String line;
       while((line=reader.readLine())!=null){
          builder.append(line+"\r\n");
       }
       return builder.toString();
       
    }

}
package com.qqclient.utils;


/**
    工具类的作用:
    处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。
*/

import java.util.Scanner;

/**

    
*/
public class Utility {
    //静态属性。。。
    private static Scanner scanner = new Scanner(System.in);

    
    /**
     * 功能:读取键盘输入的一个菜单选项,值:1——5的范围
     * @return 1——5
     */
    public static char readMenuSelection() {
        char c;
        for (; ; ) {
            String str = readKeyBoard(1, false);//包含一个字符的字符串
            c = str.charAt(0);//将字符串转换成字符char类型
            if (c != '1' && c != '2' && 
                c != '3' && c != '4' && c != '5') {
                System.out.print("选择错误,请重新输入:");
            } else break;
        }
        return c;
    }

    /**
     * 功能:读取键盘输入的一个字符
     * @return 一个字符
     */
    public static char readChar() {
        String str = readKeyBoard(1, false);//就是一个字符
        return str.charAt(0);
    }
    /**
     * 功能:读取键盘输入的一个字符,如果直接按回车,则返回指定的默认值;否则返回输入的那个字符
     * @param defaultValue 指定的默认值
     * @return 默认值或输入的字符
     */
    
    public static char readChar(char defaultValue) {
        String str = readKeyBoard(1, true);//要么是空字符串,要么是一个字符
        return (str.length() == 0) ? defaultValue : str.charAt(0);
    }
    
    /**
     * 功能:读取键盘输入的整型,长度小于2位
     * @return 整数
     */
    public static int readInt() {
        int n;
        for (; ; ) {
            String str = readKeyBoard(10, false);//一个整数,长度<=10位
            try {
                n = Integer.parseInt(str);//将字符串转换成整数
                break;
            } catch (NumberFormatException e) {
                System.out.print("数字输入错误,请重新输入:");
            }
        }
        return n;
    }
    /**
     * 功能:读取键盘输入的 整数或默认值,如果直接回车,则返回默认值,否则返回输入的整数
     * @param defaultValue 指定的默认值
     * @return 整数或默认值
     */
    public static int readInt(int defaultValue) {
        int n;
        for (; ; ) {
            String str = readKeyBoard(10, true);
            if (str.equals("")) {
                return defaultValue;
            }
          
          //异常处理...
            try {
                n = Integer.parseInt(str);
                break;
            } catch (NumberFormatException e) {
                System.out.print("数字输入错误,请重新输入:");
            }
        }
        return n;
    }

    /**
     * 功能:读取键盘输入的指定长度的字符串
     * @param limit 限制的长度
     * @return 指定长度的字符串
     */

    public static String readString(int limit) {
        return readKeyBoard(limit, false);
    }

    /**
     * 功能:读取键盘输入的指定长度的字符串或默认值,如果直接回车,返回默认值,否则返回字符串
     * @param limit 限制的长度
     * @param defaultValue 指定的默认值
     * @return 指定长度的字符串
     */
    
    public static String readString(int limit, String defaultValue) {
        String str = readKeyBoard(limit, true);
        return str.equals("")? defaultValue : str;
    }


    /**
     * 功能:读取键盘输入的确认选项,Y或N
     * 将小的功能,封装到一个方法中.
     * @return Y或N
     */
    public static char readConfirmSelection() {
        System.out.println("请输入你的选择(Y/N): 请小心选择");
        char c;
        for (; ; ) {//无限循环
            //在这里,将接受到字符,转成了大写字母
            //y => Y n=>N
            String str = readKeyBoard(1, false).toUpperCase();
            c = str.charAt(0);
            if (c == 'Y' || c == 'N') {
                break;
            } else {
                System.out.print("选择错误,请重新输入:");
            }
        }
        return c;
    }

    /**
     * 功能: 读取一个字符串
     * @param limit 读取的长度
     * @param blankReturn 如果为true ,表示 可以读空字符串。 
     *                如果为false表示 不能读空字符串。
     *        
     * 如果输入为空,或者输入大于limit的长度,就会提示重新输入。
     * @return
     */
    private static String readKeyBoard(int limit, boolean blankReturn) {
        
       //定义了字符串
       String line = "";

       //scanner.hasNextLine() 阻塞在这一行,直到有回车,返回true
        while (scanner.hasNextLine()) {
            line = scanner.nextLine();//读取这一行
           
          //如果line.length=0, 即用户没有输入任何内容,直接回车
          if (line.length() == 0) {
                if (blankReturn) return line;//如果blankReturn=true,可以返回空串
                else continue; //如果blankReturn=false,不接受空串,必须输入内容
            }

          //如果用户输入的内容大于了 limit,就提示重写输入  
          //如果用户如的内容 >0 <= limit ,我就接受
            if (line.length() < 1 || line.length() > limit) {
                System.out.print("输入长度(不能大于" + limit + ")错误,请重新输入:");
                continue;
            }
            break;
        }

        return line;
    }
}
package com.qqclient.view;

import com.qqclient.service.MessageClientService;
import com.qqclient.service.UserClientService;
import com.qqclient.utils.Utility;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-16:33
 * @describe 该类提供本系统的界面
 */
public class QQView {
    private boolean loop = true;//控制是否显示菜单
    private String key = "";//接收用户的输入
    private UserClientService userClientService = new UserClientService();//用来登录服务器或者注册用户
    private MessageClientService messageClientService = new MessageClientService();//用于用户私聊或者群聊

    public static void main(String[] args) {
        new QQView().mainMenu();
        System.out.println(">> --客户端-- 成功退出网络通信系统");
    }

    //显示主菜单
    private void mainMenu() {
        while (loop) {
            System.out.println("=======欢迎登录网络通信系统=======");
            System.out.println("\t\t 1 登录系统");
            System.out.println("\t\t 9 退出系统");

            //读取用户的选择
            System.out.print("\n>> 请输入你的选择 : ");
            key = Utility.readString(1);

            //根据用户的输入,来处理不同的逻辑
            switch (key) {
                case "1":
                    System.out.print(">> 请输入用户号:");
                    String userId = Utility.readString(50);
                    System.out.print(">> 请输入密  码:");
                    String pwd = Utility.readString(50);
                    //然后需要拿着用户ID和密码到服务器去验证是否合法。由于这里比较复杂,需要编写一个类 UserClientService[用户登录/注册]
                    if(userClientService.checkUser(userId, pwd)){
                        System.out.println(">> 欢迎用户 " + userId + " 成功登录!");
                        //进入二级菜单
                        while (loop){
                            System.out.println("\n =======网络通信系统二级菜单(用户 " + userId + " )=======");
                            System.out.println("\t\t 1 显示在线用户列表");
                            System.out.println("\t\t 2 群发信息");
                            System.out.println("\t\t 3 私聊信息");
                            System.out.println("\t\t 4 发送文件");
                            System.out.println("\t\t 9 退出系统");
                            System.out.print("\n>> 请输入你的选择 : ");
                            String content = "";
                            String getterId = "";
                            key = Utility.readString(1);
                            switch (key) {
                                case "1":
                                    System.out.println(">> 显示在线用户列表");
                                    userClientService.onlineFriendList();
                                    break;
                                case "2":
                                    System.out.println(">> 群发信息");
                                    System.out.println("请输入想要说的话:");
                                    content = Utility.readString(100);
                                    messageClientService.sendMessageToEveryone(content, userId);
                                    break;
                                case "3":
                                    System.out.println(">> 私聊信息");
                                    //userClientService.PrivateChat();//自己编写的私聊方法
                                    System.out.println("请输入想聊天的用户号(在线):");
                                    getterId = Utility.readString(50);
                                    System.out.println("请输入想要说的话:");
                                    content = Utility.readString(100);
                                    //编写一个方法,将消息发送给服务端
                                    messageClientService.sendMessageToOne(content, userId, getterId);
                                    break;
                                case "4":
                                    System.out.println(">> 发送文件");
                                    System.out.print("请输入想给其发送文件的用户号(在线):");
                                    getterId = Utility.readString(50);
                                    System.out.print("请输入想要发送的文件(完整文件路径例如 d:\\Hello.txt):");
                                    String srcFileName = Utility.readString(100);
                                    System.out.print("请输入对方的保存路径(完整文件路径例如 e:\\Hello.txt):");
                                    String destFileName = Utility.readString(100);
                                    //编写一个方法,将文件发送给服务端
                                    messageClientService.sendFileToOne(userId, getterId, srcFileName, destFileName);
                                    break;
                                case "9":
                                    System.out.println(">> 退出系统");
                                    userClientService.logout();
                                    loop = false;
                                    break;
                                default:
                                    System.out.println(">> 输入有误,请重新输入\n");
                            }
                        }
                    } else {
                        System.out.println(">> 用户名或者密码输入有误,请重新输入\n");
                    }
                    break;
                case "9":
                    System.out.println(">> 退出系统");
                    loop = false;
                    break;
                default:
                    System.out.println(">> 输入有误,请重新输入\n");
            }
        }
    }
}
package com.qqcommon;

import java.io.Serializable;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-16:10
 * @describe 表示客户端和服务端通讯的一个消息对象
 */
public class Message implements Serializable {
    private  static final long serialVersionUID = 1L;//保证兼容性
    private String sender;//发送者
    private String getter;//接收者
    private String content;//消息内容
    private String sendTime;//发送时间
    private String mesType;//消息类型【可以在接口定义消息类型】
    private String srcFileName;//文件源路径
    private String destFileName;//文件目的路径
    private byte[] bytes;//存放二进制文件字节数组

    public byte[] getBytes() {
        return bytes;
    }

    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }

    public String getSrcFileName() {
        return srcFileName;
    }

    public void setSrcFileName(String srcFileName) {
        this.srcFileName = srcFileName;
    }

    public String getDestFileName() {
        return destFileName;
    }

    public void setDestFileName(String destFileName) {
        this.destFileName = destFileName;
    }

    public Message() {

    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getGetter() {
        return getter;
    }

    public void setGetter(String getter) {
        this.getter = getter;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getSendTime() {
        return sendTime;
    }

    public void setSendTime(String sendTime) {
        this.sendTime = sendTime;
    }

    public String getMesType() {
        return mesType;
    }

    public void setMesType(String mesType) {
        this.mesType = mesType;
    }
}
package com.qqcommon;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-16:19
 * @describe 表示消息类型
 */
public interface MessageType {
    //在接口中定义了一些常量,不同常量的值表示不同的消息类型
    String MESSAGE_LOGIN_SUCCEED = "1";//表示登录成功
    String MESSAGE_LOGIN_FAIL = "2";//表示登录失败
    String MESSAGE_COMM_MES = "3";//普通信息包
    String MESSAGE_GET_ONLINE_FRIEND = "4";//要求得到在线用户列表
    String MESSAGE_RET_ONLINE_FRIEND = "5";//返回在线用户列表
    String MESSAGE_CLIENT_EXIT = "6";//客户端请求退出
    String MESSAGE_TO_ALL = "7";//群发数据报
    String MESSAGE_FILE_MES = "8";//文件数据报
}
package com.qqcommon;

import java.io.Serializable;

/**
 * @author Albert
 * @version 1.0
 * @date 2023/11/28-16:10
 * @describe 表示一个用户信息
 */
public class User implements Serializable {
    private  static final long serialVersionUID = 1L;//保证兼容性
    private String userId;//用户Id
    private String passwd;//用户密码

    public User() {
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }

    public User(String userId, String passwd) {
        this.userId = userId;
        this.passwd = passwd;
    }
}
  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值