案例: 客户端给服务器端上传一个 文本文件, 多线程版服务器端
客户端思路:
1. 创建客户端Socket对象.
2. 获取可以往服务器端写数据的流.
3. 将第2步获取到的 字节流 -> 字符流 -> 字符缓冲流.
4. 创建 高效字符输入流对象, 用来读取 文件文件中的数据.
5. 具体读写的操作.
读: 读的是文件中的数据.
写: 写给服务器端.
核心操作: 必须给服务器端一个结束标记.
6. 获取可以读取服务器端回执信息的流.
7. 将其封装成字符流 -> 字符缓冲流.
8. 具体的具体回执信息的操作.
9. 释放资源.
*/
public class Client {
public static void main(String[] args) throws Exception {
//1. 创建客户端Socket对象.
Socket socket = new Socket("192.168.18.61", 10010);
//2. 获取可以往服务器端写数据的流.
//分解版.
/*OutputStream os = socket.getOutputStream();
//3. 将第2步获取到的 字节流 -> 字符流 -> 字符缓冲流.
OutputStreamWriter osw = new OutputStreamWriter(os);
BufferedWriter bw = new BufferedWriter(osw);*/
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//4. 创建 高效字符输入流对象, 用来读取 文件文件中的数据.
BufferedReader br = new BufferedReader(new FileReader("day14/data/1.txt"));
//5. 具体读写的操作.
String line;
//读: 读的是文件中的数据.
while ((line = br.readLine()) != null) {
//写: 写给服务器端.
bw.write(line);
bw.newLine();
bw.flush(); //记得要刷新, 因为bw对象是根据Socket对象创建的, 稍后没有bw.close()代码.
}
br.close();
//这里有问题, 核心操作: 必须给服务器端一个结束标记.
socket.shutdownOutput();
//6. 获取可以读取服务器端回执信息的流.
//7. 将其封装成字符流 -> 字符缓冲流.
BufferedReader br2 = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//8. 具体的具体回执信息的操作.
String resultMessage = br2.readLine();
System.out.println(resultMessage);
//9. 释放资源.
socket.close();
案例: 客户端给服务器端上传一个 文本文件, 多线程版服务器端.
服务器思路:
1. 创建服务器端Socket对象.
2. 监听连接.
3. 获取可以读取客户端写过来的数据的 流.
4. 将第3步获取到的 字节流 -> 字符流 -> 字符高效流.
5. 创建 字符高效输出流对象, 用来往 目的地文件中写数据.
6. 具体读写的操作
读: 读的是客户端写过来的数据.
写: 写到目的地文件中.
7. 获取可以往客户端写回执信息的流.
8. 将其封装成 字符流 -> 高效字符流.
9. 给客户端写回执信息, 记得要刷新.
10. 释放资源.
服务器端多线程版本改造计划:
思路: 客户端代码不动, 服务器端升级为 多线程版.
1. 自定义资源子类MyRunnable, 实现Runnable接口.
2. 定义一个变量, 用来表示 Socket对象, 该对象是负责和 客户端Socket交互的对象.
3. 在构造方法中, 给上述的 成员变量Socket赋值.
4. 重写run()方法, 把具体上传文件操作的内容, 都Copy过来.
细节1: 注意, 因为是Runnable#run()方法, 所以有异常只能try, 不能抛.
细节2: 注意文件名重名问题.
5. 改造服务器端代码, 把一次监听改为循环监听.
6. 只要有客户端申请建立连接, 在服务器端审核数据合法后,
就创建一个 线程对象, 负责和此客户端的交互.
*/
public class Server {
public static void main(String[] args) throws Exception{
//1. 创建服务器端Socket对象.
ServerSocket server = new ServerSocket(10010);
//2. 监听连接.
while(true) {
//5. 改造服务器端代码, 把一次监听改为循环监听.
Socket accept = server.accept();
// 6. 只要有客户端申请建立连接, 在服务器端审核数据合法后,
// 就创建一个 线程对象, 负责和此客户端的交互.
//格式: new Thread(Runnable接口的子类对象).start();
new Thread(new MyRunnable(accept)).start();
}
//1. 自定义资源子类MyRunnable, 实现Runnable接口.
public class MyRunnable implements Runnable{
//2. 定义一个变量, 用来表示 Socket对象, 该对象是负责和 客户端Socket交互的对象.
private Socket accept;
//3. 在构造方法中, 给上述的 成员变量Socket赋值.
public MyRunnable(Socket accept) { //accept: 该对象是负责和 客户端Socket交互的对象.
this.accept = accept;
}
//4. 重写run()方法, 把具体上传文件操作的内容, 都Copy过来.
@Override
public void run() {
//细节1: 注意, 因为是Runnable#run()方法, 所以有异常只能try, 不能抛.
//细节2: 注意文件名重名问题.
int count = 1;
File file = new File("day14/data/copy(1).txt");
while (file.exists()) { //判断文件是否存在.
//如果存在, 就修改文件名
file = new File("day14/data/copy("+ ++count +").txt");
}
try {
//3. 获取可以读取客户端写过来的数据的 流.
//4. 将第3步获取到的 字节流 -> 字符流 -> 字符高效流.
BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));
//5. 创建 字符高效输出流对象, 用来往 目的地文件中写数据.
BufferedWriter bw = new BufferedWriter(new FileWriter(file));
//6. 具体读写的操作
String line;
//读: 读的是客户端写过来的数据.
while ((line = br.readLine()) != null) {
//写: 写到目的地文件中.
bw.write(line);
bw.newLine();
bw.flush(); //记得要刷新, 因为bw对象是根据Socket对象创建的, 稍后没有bw.close()代码.
}
bw.close(); //可以先关闭bw对象.
//7. 获取可以往客户端写回执信息的流.
//OutputStream os = accept.getOutputStream();
//8. 将其封装成 字符流 -> 高效字符流.
//BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(os));
BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
//9. 给客户端写回执信息, 记得要刷新.
bw2.write("文件上传成功!");
bw2.newLine();
bw2.flush();
//10. 释放资源.
//br.close();
accept.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
分类:
UDP协议: 类似于: 群聊.
1. 面向无连接.
2. 采用 数据报包(DatagramPacket)的形式发送数据, 每个包的大小理论上不能超过 64KB.
3. 不安全(不可靠)协议.
4. 效率相对比较高.
5. 不区分客户端和服务器端, 叫: 发送端和接收端.
TCP协议: 类似于: 打电话
1. 面向有连接(三次握手)
2. 采用IO流的形式发送数据, 理论上无大小限制.
3. 安全(可靠)协议.
4. 效率相对比较低.
5. 区分客户端和服务器端.