Java进阶14 TCP&日志&枚举
一、网络编程TCP
Java对基于TCP协议得网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
1、TCP协议发数据
1.1 构造方法
| 方法 | 说明 |
|---|---|
| Socket(InetAddress address,int port) | 创建流套接字并将其连接到指定IP指定端口号 |
| Socket(String host,int port) | 创建流套接字并将其连接到指定主机上的指定端口号 |
1.2 相关方法
| 方法 | 说明 |
|---|---|
| InputStream getInputStream() | 返回此套接字的输入流 |
| OutputStream getOutputStream() | 返回此套接字的输出流 |
1.3 代码实现
public class Client {
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协议收数据
Java为客户端提供了Socket类,为服务端提供了ServeSocket类
2.1 构造方法
| 方法 | 说明 |
|---|---|
| ServerSocket(int port) | 创建绑定到指定端口的服务器套接字 |
2.2 相关方法
| 方法 | 说明 |
|---|---|
| Socket accept() | 监听要连接到此的套接字并接受它 |
2.3 代码实现
public class Server {
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();
}
}
注意事项:
-
accept方法是阻塞的,作用就是等待客户端连接
-
客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
-
针对客户端来讲,是往外写的,所以是输出流 针对服务器来讲,是往里读的,所以是输入流
-
read方法也是阻塞的
-
客户端在关流的时候,还多了一个往服务器写结束标记的动作
-
最后一步断开连接,通过四次挥手协议保证连接终止
3、三次握手和四次挥手
3.1 三次握手

3.2 四次挥手

4、TCP文件上传案例
-
案例需求
客户端:数据来自于本地文件,接收服务器反馈
服务器:接收到的数据写入本地文件,给出反馈
-
分析思路
-
创建客户端对象,创建输入流对象指向文件,每读一次数据就给服务器输出一次数据,输出结束后使用shutdownOutput()方法告知服务端传输结束
-
创建服务器对象,创建输出流对象指向文件,每接受一次数据就使用输出流输出到文件中,传输结束后,使用输出流给客户端反馈信息
-
客户端接收服务端的回馈信息
-
-
相关方法
方法 说明 void shutdownInput() 将此套接字的输入流放置在“流的末尾” void shutdownOutput() 禁止用此套接字的输出流 -
服务端优化思路
-
服务端可以多次处理
-
循环包裹while(true)
-
使用UUID区分了重名文件,防止二次上传文件重名造成覆盖
-
-
将服务器改进为多线程版本,满足多个客户端同时通信
-
改为线程池
-
-
代码实现
public class Client { public static void main(String[] args) throws IOException { //1、创建Socket对象指定ip和端口 Socket socket = new Socket("192.168.11.251",8888); //2、获取传输数据的流对象(网络流) InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); //3、将字节流转换为字符串(读写的内容有中文) BufferedReader br = new BufferedReader(new InputStreamReader(is)); PrintStream ps = new PrintStream(os); File file = new File("D:\\好哥哥.jpg"); //4、写出文件名 ps.println(file.getName()); //5、读取服务端消息 String state = br.readLine(); if("ok".equals(state)){ //6、上传文件(字节) //创建本地字节输入流关联要上传的文件 FileInputStream fis = new FileInputStream(file); byte[] bys = new byte[1024]; int len; while((len=fis.read(bys))!=-1){ os.write(bys,0,len); } //重点:写出结束标记给服务端 socket.shutdownOutput(); fis.close(); //7、读取上传结果 String result = br.readLine(); System.out.println(result); //8、关流 socket.close(); } } }public class Server { public static void main(String[] args) throws IOException { //1、创建ServerSocket对象指定端口 ServerSocket server = new ServerSocket(8888); System.out.println("服务端开启,等待客户端连接"); ThreadPoolExecutor pool = new ThreadPoolExecutor( 5, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(20), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); while (true) { //2、调用accept方法响应客户端的请求 Socket socket = server.accept(); //new Thread(new SubmitFileTask(socket)).start(); pool.submit(new FileSubmitTask(socket)); } } }public class FileSubmitTask implements Runnable{ //为了拿到另一个类的数据,将其作为参数传进本类,在本类创建带参构造并接收该参数赋值给本类的成员变量,就拿到了 private Socket socket; public FileSubmitTask(Socket socket) { this.socket=socket; } @Override public void run() { try { //3、获取传输的输入流对象(网络流) InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); //4、将字节流转换为字符串(读写的内容有中文) BufferedReader br = new BufferedReader(new InputStreamReader(is)); PrintStream ps = new PrintStream(os); //5、读取客户端发送的文件名 String fileName = br.readLine(); //关联服务端的存储路径 File update = new File("D:\\itheima\\Resource", UUID.randomUUID()+fileName); //6、写给客户端消息 ps.println("ok"); //7、创建本地字节输出流,关联存储路径 FileOutputStream fos = new FileOutputStream(update); //8、IO读写 byte[] bys = new byte[1024]; int len; while((len = is.read(bys))!=-1){ fos.write(bys,0,len); } fos.close(); //9、写出上传成功 ps.println("上传成功!"); //10、关流 socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
二、日志
程序中的日志可以用来记录程序运行过程中的信息,并可以进行永久存储。日志和输出语句的比较
| 输出语句 | 日志技术 | |
|---|---|---|
| 输出位置 | 只能是控制台 | 可以将日志信息写入到文件或者数据库中 |
| 取消日志 | 需要修改代码,灵活性比较差 | 不需要修改代码,灵活性比较好 |
| 多线程 | 性能较差 | 性能较好 |
1、日志体系结构

-
日志规范:一些接口,提供给日志的实现框架设计的标准
-
日志框架:牛人或者第三方公司已经做好的日志记录实现代码,后来直接可以拿去使用
-
因为对Commons Logging 的接口不满意,有人就搞了SLF4J;因为对Log4j的性能不满意,有人就搞了Logback
2、Logback快速入门
-
官网: Logback Home
-
三个技术模块
模块名 介绍 locback-core 该模块为其他两个模块提供基础代码,必须有。 logback-classic 完整实现了slf4j API的模块。 logback-access logback-access 模块与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能
2.1 操纵步骤
第一步:引入jar包
jar包本质来说就是压缩包,内部存储的都是别人已经写好的代码。在需要导包的项目模块下,和src平级的位置,创建lib目录,将需要的jar包都放进去,然后勾选Add as Library。导jar包只是目前的做法,后面会学maven来管理jar包,不用手动导入
第二步:导入配置文件
将日志的配置信息文件(logback.xml)复制到模块的src
-
日志级别和配置文件详解
通过通过logback.xml 中的<appender>标签可以设置输出位置和日志信息的详细格式。通常可以设置2个日志输出位置:一个是控制台、一个是系统文件中



第三步:获取日志对象使用
public class LogTest {
public static void main(String[] args) {
//获取记录日志对象
Logger logger = LoggerFactory.getLogger("LogTest.class");
//记录日志
logger.info("记录了一条日志");
}
}
三、枚举
枚举是Java中的一种特殊类型,其作用是为了做信息的标志和信息的分类。枚举可以理解为一种多例设计模式,保证类的对象,在内存中只有固定的几个
1、定义格式
修饰符 enum 枚举类型{ //第一行都是罗列枚举类实例的名称 枚举项1,枚举项2,枚举项3; }enum Season{ SPRING,SUMMER,AUTUMN,WINTER; }
2、枚举的特点
-
所有枚举类都是 Enum 的子类
-
我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项
-
每一个枚举项其实就是该枚举的一个对象
-
枚举也是类, 可以定义成员变量
-
枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不要省略
-
枚举类可以有构造器,但必须是 private 的,它默认的也是 private 的。枚举项的用法比较特殊:枚举("");
-
枚举类也可以有抽象方法,但是枚举项必须重写该方法
3、示例代码
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;
}
}
4、做信息标志方式(2种)
4.1 常量
虽然可以实现可读性,但是入参不受约束,代码相对不够严谨
4.2 枚举
代码可读性好,入参约束严谨,代码优雅,是最好的信息分类技术!建议使用!