4.7 TCP-日志-枚举

1、TCP 协议发数据

  • Java中的 TCP 通信
    • Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
    • Java为客户端提供了 Socket 类,为服务器端提供了 ServerSocket 类

构造方法

方法名

说明

Socket(InetAddress address,int port)

创建流套接字并将其连接到指定IP指定端口号

Socket(String host, int port)

创建流套接字并将其连接到指定主机上的指定端口号

  • 相关方法

方法名

说明

InputStream getInputStream()

返回此套接字的输入流

OutputStream getOutputStream()

返回此套接字的输出流

  • 示例代码
public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //创建客户端的Socket对象(Socket)
        //Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号
        Socket s = new Socket("127.0.0.1",10000);

        //获取输出流,写数据
        //OutputStream getOutputStream() 返回此套接字的输出流
        OutputStream os = s.getOutputStream();
        os.write("hello,tcp,我来了".getBytes());

        //释放资源
        os.close();
        s.close();
    }
}

2、TCP协议收数据

  • 构造方法

方法名

说明

ServletSocket(int port)

创建绑定到指定端口的服务器套接字

  • 相关方法

方法名

说明

Socket accept()

监听要连接到此的套接字并接受它

  • 示例代码
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //创建服务器端的Socket对象(ServerSocket)
        //ServerSocket(int port) 创建绑定到指定端口的服务器套接字
        ServerSocket ss = new ServerSocket(10000);

        //Socket accept() 侦听要连接到此套接字并接受它
        Socket s = ss.accept();

        //获取输入流,读数据,并把数据显示在控制台
        InputStream is = s.getInputStream();
        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String data = new String(bys,0,len);
        System.out.println("数据是:" + data);

        //释放资源
        s.close();
        ss.close();
    }
}
  • 注意事项
    1. accept方法是阻塞的,作用就是等待客户端连接
    2. 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
    3. 针对客户端来讲,是往外写的,所以是输出流针对服务器来讲,是往里读的,所以是输入流
    4. read方法也是阻塞的
    5. 客户端在关流的时候,还多了一个往服务器写结束标记的动作
    6. 最后一步断开连接,通过四次挥手协议保证连接终止

三次握手和四次挥手

  • UDP : 面向无连接, 数据不安全, 速度快
    • 有传输大小限制 , 一次最多只能发送64k
  • TCP : 面向连接, 数据安全, 速度相对来说较慢
    • 没有传输大小限制
  • 三次握手

image

  • 四次挥手

image

3、TCP案例:文件上传 – 待优化

  • 案例需求客户端:数据来自于本地文件,接收服务器反馈服务器:接收到的数据写入本地文件,给出反馈
  • 案例分析
    • 创建客户端对象,创建输入流对象指向文件,每读一次数据就给服务器输出一次数据,输出结束后使用shutdownOutput()方法告知服务端传输结束
    • 创建服务器对象,创建输出流对象指向文件,每接受一次数据就使用输出流输出到文件中,传输结束后。使用输出流给客户端反馈信息
    • 客户端接受服务端的回馈信息
  • 相关方法

方法名

说明

void shutdownInput()

将此套接字的输入流放置在“流的末尾”

void shutdownOutput()

禁止用此套接字的输出流

  • 代码实现
package com.itheima.tcp;

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

public class Client {
    public static void main(String[] args) throws IOException {

        // 1. 本地输入流, 关联文件
        FileInputStream fis = new FileInputStream("D:\\win.png");

        // 2. 创建socket连接服务端
        Socket socket = new Socket("127.0.0.1", 10010);

        // 3. 获取网络输出流对象
        OutputStream os = socket.getOutputStream();

        // 4. 读写
        byte[] bys = new byte[8192];
        int len;
        while ((len = fis.read(bys)) != -1) {
            os.write(bys, 0, len);
        }

        fis.close();

        // 5. 给服务端一个结束标记
        socket.shutdownOutput();

        // 6. 关流
        socket.close();

    }
}

4、TCP案例:文件上传 – 优化

  • 优化方案一
    • 需求服务器只能处理一个客户端请求,接收完一个图片之后,服务器就关闭了。
    • 解决方案使用循环
    • 服务代码改写
public class Server {
    public static void main(String[] args) throws IOException {
        // 1. 创建服务端对象, 指定端口号
        ServerSocket server = new ServerSocket(10010);

        while (true) {
            // 2. 响应客户端发送的请求, 获取socket对象
            Socket socket = server.accept();
            // 3. 创建本地输出流, 关流文件

            FileOutputStream fos = new FileOutputStream("E:\\result.png");
            // 4. 获取网络输入流对象
            InputStream is = socket.getInputStream();
            // 5. 读写
            byte[] bys = new byte[8192];
            int len;
            while ((len = is.read(bys)) != -1) {
                fos.write(bys, 0, len);
            }
            fos.close();
        }

    }
}
  • 优化方案二
    • 需求第二次上传文件的时候,会把第一次的文件给覆盖。
    • 解决方案UUID. randomUUID()方法生成随机的文件名
    • 代码实现
public class Server {
    public static void main(String[] args) throws IOException {
        // 1. 创建服务端对象, 指定端口号
        ServerSocket server = new ServerSocket(10010);

        while (true) {
            // 2. 响应客户端发送的请求, 获取socket对象
            Socket socket = server.accept();
            // 3. 创建本地输出流, 关流文件

            FileOutputStream fos = new FileOutputStream("E:\\result" + UUID.randomUUID().toString() + ".png");
            // 4. 获取网络输入流对象
            InputStream is = socket.getInputStream();
            // 5. 读写
            byte[] bys = new byte[8192];
            int len;
            while ((len = is.read(bys)) != -1) {
                fos.write(bys, 0, len);
            }
            fos.close();
        }

    }
}
  • 优化方案三
    • 需求使用循环虽然可以让服务器处理多个客户端请求。但是还是无法同时跟多个客户端进行通信。
    • 解决方案开启多线程处理
    • 代码实现
package com.itheima.tcp;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Server {
    public static void main(String[] args) throws IOException {
        // 1. 创建服务端对象, 指定端口号
        ServerSocket server = new ServerSocket(10010);

        // 优化代码: 线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
            2,
            5,
            60,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(10),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy()
        );

        while (true) {
            // 2. 响应客户端发送的请求, 获取socket对象
            Socket socket = server.accept();
            // 3. 提交线程任务
            pool.submit(new SubmitFileTask(socket));
        }

    }
}


class SubmitFileTask implements Runnable {

    private Socket socket;

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

    @Override
    public void run() {
        try {
            FileOutputStream fos = new FileOutputStream("E:\\result" + UUID.randomUUID().toString() + ".png");
            // 4. 获取网络输入流对象
            InputStream is = socket.getInputStream();
            // 5. 读写
            byte[] bys = new byte[8192];
            int len;
            while ((len = is.read(bys)) != -1) {
                fos.write(bys, 0, len);
            }
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5、日志框架

  • 介绍 : 程序中的日志可以用来记录程序运行过程中的信息,并可以进行永久存储。
    • 生活中的日志: 生活中的日志就好比日记,可以记录你生活的点点滴滴。

引入 :

  • 目前输出语句的弊端 :
    • 信息只能展示在控制台
    • 不能将其记录到其他的位置(文件,数据库)
    • 想取消记录的信息需要修改代码才可以完成12

image

日志体系结构

image

JCL : Jakarta Commons Logging

Jakarta : Apache基金旗下的开源Java项目社区

Logback 快速入门

模块名

介绍

logback-core:

该模块为其他两个模块提供基础代码,必须有。

logback-classic:

完整实现了slf4j API的模块。

logback-access

logback-access 模块与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能

第一步 : 引入 jar 包

jar 包 : 本质来说是压缩包, 内部存储的都是别人已经写好的代码

image

image

image

第二步 : 导入配置文件

image

第三步 : 获取日志对象使用

image

日志级别和配置文件详解

image

通过logback.xml 中的<appender>标签可以设置输出位置和日志信息的详细格式。

通常可以设置2个日志输出位置:一个是控制台、一个是系统文件中

image

6、枚举

image

  • 格式
public enum 枚举名 {   
    枚举项1,枚举项2,枚举项3;
}
注意: 定义枚举类要用关键字 enum
  • 示例代码
// 定义一个枚举类,用来表示春,夏,秋,冬这四个固定值
public enum Season {
    SPRING,SUMMER,AUTUMN,WINTER;
}

枚举的特点【理解】

  • 特点
    • 所有枚举类都是 Enum 的子类
    • 我们可以通过”枚举类名.枚举项名称”去访问指定的枚举项
    • 每一个枚举项其实就是该枚举的一个对象
    • 枚举也是类, 可以定义成员变量
    • 枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略
    • 枚举类可以有构造器,但必须是 private 的,它默认的也是 private 的。
      • 枚举项的用法比较特殊:枚举(“”);
    • 枚举类也可以有抽象方法,但是枚举项必须重写该方法

示例代码 :

public enum Season {

    SPRING("春"){

        //如果枚举类中有抽象方法
        //那么在枚举项中必须要全部重写
        @Override
        public void show() {
            System.out.println(this.name);
        }

    },

    SUMMER("夏"){
        @Override
        public void show() {
            System.out.println(this.name);
        }
    },

    AUTUMN("秋"){
        @Override
        public void show() {
            System.out.println(this.name);
        }
    },

    WINTER("冬"){
        @Override
        public void show() {
            System.out.println(this.name);
        }
    };

    public String name;

    //空参构造
    //private Season(){}

    //有参构造
    private Season(String name){
        this.name = name;
    }

    //抽象方法
    public abstract void show();
}

public class EnumDemo {
    public static void main(String[] args) {
        //我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项
        System.out.println(Season.SPRING);
        System.out.println(Season.SUMMER);
        System.out.println(Season.AUTUMN);
        System.out.println(Season.WINTER);

        //每一个枚举项其实就是该枚举的一个对象
        Season spring = Season.SPRING;
    }
}

image

  • 枚举 : 可以理解是一种多例设计模式
单例设计模式: 保证类的对象, 在内存中只有一份
枚举 : 保证类的对象, 在内存中, 只有固定的几个
© 版权声明
THE END
喜欢就支持一下吧
点赞582赞赏 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容