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;
}
}