Java基础加强重温_11:软件结构、网络编程三要素、UDP协议、TCP协议、综合案例(上传和下载文件)、单用户/多用户上传下载(多线程应用)

摘要

Java基础加强重温_11:
软件结构(C/S结构:客户端/服务器结构、B/S结构:浏览器/服务器结构)、
网络编程三要素(IP地址:设备标识、端口号:进程标识、通信协议:UDP/TCP)、
UDP协议(用户数据报协议、DatagramPacket类:数据包对象、DatagramSocket类:数据发送对象)
InetAddress类(IP地址)、
TCP协议(传输控制协议、三次握手四次挥手、Socket类:客户端、ServerSocket类:服务端)
综合案例(本机上传和下载文件、文件命名:当前时间毫秒数、服务端多线程接收客户端数据)、
其他机单用户/多用户上传下载(文件命名:随机数+当前时间毫秒数、服务端多线程应用)

一、网络编程

1、软件结构

常见的软件结构有:CS结构、BS结构

CS结构

CS结构:客户端服务器结构。全称为Client/Server结构,是指客户端和服务器器结构。常见程序有QQ、迅雷雷等软件。
缺点:要开发多个端,开发成本高,维护成本高,开发周期长
优点:可以将一些运算交给客户端完成,减轻服务器压力,用户体验好
在这里插入图片描述

BS结构

BS结构:浏览器服务器结构。全称为Browser/Server结构,是指浏览器器和服务器结构。常⻅见浏览器器有⾕谷歌、火狐等。
缺点:所有运算都需要在服务器端完成,压力大
优点:只需要开发服务器端,开发成本低,维护成本低,开发周期短
在这里插入图片描述

小结

两种架构各有优势,但是无论哪种架构,都离不开网络的支持。
网络编程,就是在一定的协议下,实现两台计算机的通信的程序。

2、网络编程三要素

三要素:IP地址、端口号、通信协议

IP地址

IP地址:指互联网协议地址(Internet Protocol Address) ,俗称IP,IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把"个人电脑"比作"一台电话"的话,那么"IP地址"就相当于“电话号码"。

理解:
IP地址:网络设备的唯一标识

IP分类

1、IPV4:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d的形式,例如192.168.65.100。其中a, b、c.d都是0-255之间的十进制整数,那么最多可以表示大约43亿个

2、IPV6:由于互联网的蓬勃发展, IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。有资料显示,全球IPV4地址在2011年2月分配完毕。
为了扩大地址空间,拟通过IPV6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD: EFQ1:2345: 6789: ABCD :EFA1 :2345:6789 ,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。

特殊的IP地址

本机IP地址: 127.0.0.1 、 localhost 。

DOS窗口查看IP常用命令

查看本机IP地址,在控制台输入:

ipconfig 

检查⽹网络是否连通,在控制台输入:

ping 空格 IP地址
ping 220.181.57.216

端口号

网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,在网络通信时,就是用端口号区分这些进程。
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。

  • 端口号:用两个字节表示的整数,它的取值范围是0-65535。
    其中, 0-1023之间的端口号用于一些知名的网络服务和应用。
    普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。

理解:
端口号:进程的唯一标识

通信协议(UDP、TCP)

网络通信协议:通信协议是对计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信。这就好比在道路中行驶的汽车一定要遵守交通规则一样,协议中对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守,最终完成数据交换。
java. net包中提供了两种常见的网络协议的支持: UDP和TCP

理解:
通信协议:确定如何传输数据

小结

网络三要素使用:通过IP找主机,通过端口号找程序,通过协议确定如何传输数据。

3、UDP(用户数据报协议)、TCP(传输控制协议)

TCP协议(传输控制协议)

TCP:传输控制协议(Transmission Control Protocol), TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。

三次握手(两次确认,一次回应)

TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。

第一次握手:客户端向服务器端发出连接请求,等待服务器确认。
第二次握手:服务器端向客户端回送一个响应,通知客户端收到了连接请求。
第三次握手:客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。
在这里插入图片描述
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。

理解:
客户端两次确认、服务器一次回应

四次挥手(别名连接终止协议)

所谓的四次挥手即TCP连接的释放(解除)。连接的释放必须是一方主动释放,另一方被动释放。一般由客户端端发起,服务端也可以发起。

第一次,A端像B端发送FIN结束报文段,准备关闭连接
第二次,B端确认A端的FIN,表示自己已经收到对方关闭连接的请求

中间这段时间,A端停止向B端发送数据,但是B端可以向A端发送数据,要将自己未处理完任务处理完

第三次,B端向A端发送FIN结束报文段,进入进入LAST_ACK状态。准备关闭连接
第四次,A端确认B端的FIN,进入TIME_WAIT状态,接着发送一个ACK给B端,此时A端进程已经退出,但是连接还在。
当B端收到A端的ACK之后,先断开连接。
当A端等待2 MSL之后,确认的B端接收到ACK后,再断开连接

发起断开连接请求的一端最后要进入有一个TIME_WAIT状态
发起连接请求的可以是客户端也可以是服务器端

什么是三次握手、四次挥手
https://www.cnblogs.com/ranyonsue/p/10309292.html
TCP面试题之四次挥手过程
https://www.cnblogs.com/hujinshui/p/10473364.html

面试题:三次握手,四次挥手

1、TCP的三次握手与四次挥手理解及面试题(很全面)
https://blog.csdn.net/qq_38950316/article/details/81087809
2、详解 TCP 连接的“ 三次握手 ”与“ 四次挥手 ”
https://baijiahao.baidu.com/s?id=1654225744653405133&wfr=spider&for=pc
3、程序员面试被问到“三次握手,四次挥手”怎么办?
https://blog.csdn.net/csdnnews/article/details/86570658

UDP协议(用户数据报协议)

UDP:用户数据报协议(User Datagram Protocol), UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等。

UDP ==> User DatagramPacket Protocol 用户数据报协议
面向无连接的协议
只管发送,不确定对方是否能收到
基于数据包传输:将数据,接收端ip,接收端端口号,发送端ip封装到数据包中
数据包大小限制在64k以内
因为面向无连接,速度快但不可靠

二、UDP通信程序

UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。UDP通信过程如下图所示:
在这里插入图片描述

UDP协议特点

1、UDP ==> User DatagramPacket Protocol 用户数据报协议
2、面向无连接的协议
3、只管发送,不确定对方是否能收到
4、基于数据包传输:将数据,接收端ip,接收端端口号,发送端ip封装到数据包中
5、数据包大小限制在64k以内
6、因为面向无连接,速度快但不可靠

UDP协议使用场景

即时通讯(qq,微信,内网通…)
在线视频
在线语言电话

1、UDP相关的两个类

DatagramPacket类
数据包对象:用来封装要发送的数据或封装要接收的数据,比如集装箱

DatagramSocket类
发送数据包对象或接收数据包对象:用来发送数据包和接收数据包,比如码头

DatagramPacket类(数据包对象)

概述:数据包对象
作用:用来封装发送端或接收端的数据。类似于"集装箱"。

构造方法
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
	* 创建发送端的数据包对象
	* buf:字节数组:存储要发送的数据
	* length:要发送内容的长度,单位:字节
	* address:接收端的IP地址
	* port:接收端的端口号

DatagramPacket(byte buf[], int length)
	* 创建接收端的数据包对象
	* buf:字节数组:存储接收到的数据
	* length:能够接收内容的长度,单位字节
常用方法
InetAddress getAddress() 
	获得发送端IP地址对象

int port() 
	获得发送端的端口号

DatagramSocket类(数据发送对象)

概述:数据发送对象
作用:用来发送和接收数据。类似于"码头"。
使用这个类的实例对象就可以发送和接收DatagramPacket数据包。

构造方法

Socket ==> 套接字,插座
网络编程就是Socket编程,Socket编程就是网络编程

DatagramSocket()
	创建发送端的Socket对象,相当于开启一个进程,随机分配一个端口号。

DatagramSocket(int port)
	创建接收端的Socket对象并指定端口号,相当于开启一个进程
常用方法
void send(DatagramPacket dp) 
	发送数据包

void receive(DatagramPacket dp) 
	接收数据包

void close() 
	释放资源

InetAddress类(IP地址)

InetAddress类的对象就代表一个IP地址

静态方法
static InetAddress	getLocalHost()
    获得本机IP地址对象
    
static InetAddress getByName(String host)
    根据IP地址字符串或主机名或域名获得IP地址对象

2、UDP发送端、接收端使用

发送端

UDP发送端实现步骤

  1. 创建数据包对象:用来封装要发送的数据
  2. 创建发送对象:用来发送数据包
  3. 调用发送对象的方法发送数据包
  4. 调用发送对象的close方法释放资源
代码实现
public class Demo041 {
    public static void main(String[] args) throws Exception{
        // 1.1 准备要发送的数据
        byte[] message = "约吗".getBytes();
        // 1.2 创建数据包对象:用来封装要发送的数据(集装箱)
        DatagramPacket dp = new DatagramPacket(
                message,
                message.length,
                InetAddress.getByName("127.0.0.1"),
                6666);
        // 2. 创建发送对象:用来发送数据包(码头)
        DatagramSocket ds = new DatagramSocket();
        // 3. 调用发送对象的方法发送数据包
        ds.send(dp);
        // 4. 调用发送对象的close方法释放资源
        ds.close();
    }
}

接收端

UDP接收端实现步骤

  1. 创建数据包对象:用来存储接收到的数据
  2. 创建接收对象:用来接收数据包
  3. 调用接收对象的方法receive方法接收数据包
  4. 调用接收对象的方法close释放资源
代码实现
public class Demo042 {
    public static void main(String[] args) throws Exception{
        // 1. 创建数据包对象:
        // 1.1 创建字节数组:用来存储接收到的数据
        byte[] buf = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buf, buf.length);
        // 2. 创建接收对象:用来接收数据包
        DatagramSocket ds = new DatagramSocket(6666);
        System.out.println("启动接收端程序....");

        // 3. 调用接收对象的方法receive方法接收数据包
        ds.receive(dp);
        System.out.println("接收到的内容:" + new String(buf,0,6)); // 约吗,(utf-8,1个汉字=3个字节)

        // 获得发送端IP地址:127.0.0.1 代表本机IP地址
        //getHostAddress():返回与InetAddress对象相关的主机地址字符串
        String sendIP = dp.getAddress().getHostAddress();
        System.out.println("发送端IP地址:"  + sendIP); // 56579
        // 获得发送端端口号
        System.out.println(dp.getPort()); //

        // 4. 调用接收对象的方法close释放资源
        ds.close();
    }
}

getAddress():根据IP地址获得一个IP地址对象
getHostAddress():返回与InetAddress对象相关的主机地址字符串

测试

先启动接收端,再启动发送端。
接收端执行到receive()方法会阻塞,一直等数据传输过来,接收数据后再往下执行

3、UDP通信案例

需求
本机电脑的一个程序发送数据,一个程序接收数据,使用本机的ip

UDP发送端代码实现

/**
* UDP发送端
*/
public class UDPSender {
	public static void main(String[] args) throws Exception {
		// 准备要发送的数据
		String content = "你好 UDP接收端";
		// 创建数据包对象
		DatagramPacket dp = new DatagramPacket(
			content.getBytes(),
			content.getBytes().length, 
			InetAddress.getLocalHost(), 
			6666);
		
		// 创建发送对象
		DatagramSocket ds = new DatagramSocket();
		// 发送数据包
		ds.send(dp);
		// 关闭资源
		ds.close();
	}
}

UDP接收端代码实现

/**
* UDP接收端
*/
public static void main(String[] args) throws Exception {
	// 创建接收对象:码头
	DatagramSocket ds = new DatagramSocket(6666);
	// 创建字节数组,用来存储接收到的数据
	byte[] buf = new byte[1024];
	// 创建数据包对象用来存储发送端发送过来的数据
	DatagramPacket dp = new DatagramPacket(buf, buf.length);
	// 接收数据报:同步方法
	ds.receive(dp);
	
	// 获得实际接收到的字节个数
	int len = dp.getLength();
	System.out.println("len = " + len);
	System.out.println("接收到内容:" + new String(buf,0,len));
	
	// 获得发送端的ip地址和端口号
	String ip = dp.getAddress().getHostAddress();
	int port = dp.getPort();
	System.out.println("发送端IP地址:" + ip);
	System.out.println("发送端口号:" + port);
	// 关闭资源
	ds.close();
}

三、TCP通信程序

TCP协议是面向连接的通信协议,即在传输数据前先在客户端和服务器端建立逻辑连接,然后再传输数据。它提供了两台计算机之间可靠无差错的数据传输。TCP通信过程如下图所示:
在这里插入图片描述

TCP协议特点

1、面向连接的协议
2、通过三次握手建立连接:形成数据传输通道
3、通过四次挥手断开连接
4、基于IO流传输数据
5、传输数据大小没有限制
6、因为面向连接,速度慢但可靠

TCP协议使用场景

上传和下载文件
邮件发送和接收
远程登录:用户名和密码

1、TCP相关的两个类

Socket类
代表客户端程序

ServerSocket类
代表服务器端程序,一个该类的对象就代表一个服务器程序

Socket类(客户端)

Socket类实现客户端套接字,套接字指的是两台设备之间通讯的端点。
代表客户端程序客户端程序

构造方法
public Socket(String host, int port)
    创建客户端的Socket对象(客户端程序)
    host:服务器端的IP地址
    port:服务器端的端口号
代码示例
//创建客户端的Socket对象(客户端程序)
Socket client = new Socket("localhost", 6666); 
常用方法
public OutputStream getOutputStream()
	返回此套接字的输出流。即获得字节输出流
	如果此Scoket具有相关联的通道,则生成的Outputstream的所有操作也关联该通道。
	关闭生成的OutputStream也将关闭相关的Socket.
	
public InputStream getInputStream()
	返回此套接字的输入流。即获得字节输入流
	如果此Scoket具有相关联的通道,则生成的InputStream的所有操作也关联该通道。
	关闭生成的InputStream也将关闭相关的Socket

public void close() 
	断开连接释放资源

public void shutdownoutput() 
	禁用此套接字的输出流。
	先前写出的数据将全部发送后,再终止输出流。
TCP客户端代码实现步骤
  1. 创建客户端Socket对象(只要该对象创建成功,三次握手就通过了)
  2. 准备要发送的数据:String
  3. 获得字节输出流:OutputStream
  4. 调用字节输出流的write方法输出数据到服务器端
  5. 获得字节输入流:InputStream
  6. 调用字节输入流的read方法读取服务器端发送的数据
  7. 调用Socket对象的close方法关:断开连接释放资源
代码示例

客户端和服务端用本机电脑完成

public class Demo061 {
    public static void main(String[] args) throws Exception{
        // 1. 创建客户端Socket对象(只要该对象创建成功,三次握手就通过了)
        Socket socket = new Socket("localhost", 8888);
        System.out.println(socket);
        // 2. 准备要发送的数据:String
        String str = "如何迎娶白富美";
        // 3. 获得字节输出流:OutputStream
        OutputStream out = socket.getOutputStream();
        // 4. 调用字节输出流的write方法输出数据到服务器端
        out.write(str.getBytes());
        // 5. 获得字节输入流:InputStream
        InputStream in = socket.getInputStream();
        // 6. 调用字节输入流的read方法读取服务器端发送的数据
        // 创建字节数组:存储服务器返回的数据
        byte[] buf = new byte[1024];
        int len = in.read(buf);
        System.out.println("len = " + len);
        System.out.println(new String(buf,0,len));

        // 7. 调用Socket对象的close方法关:断开连接释放资源
        socket.close();
    }
}

需要先开启服务端,再开启客户端。不然报错连接失败

ServerSocket类(服务端)

Serversocket类实现了服务器套接字,该对象等待通过网络的请求。

构造方法
public serversocket(int port) 
	创建服务器端Socket对象并指定端口号
代码示例
ServerSocket server = new ServerSocket(6666); 
常用方法
Socket accept()
	等待客户端连接并获得与客户端关联的Socket
TCP服务器端代码实现步骤:
  1. 创建服务器端ServerSocket对象:开启服务器程序
  2. 等待客户端连接
  3. 获得字节输入流
  4. 调用字节输入流的read方法:读取客户端发送的数据
  5. 获得字节输出流
  6. 调用字节输出流的write方法:往客户端输出数据
  7. 关闭服务器断开连接(在实际开发中服务器是不会关闭的)
代码示例

客户端和服务端用本机电脑完成

public class Demo062 {
    public static void main(String[] args) throws Exception{
        // 1. 创建服务器端ServerSocket对象:开启服务器程序
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("开启了服务器......");
        // 2. 等待客户端连接
        Socket socket = serverSocket.accept();
        // 3. 获得字节输入流
        InputStream in = socket.getInputStream();
        // 4. 调用字节输入流的read方法:读取客户端发送的数据
        // 创建字节数组:用来客户端发送的数据
        byte[] buf = new byte[1024];
        int len = in.read(buf);
        System.out.println("len = " + len);
        System.out.println("客户端发送的内容:" + new String(buf,0,len));
        // 5. 获得字节输出流
        OutputStream out = socket.getOutputStream();
        // 6. 调用字节输出流的write方法:往客户端输出数据
        out.write("你必须高富帅".getBytes());
        // 7. 关闭服务器断开连接(在实际开发中服务器是不会关闭的)
        serverSocket.close();
    }
}

2、TCP通信案例

TCP通信分析图解

1、【服务端】启动,创建ServerSocket对象,等待连接。
2、【客户端】启动,创建Socket对象,请求连接。
3、【服务端】接收连接,调用accept方法,并返回一个Socket对象。
4、【客户端】 Socket对象,获取OutputStream,向服务端写出数据。
5、【服务端】 Scoket对象,获取InputStream,读取客户端发送的数据。
到此,客户端向服务端发送数据成功。
在这里插入图片描述
自此,服务端向客户端回写数据。
6、【服务端】 Socket对象,获取OutputStream,向客户端回写数据。
7、【客户端】 Scoket对象,获取InputStream,解析回写数据。
8、【客户端】释放资源,断开连接。

代码实现

客户端向服务器发送数据
public class ClientTCP {
	public static void main(String[] args) throws Exception {
		System.out.println("客户端 发送数据");
		// 1.创建 Socket ( ip , port ) , 确定连接到哪里.
		Socket client = new Socket("localhost", 6666);
		// 2.通过Scoket,获取输出流对象
		OutputStream os = client.getOutputStream();
		// 3.写出数据.
		os.write("你好么? tcp ,我来了".getBytes());
		// ==============解析回写=========================
		// 4. 通过Scoket,获取 输入流对象
		InputStream in = client.getInputStream();
		// 5. 读取数据
		byte[] b = new byte[100];
		int len = in.read(b);
		System.out.println(new String(b, 0, len));
		// 6. 关闭资源 .
		in.close();
		os.close();
		client.close();
	}
}
服务器向客户端回写数据
public class ServerTCP {
	public static void main(String[] args) throws IOException {
		System.out.println("服务端启动 , 等待连接 .... ");
		// 1.创建 ServerSocket对象,绑定端口,开始等待连接
		ServerSocket ss = new ServerSocket(6666);
		// 2.接收连接 accept 方法, 返回 socket 对象.
		Socket server = ss.accept();
		// 3.通过socket 获取输入流
		InputStream is = server.getInputStream();
		// 4.一次性读取数据
		// 4.1 创建字节数组
		byte[] b = new byte[1024];
		// 4.2 据读取到字节数组中.
		int len = is.read(b)// 4.3 解析数组,打印字符串信息
		String msg = new String(b, 0, len);
		System.out.println(msg);
		//5.关闭资源.
		is.close();
		server.close();
	}
}

四、综合案例

1、文件上传与下载(本机上传下载)

文件上传分析图解

1、【客户端】输入流,从硬盘读取文件数据到程序中。
2、【客户端】输出流,写出文件数据到服务端。
3、【服务端】输入流,读取文件数据到服务端程序。
4、【服务端】输出流,写出文件数据到服务器硬盘中。
在这里插入图片描述

代码实现

客户端
public class FileUPload_Client {
	public static void main(String[] args) throws IOException {
		// 1.创建流对象
		// 1.1 创建输入流,读取本地文件
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
		// 1.2 创建输出流,写到服务端
		Socket socket = new Socket("localhost", 6666);
		BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
		
		//2.写出数据.
		byte[] b = new byte[1024 * 8 ];
		int len ;
		while (( len = bis.read(b))!=-1) {
			bos.write(b, 0, len);
			bos.flush();
		}
		System.out.println("文件发送完毕");
		// 3.释放资源
		bos.close();
		socket.close();
		bis.close();
		System.out.println("文件上传完毕");
	}
}
服务端
public class FileUpload_Server {
	public static void main(String[] args) throws IOException {
		System.out.println("服务器 启动..... ");
		// 1. 创建服务端ServerSocket
		ServerSocket serverSocket = new ServerSocket(6666);
		// 2. 建立连接
		Socket accept = serverSocket.accept();
		
		// 3. 创建流对象
		// 3.1 获取输入流,读取文件数据
		BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
		// 3.2 创建输出流,保存到本地 
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.jpg"));
		
		// 4. 读写数据
		byte[] b = new byte[1024 * 8];
		int len;
		while ((len = bis.read(b)) != -1) {
			bos.write(b, 0, len);
		}
		//5. 关闭 资源
		bos.close();
		bis.close();
		accept.close();
		System.out.println("文件上传已保存");
	}
}

文件上传问题分析(主要是服务端)

文件名称写死的问题(系统时间优化文件名)

服务端,保存文件的名称如果写死,那么最终导致服务器硬盘只会保留一个文件,建议使用系统时间优化,保证文件名称唯一,代码如下:

FileOutputStream fis = new FileOutputStream(System.currentTimeMillis()+".jpg") //文件名称
BufferedOutputStream bos = new BufferedOutputStream(fis);
循环接收的问题(循环创建socket,多个用户多个socket)

服务端,指保存一个文件就关闭了,之后的用户无法再上传,这是不符合实际的,使用循环改进,可以不断的接收不同用户的文件,代码如下:

// 每次接收新的连接,创建⼀一个Socket
whiletrue{
	Socket accept = serverSocket.accept();
	......
}
效率问题(多线程处理多用户)

服务端,在接收大文件时,可能耗费几秒钟的时间,此时不能接收其他用户上传,所以,使用多线程技术优化,代码如下:

whiletrue{
	Socket accept = serverSocket.accept();
	// accept 交给子线程处理.
	new Thread(() -> {
		......
		InputStream bis = accept.getInputStream();
		......
	}).start();
}

上传下载案例优化实现(服务端)

服务端

public class FileUpload_Server {
	public static void main(String[] args) throws IOException {
		System.out.println("服务器 启动..... ");
		// 1. 创建服务端ServerSocket
		ServerSocket serverSocket = new ServerSocket(6666);
		
		// 2. 循环接收,建立连接
		while (true) {
			Socket accept = serverSocket.accept();
		
			/*
			3. socket对象交给子线程处理,进行读写操作
			Runnable接口中,只有一个run方法,使用lambda表达式简化格式
			*/
			new Thread(() -> {
				try (
					//3.1 获取输入流对象
					BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
					//3.2 创建输出流对象, 保存到本地 .
					FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
					BufferedOutputStream bos = new BufferedOutputStream(fis);) {
					// 3.3 读写数据
					byte[] b = new byte[1024 * 8];
					int len;
					while ((len = bis.read(b)) != -1) {
						bos.write(b, 0, len);
					}
					//4. 关闭 资源
					bos.close();
					bis.close();
					accept.close();
					System.out.println("⽂件上传已保存");
				} catch (IOException e) {
					e.printStackTrace();
				}
			}).start();
		}
	}
}

2、信息回写分析图解(上传下载案例增加信息回写)

前四步与基本文件上传一致
5、【服务端】获取输出流,回写数据。
6、【客户端】获取输入流,解析回写数据。
在这里插入图片描述

代码实现

客户端
public class FileUPload_Client {
	public static void main(String[] args) throws IOException {
		// 1.创建流对象
		// 1.1 创建输入流,读取本地文件
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
		// 1.2 创建输出流,写到服务端
		Socket socket = new Socket("localhost", 6666);
		BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
		
		//2.写出数据.
		byte[] b = new byte[1024 * 8 ];
		int len ;
		while (( len = bis.read(b))!=-1) {
			bos.write(b, 0, len);
			bos.flush();
		}
		System.out.println("文件发送完毕");

		// 3. =====解析回写============
		InputStream in = socket.getInputStream();
		byte[] back = new byte[20];
		in.read(back);
		System.out.println(new String(back));
		in.close();
		// ============================

		// 4.释放资源
		bos.close();
		socket.close();
		bis.close();
		System.out.println("文件上传完毕");
	}
}
服务端
public class FileUpload_Server {
	public static void main(String[] args) throws IOException {
		System.out.println("服务器 启动..... ");
		// 1. 创建服务端ServerSocket
		ServerSocket serverSocket = new ServerSocket(6666);
		
		// 2. 循环接收,建立连接
		while (true) {
			Socket accept = serverSocket.accept();
		
			/*
			3. socket对象交给子线程处理,进行读写操作
			Runnable接口中,只有一个run方法,使用lambda表达式简化格式
			*/
			new Thread(() -> {
				try (
					//3.1 获取输入流对象
					BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
					//3.2 创建输出流对象, 保存到本地 .
					FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
					BufferedOutputStream bos = new BufferedOutputStream(fis);) {
					// 3.3 读写数据
					byte[] b = new byte[1024 * 8];
					int len;
					while ((len = bis.read(b)) != -1) {
						bos.write(b, 0, len);
					}

					// 4.=======信息回写===========================
					System.out.println("back ........");
					OutputStream out = accept.getOutputStream();
					out.write("上传成功".getBytes());
					out.close();
					//================================
					
					//5. 关闭 资源
					bos.close();
					bis.close();
					accept.close();
					System.out.println("⽂件上传已保存");
				} catch (IOException e) {
					e.printStackTrace();
				}
			}).start();
		}
	}
}

3、模拟服务器(只写服务端代码)

需求

模拟网站服务器,使用浏览器访问自己编写的服务端程序,查看网页效果。

案例分析

1、准备页面数据,保存到baidu.txt文本中。

<!DOCTYPE html><!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>

2、当使用浏览器器访问服务端程序时,回写页面信息。

处理文本文件使用字符流

代码实现

public class SerDemo {
	public static void main(String[] args) throws IOException {
		System.out.println("服务端 启动 , 等待连接 .... ");
		// 1.创建ServerSocket 对象
		ServerSocket ss = new ServerSocket(8888);
		// 2.建⽴连接
		Socket socket = ss.accept();
		// 3. 获取输入流,打印浏览器访问信息
		InputStream in = socket.getInputStream();
		byte[] bytes = new byte[1024];
		int len= in.read(bytes);
		System.out.println(new String(bytes,0,len));
		// 4. 读取文本文件,准备回写数据
		BufferedReader bufferedReader = new BufferedReader(new FileReader("baidu.txt"));
		String page = bufferedReader.readLine();
		// System.out.println(page);
		// 5. 获取输出流,回写数据
		OutputStream out = socket.getOutputStream();
		out.write(page.getBytes());
		// 6. 释放资源
		socket.close();
		ss.close();
	}
}

测试

使用浏览器访问:localhost:8888

火狐浏览器
在这里插入图片描述
IE浏览器
在这里插入图片描述
不同的浏览器,内核不一样,解析效果也就不一样。

五、文件上传与下载案例(不同机的单用户/多用户)

1、单用户上传

客户端

实现步骤

  1. 创建客户端Socket对象
  2. 创建字节输入流关联源文件:要上传的文件
  3. 调用Socket对象的方法获得字节输出流:将文件输出发送给服务器端
  4. 创建字节数组:用来存储读取到的文件数据
  5. 定义整形变量:接收实际读取到的字节个数
  6. 使用循环读取源文件数据
  7. 将字节数组中的文件数据输出到服务器端
  8. 调用Socket对象的方法获得字节输入流:读取服务器返回的上传状态信息
  9. 关闭Socket断开连接释放资源
public class UploadFileClient {
    public static void main(String[] args) throws Exception{
        // 1. 创建客户端Socket对象
        Socket socket = new Socket("192.168.65.78", 9999);
        // 2. 创建字节输入流关联源文件:要上传的文件
        FileInputStream fis = new FileInputStream("/Users/pkxing/documents/aaa.jpg");
        // 3. 调用Socket对象的方法获得字节输出流:将文件输出发送给服务器端
        OutputStream out = socket.getOutputStream();
        // 4. 创建字节数组:用来存储读取到的文件数据
        byte[] buf = new byte[1024];
        // 5. 定义整形变量:接收实际读取到的字节个数
        int len = -1;
        // 6. 使用循环读取源文件数据
        while((len = fis.read(buf)) !=-1) { // len = -1
            // 7. 将字节数组中的文件数据输出到服务器端
            out.write(buf,0,len);
        }
        // 告诉服务器端文件传输完毕:输出一个结束标记给服务器端
        socket.shutdownOutput();
        // 8. 调用Socket对象的方法获得字节输入流:读取服务器返回的上传状态信息
        InputStream in = socket.getInputStream();
        len = in.read(buf);
        System.out.println(new String(buf,0,len)); // 上传成功
        // 9. 关闭Socket断开连接释放资源
        socket.close();
    }
} 

服务端

实现步骤:

  1. 创建服务器端的ServerSocket对象并指定端口号
  2. 等待客户端连接并获得Socket对象
  3. 调用Socket对象的方法获得字节输入流对象:用来读取客户端发送的文件数据
  4. 创建字节输出流对象关联目标文件
  5. 定义字节数组:存储读取到的文件数据
  6. 定义整形变量:接收实际读取到的字节个数
  7. 使用循环读取客户端发送的文件数据
  8. 将字节数组的内容输出到目标文件中
  9. 调用Socket对象的方法获得字节输出流
  10. 调用字节输出流的write返回上传状态信息给客户端
  11. 关闭服务器端
public class UploadFileServer {
    public static void main(String[] args) throws Exception {
        // 1. 创建服务器端的ServerSocket对象并指定端口号
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("开启了服务器......");
        // 2. 等待客户端连接并获得Socket对象
        Socket socket = serverSocket.accept();
        try{
           // 3. 调用Socket对象的方法获得字节输入流对象:用来读取客户端发送的文件数据
           InputStream in = socket.getInputStream();
           // 4. 创建字节输出流对象关联目标文件
           // 目标文件如何命名?
           // 在实际开发中,目标文件名命名方式有两种:
           // 方式1:目标文件名和用户上传的文件名保持一致(几乎不使用了,有可能不同用户会上传同名的文件会,出覆盖)
           // 方式2:不理会用户上传的文件名是什么,服务器端自动随机生成一个新的目标文件名,推荐使用
           // 可以采用当前时间的毫秒值来作为目标文件的名称
           int index = new Random().nextInt(999999999); // 随机生成一个0到999999998整数
           String fileName = index + System.currentTimeMillis()+".jpg"; // 15003243241.jpg

            // 创建目标文件对象
            File destFile = new File("/Users/pkxing/documents/aaa/" + fileName);
            FileOutputStream fos = new FileOutputStream(destFile);
           // 5. 定义字节数组:存储读取到的文件数据
           byte[] buf = new byte[1024];
           // 6. 定义整形变量:接收实际读取到的字节个数
           int len = -1;
           // 7. 使用循环读取客户端发送的文件数据
           while ((len = in.read(buf)) != -1) { // len = -1
               // 8. 将字节数组的内容输出到目标文件中
               fos.write(buf, 0,len);
           }
           // 获得客户端ip地址
            String clientIP = socket.getInetAddress().getHostAddress();
            System.out.println("恭喜IP为 "+clientIP+" 用户上传成功");
            // 关闭流释放资源
           fos.close();
           // 9. 调用Socket对象的方法获得字节输出流
           OutputStream out = socket.getOutputStream();
           // 10. 调用字节输出流的write返回上传状态信息给客户端
           out.write("上传成功".getBytes());
       } catch(Exception e){
           // 9. 调用Socket对象的方法获得字节输出流
           OutputStream out = socket.getOutputStream();
           // 10. 调用字节输出流的write返回上传状态信息给客户端
           out.write("上传失败".getBytes());
       }
    }
}

文件命名:随记数+当前时间毫秒数

2、多用户上传

客户端

public class UploadFileClient {
    public static void main(String[] args) throws Exception{
        // 1. 创建客户端Socket对象
        Socket socket = new Socket("192.168.65.78", 9999);
        // 2. 创建字节输入流关联源文件:要上传的文件
        FileInputStream fis = new FileInputStream("/Users/pkxing/documents/aaa.jpg");
        // 3. 调用Socket对象的方法获得字节输出流:将文件输出发送给服务器端
        OutputStream out = socket.getOutputStream();
        // 4. 创建字节数组:用来存储读取到的文件数据
        byte[] buf = new byte[1024];
        // 5. 定义整形变量:接收实际读取到的字节个数
        int len = -1;
        // 6. 使用循环读取源文件数据
        while((len = fis.read(buf)) !=-1) { // len = -1
            // 7. 将字节数组中的文件数据输出到服务器端
            out.write(buf,0,len);
        }
        // 告诉服务器端文件传输完毕:输出一个结束标记给服务器端
        socket.shutdownOutput();
        // 8. 调用Socket对象的方法获得字节输入流:读取服务器返回的上传状态信息
        InputStream in = socket.getInputStream();
        len = in.read(buf);
        System.out.println(new String(buf,0,len)); // 上传成功
        // 9. 关闭Socket断开连接释放资源
        socket.close();
    }
}

服务端

public class UploadFileServer {
    public static void main(String[] args) throws Exception {
        // 1. 创建服务器端的ServerSocket对象并指定端口号
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("开启了服务器......");
        // 使用死循环保证服务器永不退出
        while (true){
            // 2. 等待客户端连接并获得Socket对象
            Socket socket = serverSocket.accept();
            // 3. 创建线程处理用户上传文件
            // Runnable接口的抽象方法:void run();
            new Thread(()->{
                try{
                    // 3. 调用Socket对象的方法获得字节输入流对象:用来读取客户端发送的文件数据
                    InputStream in = socket.getInputStream();
                    // 4. 创建字节输出流对象关联目标文件
                    // 目标文件如何命名?
                    // 在实际开发中,目标文件名命名方式有两种:
                    // 方式1:目标文件名和用户上传的文件名保持一致(几乎不使用了,有可能不同用户会上传同名的文件会,出覆盖)
                    // 方式2:不理会用户上传的文件名是什么,服务器端自动随机生成一个新的目标文件名,推荐使用
                    // 可以采用当前时间的毫秒值来作为目标文件的名称
                    int index = new Random().nextInt(999999999); // 随机生成一个0到999999998整数
                    String fileName = index + System.currentTimeMillis()+".jpg"; // 15003243241.jpg

                    // 创建目标文件对象
                    File destFile = new File("/Users/pkxing/documents/aaa/" + fileName);
                    FileOutputStream fos = new FileOutputStream(destFile);
                    // 5. 定义字节数组:存储读取到的文件数据
                    byte[] buf = new byte[1024];
                    // 6. 定义整形变量:接收实际读取到的字节个数
                    int len = -1;
                    // 7. 使用循环读取客户端发送的文件数据
                    while ((len = in.read(buf)) != -1) { // len = -1
                        // 8. 将字节数组的内容输出到目标文件中
                        fos.write(buf, 0,len);
                    }
                    // 获得客户端ip地址
                    String clientIP = socket.getInetAddress().getHostAddress();
                    System.out.println("恭喜IP为 "+clientIP+" 用户上传成功");
                    // 关闭流释放资源
                    fos.close();
                    // 9. 调用Socket对象的方法获得字节输出流
                    OutputStream out = socket.getOutputStream();
                    // 10. 调用字节输出流的write返回上传状态信息给客户端
                    out.write("上传成功".getBytes());
                } catch(Exception e){
                    try{
                        // 9. 调用Socket对象的方法获得字节输出流
                        OutputStream out = socket.getOutputStream();
                        // 10. 调用字节输出流的write返回上传状态信息给客户端
                        out.write("上传失败".getBytes());
                    } catch(Exception e1){

                    }
                }
            }).start();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值