1、TCP 协议发数据
- Java中的 TCP 通信
-
- Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
- Java为客户端提供了 Socket 类,为服务器端提供了 ServerSocket 类
构造方法
方法名 |
说明 |
|
创建流套接字并将其连接到指定IP指定端口号 |
|
创建流套接字并将其连接到指定主机上的指定端口号 |
- 相关方法
方法名 |
说明 |
|
返回此套接字的输入流 |
|
返回此套接字的输出流 |
- 示例代码
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协议收数据
- 构造方法
方法名 |
说明 |
|
创建绑定到指定端口的服务器套接字 |
- 相关方法
方法名 |
说明 |
|
监听要连接到此的套接字并接受它 |
- 示例代码
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();
}
}
- 注意事项
-
- accept方法是阻塞的,作用就是等待客户端连接
- 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
- 针对客户端来讲,是往外写的,所以是输出流针对服务器来讲,是往里读的,所以是输入流
- read方法也是阻塞的
- 客户端在关流的时候,还多了一个往服务器写结束标记的动作
- 最后一步断开连接,通过四次挥手协议保证连接终止
三次握手和四次挥手
- UDP : 面向无连接, 数据不安全, 速度快
-
- 有传输大小限制 , 一次最多只能发送64k
- TCP : 面向连接, 数据安全, 速度相对来说较慢
-
- 没有传输大小限制
- 三次握手
- 四次挥手
3、TCP案例:文件上传 – 待优化
- 案例需求客户端:数据来自于本地文件,接收服务器反馈服务器:接收到的数据写入本地文件,给出反馈
- 案例分析
-
- 创建客户端对象,创建输入流对象指向文件,每读一次数据就给服务器输出一次数据,输出结束后使用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
日志体系结构
JCL : Jakarta Commons Logging
Jakarta : Apache基金旗下的开源Java项目社区
Logback 快速入门
- 官网 : https://logback.qos.ch/index.html
- 三个技术模块
模块名 |
介绍 |
logback-core: |
该模块为其他两个模块提供基础代码,必须有。 |
logback-classic: |
完整实现了slf4j API的模块。 |
logback-access |
logback-access 模块与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能 |
第一步 : 引入 jar 包
jar 包 : 本质来说是压缩包, 内部存储的都是别人已经写好的代码
第二步 : 导入配置文件
第三步 : 获取日志对象使用
日志级别和配置文件详解
通过logback.xml 中的<appender>标签可以设置输出位置和日志信息的详细格式。
通常可以设置2个日志输出位置:一个是控制台、一个是系统文件中
6、枚举
- 格式
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;
}
}
- 枚举 : 可以理解是一种多例设计模式
单例设计模式: 保证类的对象, 在内存中只有一份
枚举 : 保证类的对象, 在内存中, 只有固定的几个
暂无评论内容