【Java】网络编程综合练习

综合练习1:多发多收

客户端:多次发送数据
服务器:接收多次接收数据,并打印

Client.java

//1. 创建Socket对象并连接服务端
Socket socket = new Socket("127.0.0.1",10000);
//2.写出数据
Scanner sc = new Scanner(System.in);
OutputStream os = socket.getOutputStream(); // 输出流只需要获取一次,因此放在循环外面
while (true) {
    System.out.println("请输入您要发送的信息");
    String str = sc.nextLine();
    if("886".equals(str)){
        break;
    }
    os.write(str.getBytes());
}
//3.释放资源
socket.close();

Server.java

//1.创建对象绑定10000端口(因为客户端在连的时候连的就是10000端口)
ServerSocket ss = new ServerSocket(10000);
//2.等待客户端来连接
Socket socket = ss.accept();
//3.读取数据
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
int b;
while ((b = isr.read()) != -1){
    System.out.print((char)b);
}
//4.释放资源
socket.close();
ss.close();

综合练习2:接收和反馈

客户端:发送一条数据,接收服务端反馈的消息并打印
服务器:接收数据并打印,再给客户端反馈消息

这一题你可以将它看做两部分来写,第一部分:客户端发,服务端接;第二部分:服务端发,客户端再去接。

image-20240508162223677

代码书写时数据的方向如下图

image-20240508162808653

但是上图这个代码是有点问题的,如果直接运行上图代码,服务端回写的字是没有打出来的。

此时我们可以通过输出语句来调试程序,就可以看见它到底卡死在哪里。

image-20240508163126550

运行程序,可以发现控制台读取完客户端传过来的数据后,就没有任何操作了,此时就卡死在32行了。

image-20240508163152973

32行结束后,它其实会回到循环中继续从通道继续读取数据,直到读到数据结尾为止。

在左边的客户端中只给它传输了 "见到你很高兴!",并没有给服务端一个结束标记,因此就会导致右边30行read方法它读不到结束标记,读不到技术标记程序就会停在read方法这个地方,继续等待你下面的数据,这个就是卡死的原因。

完整代码如下。

Client.java

//1.创建Socket对象并连接服务端
Socket socket = new Socket("127.0.0.1",10000);
//2.写出数据
String str = "见到你很高兴!";
OutputStream os = socket.getOutputStream();
os.write(str.getBytes());
//写出一个结束标记,不要写-1,而是需要用socket调用shutdownOutput()方法,即将输出流结束,可以将它理解为结束标记
socket.shutdownOutput();
//3.接收服务端回写的数据
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
int b;
while ((b = isr.read()) != -1){
    System.out.print((char)b);
}
//释放资源
socket.close();

Server.java

//1.创建对象并绑定10000端口
ServerSocket ss = new ServerSocket(10000);
//2.等待客户端连接
Socket socket = ss.accept();
//3.socket中获取输入流读取数据
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
int b;
//细节:
//read方法会从连接通道中读取数据
//但是,需要有一个结束标记,此处的循环才会停止
//否则,程序就会一直停在read方法这里,等待读取下面的数据
while ((b = isr.read()) != -1){
    System.out.println((char)b);
}
//4.回写数据
String str = "到底有多开心?";
OutputStream os = socket.getOutputStream();
os.write(str.getBytes());
//释放资源
socket.close();
ss.close();

综合练习3:上传文件

客户端:将本地文件上传到服务器。接收服务器的反馈。
服务器:接收客户端上传的文件,上传完毕之后给出反馈。

针对于客户端而言,首先要从本地文件中利用 FileInputStream 读取本地文件里面的信息,读一个就需要将数据写到服务器中。

服务器接收到后,要把这个数据保存到本地文件中,当这个文件全都上传完毕后,需要给客户端做一个回写,回写:上传成功。

image-20240508170657562

Client.java

//1. 创建Socket对象,并连接服务器
Socket socket = new Socket("127.0.0.1", 10000);
//2.读取本地文件中的数据,并写到服务器当中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
    bos.write(bytes, 0, len);
}
//往服务器写出结束标记
socket.shutdownOutput();
//3.接收服务器的回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = br.readLine();
System.out.println(line);
//4.释放资源
socket.close();

Server.java

//1.创建对象并绑定端口
ServerSocket ss = new ServerSocket(10000);
//2.等待客户端来连接
Socket socket = ss.accept();
//3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\a.jpg"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
    bos.write(bytes, 0, len);
}
bos.close();
//4.回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
//5.释放资源
socket.close();
ss.close();

综合练习4:上传文件(文件名重复问题)

客户端:将本地文件上传到服务器。接收服务器的反馈。
服务器:接收客户端上传的文件,上传完毕之后给出反馈。
解决上一题文件名重复问题,刚刚我们在上次文件的时候,每次上传文件的名字是固定的,那能不能解决文件重名的问题呢?

在Java中有一个类专门去管这件事情:UUID,这个类就表示 通用唯一标识符 的类。

简单来说:它可以生成一个随机的字符串,而且字符串的内容是唯一的,因此我可以使用这个类去生成一个随机的文件名。

想获取到这个类的对象,可以调用静态方法 randomUUID(),方法会给你返回一个UUID的对象。

image-20240508172014873

UUIDTest.java

public static void main(String[] args) {
    //UUID.randomUUID()对象中,里面的内容其实就是随机,而且是唯一的。
    System.out.println(UUID.randomUUID()); // 8c31d904-934e-4014-b9d1-61aeaa28e552
    //但是会有一个小问题,文件的名字我不想要这个 '-',先将获取到的UUID先变成字符串,再去调用字符串的replace(),将-替换成长度为0的字符串
    String str = UUID.randomUUID().toString().replace("-", "");
    System.out.println(str);//9f15b8c356c54f55bfcb0ee3023fce8a,这个结果跟我们在网站上下载的图片/视频是一样的
}

修改服务端代码

String name = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\" + name + ".jpg"));

综合练习5:上传任务(多线程版的服务端)

客户端:将本地文件上传到服务器。接收服务器的反馈。
服务器:接收客户端上传的文件,上传完毕之后给出反馈。
这一题还是解决上传文件的问题,此时需要用多线程进行改写,实现:让多个用户同时进行传递。

很多同学会想到循环,但是仅仅用循环是不合理的。

最优的写法是 循环 + 多线程 的形式进行改写。

单单用循环改写,如果第一个用户上传的文件比较大,代码还在运行,这个时候第二个用户来连接了,此时服务端就不能和第二个用户连接了。

image-20240508180246917

这是我们用循环去改进的代码,其实这也是单线程程序的弊端,单线程程序只能一个一个的来,第一个用户传完了,第二个用户才能上传。

但是这不是我们想要的,我们想要的是服务器能同时被多个用户上传,此时只能用多线程的形式。

MyRunnable.java

public class MyRunnable implements Runnable {

    Socket socket;

    public MyRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //3.读取数据并保存到本地文件中
            BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
            String name = UUID.randomUUID().toString().replace("-", "");
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\" + name + ".jpg"));
            int len;
            byte[] bytes = new byte[1024];
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }
            bos.close();
            //4.回写数据
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //5.释放资源
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Client.java

public static void main(String[] args) throws IOException {
        //客户端:将本地文件上传到服务器。接收服务器的反馈。
        //服务器:接收客户端上传的文件,上传完毕之后给出反馈。


        //1. 创建Socket对象,并连接服务器
        Socket socket = new Socket("127.0.0.1", 10000);

        //2.读取本地文件中的数据,并写到服务器当中
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[1024];
        int len;
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
        }

        //往服务器写出结束标记
        socket.shutdownOutput();


        //3.接收服务器的回写数据
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = br.readLine();
        System.out.println(line);


        //4.释放资源
        socket.close();

    }

Server.java

public class Server {
    public static void main(String[] args) throws IOException {
        //1.创建对象并绑定端口
        ServerSocket ss = new ServerSocket(10000);

        while (true) {
            //2.等待客户端来连接
            Socket socket = ss.accept();

            //开启一条线程
            //一个用户就对应服务端的一条线程
            new Thread(new MyRunnable(socket)).start();
        }
    }
}

综合练习6:删除文件(线程池优化)

客户端:将本地文件上传到服务器。接收服务器的反馈。
服务器:接收客户端上传的文件,上传完毕之后给出反馈。
频繁的创建线程并销毁线程非常的浪费系统资源,那能不能用线程池优化呢?

MyRunnable.java

public class MyRunnable implements Runnable {

    Socket socket;

    //需要将客户端的连接对象socket传递给线程,这里可以通过构造方法进行传递。
    public MyRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //3.读取数据并保存到本地文件中
            BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
            String name = UUID.randomUUID().toString().replace("-", "");
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\" + name + ".jpg"));
            int len;
            byte[] bytes = new byte[1024];
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }
            bos.close();
            //4.回写数据
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //5.释放资源,别忘了做非空判断
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Client.java

public class Client {
    public static void main(String[] args) throws IOException {
        //客户端:将本地文件上传到服务器。接收服务器的反馈。
        //服务器:接收客户端上传的文件,上传完毕之后给出反馈。


        //1. 创建Socket对象,并连接服务器
        Socket socket = new Socket("127.0.0.1", 10000);

        //2.读取本地文件中的数据,并写到服务器当中
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[1024];
        int len;
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
        }

        //往服务器写出结束标记
        socket.shutdownOutput();

        //3.接收服务器的回写数据
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = br.readLine();
        System.out.println(line);

        //4.释放资源
        socket.close();
    }
}

Server.java

public class Server {
    public static void main(String[] args) throws IOException {
        //客户端:将本地文件上传到服务器。接收服务器的反馈。
        //服务器:接收客户端上传的文件,上传完毕之后给出反馈。


        //创建线程池对象
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                16,//线程池总大小
                60,//空闲时间
                TimeUnit.SECONDS,//空闲时间(单位)
                new ArrayBlockingQueue<>(2),//队列
                Executors.defaultThreadFactory(),//线程工厂,让线程池如何创建线程对象
                new ThreadPoolExecutor.AbortPolicy()//阻塞队列
        );


        //1.创建对象并绑定端口
        ServerSocket ss = new ServerSocket(10000);

        while (true) {
            //2.等待客户端来连接
            Socket socket = ss.accept();

            //开启一条线程
            //一个用户就对应服务端的一条线程
            //new Thread(new MyRunnable(socket)).start();
            pool.submit(new MyRunnable(socket));
        }
    }
}

综合练习7:BS(接受浏览器的消息并打印)

客户端:不需要我们自己写,客户端直接用浏览器
服务器:接收浏览器请求的数据并打印

服务端代码我们都不需要重新写,直接运行之前的代码。

然后打开一个浏览器,访问 127.0.0.1:10010

看右边,就是浏览器给服务器发过来的所有的数据。

image-20240508193255115

将这个练习的目的就是为了大家在大脑中有一个BS的概念,在BS架构中,客户端其实就是浏览器,而服务端就是接收浏览器传输过来的数据,当我们要回显的时候,也是把数据回显给浏览器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值