JAVA编程思想进阶(二)IO流,网络编程,


程序就是对象的集合

五. IO流

File类的使用

在高中的时候,我考虑过聊天机器人的技术。我当时想,如果机器人无法记住我们之前的对话,那么我们跟机器人的对话就没有积累,每次都是新的开始。但是程序关掉之后,那些对话全都消失了,有没有什么办法可以记录下来呢。当时的我没有办法解决这个问题

File类:使用File类对象表示一个文件或者文件夹,并对其进行操作。但是,涉及到写入或者读取文件内容的操作时,需要IO流来完成

持久化内存技术

持久化指的就是当程序退出后,其中的数据能够保留下来,供程序再次运行的时候使用

File类的实例化

java.io.File
提供了三种构造器
(1)带参构造器,绝对路径或者相对路径
路径中的每级目录之间用一个路径分隔符隔开
windows系统,DOS系统:使用反斜杠
UNIX系统:使用斜杠/
java支持跨平台,使用时要注意
File类提供了一个静态常量作为分隔符,public static final String separater

File file1 = new File( pathname: "hello.txt");//相对于当前module
File file2 = new File( pathname:"D: \\workspace_idea1\\JavaSenior\\daye8\\he.txt");
File file2 = new File("d:" + File.separator + "atguigu” File.separator + "infd.txt" );

(2)带参构造器,父目录路径,子目录路径

File file3 = new File( parent: "D:\\workspace_idea1", child: "JavaSenior");

(3)带参构造器,File类对象,路径

File file3 = new File( parent: "D:\\workspace_idea1", child: "JavaSenior");
File file4 = new File(file3,child: "hi.txt" );

File类实例化之后,如果直接输出对象,输出的是传入构造器参数的拼接,最终指向文件或者文件夹
并且实例化还只是内存层面,不涉及到持久化内存

File类常用方法

File类的获取功能

public String getAbsolutePath():获取绝对路径
public String getPath():获取路径
public String getName():获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length():获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified():获取最后一次的修改时间,毫秒值

如下两个方法适用于文件目录(文件目录就是文件夹)
public String[] list():获取指定目录下的所有文件或者文件目录的名称数组,带后缀的文件名
public File[] listFiles():获取指定目录下的所有文件或者文件目录的File数组,文件的绝对路径

使用不同的构造器实例化对象,那么上面某些函数的结果就会不一样
(1)实例化传入相对路径,那么getParent()的结果就是null
(2)实例化传入绝对路径,那么getParent()的结果就是去掉绝对路径的最后一个目录

public booleanrenameTo(File dest):把文件重命名为指定的文件路径

File file1 = new File( pathname: "hello.txt" );
File file2 = new File( pathname: "D: llio\\hi.txt" );
boolean renameTo = file1.renameTo(file2);
system.out.println(renameTo);

所谓的重命名,其真实过程是这样的:将f1重命名为f2
(1)首先保证需要file1在硬盘中是存在的,且file2不能在硬盘中存在
(2)创建f2
(3)将f1的内容写到f2里面
(4)删除f1

File类的判断方法

public boolean isDirectory():判断是否是文件目录
public boolean isFile():判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite():判断是否可写
public boolean isHidden() :判断是否隐藏

前三个用于判断的方法非常常用

File类的创建功能

在硬盘中创建文件或者文件夹
public boolean createNewFile():创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs():创建文件目录。如果上层文件目录不存在,一并创建

File类的删除功能

public boolean delete():删除文件或者文件夹删除
注意事项:
Java中的删除不走回收站
要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录,文件目录必须是空的

内存结构

在这里插入图片描述
在这里插入图片描述
当硬盘中真有一个真实的文件或目录存在时,创建File对象时,各个属性会显式赋值。
当硬盘中没有真实的文件或目录对应时,那么创建对象时,除了指定的目录和路径之外,其他的属性都是取成员变量的默认值。

IO流原理及流的分类

背景

I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。

在java程序中,数据以流的形式输入输出

java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

核心

文件输入以字符流方式,那么输出也必须是字符流方式;输入以字节流方式,输出也必须是字节流方式

java IO原理

输入输出是相对概念,我们是站在程序,也是内存的这一侧看待输入输出

流的分类

字节流(8bit)(图片,视频,非文本数据),字符流(16bit)
输入流,输出流
节点流(最底层的流),处理流(修饰底层流的流)

在这里插入图片描述
java的IO流共涉及40多个类,但都是基于4个抽象基类

在这里插入图片描述
子类都会以其继承的父类名作为后缀
除了第二行是节点流,之后的每一行都是处理流
节点流又叫做文件流

节点流(文件流)

FileReader子类

注意在方法中创建File对象和在main中不同,相对路径的上一级目录不同

public static void main( string[] args) i
	File file = new File( pathname: "hello.txt");//相较于当前工程
	File file1 = new File( pathname: "day09\\hello.txt" );
	System.out.println(file1.getAbsolutePath());
@Test
public void testFileReader(){
	File file = new File( pathname: "hello.txt");//相较于当前ModuLe
}

将文件的内容读入内存并输出到控制台
说明点:
(1)read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1。本质上每次使用read(),就会和硬盘交互一次
(2)异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理3.读入的文件一定要存在,否则就会报FiLeNotFoundException。

public void testFileReader() throws IOException {
	//1.实例化FiLe类的对象,指明要操作的文件
	File file = new File( pathname: "hello.txt");//相较于当前ModuLe
	//2.提供具体的流,字符输入节点流
	FileReader fr = new FileReader(file);
	//3.数据的读入
	//read():返回读入的一个字符。如果达到文件末尾,返回-1
	int data = fr.read();
	while(data != -1){
	system.out.print1n((char)data);
	data = fr.read();
		}

	//流的关闭
	//JVM对其他物理连接,比如数据库连接、输入流输出流、Socket连接无能为力
	fr.close();

}

read()的工作方式和Iterator接口的next()很像,我估计底层也是用了指针

流的关闭是必须要执行的,而此方法选择throws的方式抓住异常,这样就导致流没有关闭。将throws改成try-catch模型

public void testFileReader(){
		FileReader fr = null;
		try {
			File f = new File("src\\hello.txt");
			fr = new FileReader(f);
			int data;
			while((data = fr.read()) != -1){
				System.out.print((char)data);
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			try {
				fr.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

这里我自己写的时候出现了一个问题,查了好久。就是节点流引用的创建要放在try外面,因为一旦出现异常,跳出try之后,节点流引用就丢失了,想去关闭节点流会找不到这个引用

加强版的read(),read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。并且将取到的字符存入cbuf数组。如果达到文件末尾,返回-1

char[] cbuf = new char[5];//这里要定好char数组的长度,表示预期每次读入多少个字符
int len;
while((len = fr.read(cbuf)) != -1){
	for(int i = 0;i < len;i++) syso(cbuf[i]);

	//或者说可以采用char型数组和String数组的转换
	String s = new String(cbuf,0,len);//从下标0开始,取len个元素
	syso(s);
}

FileWriter子类

//1.提供File类的对象,指明写出到的文件
File file = new File( pathname: "he11o1.txt" );
//2.提供Filewriter的对象,用于数据的写出
Filewriter fw = new Filewriter(file);
//3.写出的操作
fw.write( str: "I have a dream! " );
//4.流资源的关闭
fw.close();

如果没有这个文件,可以先创建文件。File类有用于创建文件的方法,public boolean createNewFile()
但是在这里,FileWriter子类会自己创建还不存在的文件,所以不必担心

FileWriter子类还有一个重载的构造器,多了一个append参数,表示保留文件的原有内容,在后面添加新的内容,而非替换掉旧文件

使用FileReader子类和FileWriter子类实现文本复制

//1.创建File类的对象,指明读入和写出的文件
File srcFile = new File( pathname: "hello.txt" );
File destFile = new File( pathname: "hello2.txt" );

//2.创建输入流和输出流的对象
FileReader fr = new FileReader(srcFile) ;
Filewriter fw = new Filewriter(destFile);

//3.数据的读入和写出操作
char[]cbuf = new char[5];
int len;//记录每次读入到cbuf数组中的字符的个数
while( ( len = fr.read(cbuf)) != -1){
	//每次写出Len个字符
	fw.write(cbufl off: 8,len) ;
}
//4.关闭输入输出流资源

这四步就是IO流的核心框架

字符流不能处理图片文件

图片文件属于字节数据,都是0101的,字符流是不能用来处理字节数据的。python要打开一张图片倒是非常的简单,opencv就能轻松搞定

节点流有四种,其中两种是字节流,两种是字符流

FileInputStream子类和FileOutputStream子类

//1.造文件
File file = new File(pathname: "hello.txt" );
//2.造流
FileInputstream fis = new FileInputStream(file);
//3.读数据
byte[] buffer = new byte[5];
int len;//记录每次读取的字节的个数
while( ( len = fis.read(buffer)) != -1){
	string str = new string( buffer, offset: 0,len);
	system.out.print(str);
}
//4.关闭资源
fis.close();

用字节流对象读取文件,那么就造一个字节类型的数组byte[]

程序的结果和使用字符流读取的结果是一样的。按理说,字符是占两个字节,用字节流应该出现乱码。但实际上,英文字母,数字就只用了一个字节来存储。如果文件中加入中文这种一个字符占四个字节的数据,出现乱码就在情理之中了

对于纯文本文件(.txt .java .c .cpp),使用字符流处理
对于非纯文本文件(.jpg .mp3 .mp4 .avi .doc),使用字节流处理

核心步骤和字符流那里是一样的:
1.创建File类的对象,指明读入和写出的文件
2.创建输入流和输出流的对象
3.数据的读入和写出操作
4.关闭流资源

缓冲流(处理流)

BufferInputStream, BufferOutputStream, BufferReader, BufferWriter四个子类

核心:提升文件的读写速率

使用缓冲流加速非文本文件的复制

//1.造文件
File fi = new File("C:\\Users\\JJCale\\Desktop\\mingtian.jpg");
File fo = new File("C:\\Users\\JJCale\\Desktop\\mingtian2.jpg");
		
//2.造流
FileInputStream fis = new FileInputStream(fi);
FileOutputStream fos = new FileOutputStream(fo);

BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);

//3.复制
byte[] b = new byte[1024];
int len;
while((len = bis.read(b)) != -1){
	bos.write(b, 0, len);
}
System.out.println("复制成功");

//4.关闭流资源
bos.close();
bis.close();
fos.close();
fis.close();

说明:外层流关闭的时候,会把传入的内层流自动关闭

缓冲流加速的原理

其内部提供了一个类变量缓冲区:private static int DEFAULT_BUFFER_SIZE = 8192;

读取的文件数据先存到缓冲区,等到存满了再刷新缓冲区

使用缓冲流复制文本文件

BufferReader子类又多了一个read系列的方法,可以实现按行读取文本文件

/方式二:使用string
string data;
while((data = br.readLine()) != null){
	bw.write(data); //data中不包含换行符
	bw.newLine();//增加每行的换行符
}

因为readLine方法不包含换行符,所以在输出的时候还需要补上换行符,BufferWriter提供了newLine方法以实现这一点

总结:
在这里插入图片描述

转换流

转换流提供了在字节流和字符流之间的转换

也能实现文件以不同编码集的持久化存储

有InputStreamReader和OutputStreamWriter两个子类
在这里插入图片描述
从见名生意的角度看,OutputStreamWriter看起来很像是从字节流转换到字符流,但实际上它是从字符流到字节流

这张图有些反直觉的地方。在之前,进行文本文件的复制是不涉及到编码集的。一个文本文件的输入和输出都应该用字符流来处理。输入使用节点流里边的输入字符流FileReader,输出使用节点流里边的输出字符流FileWriter

我感觉这个图可以做得更细致一点,按照标准的四个步骤:
在这里插入图片描述
首先,文件在底层是二进制形式,对于纯文本文件,以字符流的方式读取;否则以字节流的方式读取。上图左分支相当于先用字节流读取,再将结果转换为字符流;等价于直接用字符流读取。这也符合对纯文本文件的读取方式

右边分支先将文件设置为数据以字节流方式写入,再将文件设置为字符流方式写入

实现文本文件以不同编码集的复制

//1.造文件、造流
File file1 = new File( pathname: "dbcp.txt" ) ;
File file2 = new File( pathname: "dbcp_gbk.txt" );
FileInputStream fis = new FileInputStream(file1);
Fileoutputstream fos = new Fileoutputstream(file2);
InputstreamReader isr = new InputStceamReader(fis,".utf.8".);
outputstreamwriter osw = new OutputStreamWriter(fos,"gbk");
//2.读写过程
char[ ] cbuf = new char[20];
int len;
while((len = isr.cead(cbuf)) != -1){
	osw. write(cbuf,0,len);
}
//3.关闭资源
I
isr.close();
osw.close();

标准输入输出流

数据流

对象流

随机存储文件流

NIO.2中Path,Paths,Files类的使用

网络编程

概述

Java提供的网络类库,可以实现无痛的网络连接,联网的底层细节被隐藏在Java的本机安装系统里,由JVM进行控制。并且Java实现了一个跨平台的网络库,程序员面对的是一个统一的网络编程环境。

网络编程的目的

直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯

两个主要问题

(1)定位:如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
(2)高效传输:找到主机后如何可靠高效地进行数据传输

网络通信要素

IP和网络端口

网络协议

OSI参考模型(七层,物联网淑慧试用,不用这个),TCP/IP参考模型(四层,国际标准)
在这里插入图片描述
计算机网络中实现通信必须有一些约定,即通信协议,对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。

计算机网络通信涉及内容很多,比如指定源地址和目标地址,加密解密,压缩解压缩,差错控制,流量控制,路由控制,如何实现如此复杂的网络协议呢?

通信要素1:IP和网络端口

IP地址

IP地址很抽象,采用域名来定位一台或多台主机

本地回环地址:127.0.0.1,对应的域名localhost。访问本机上的服务器,那就填这个IP地址。比如说在本机上装MySQL数据服务器

InetAddress类

在Java中使用InetAddress类对象代表一台或多台主机

创建主机是通过调用InetAddress类的getByName方法来实现的

InetAddress对象可以通过两个方法getHostName方法,getHostAddress方法分别获取主机域名和主机IP地址

InetAddress i = InetAddress.getByName("192.168.10.14");

getByName方法中的参数是主机名,可以将对象i视作IP地址为192.168.10.14的主机

也可以在getByName方法中填入域名,输出结果除了域名,还有DNS解析出来的IP地址

InetAddress i1 = InetAddress.getByName("www.baidu.com");

本机IP地址使用也很频繁,InetAddress类提供了获取本机地址的静态方法

InetAddress i2 = InetAddress.getLocalHost();

输出i2的结果是,本机自定义主机名+局域网地址(私网地址)DESKTOP-…/192.168.256.256

网络端口

端口号标识正在计算机上运行的进程(程序)

一个16位的整数0~65535

端口分类

公认端口:~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占巾端口21,Telnet占用端口23)
注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。可以改,比如启用两个Tomcat
动态/私有端口:49152~65535。

端口号与IP地址的组合得出一个网络套接字:Socket,一个socket可以定位一台主机上的一个应用。因此网络编程也叫作socket编程。socket,直译就是插座的意思,最先采用这个词的人,觉得网络连接,就像插口和插座的关系
Socket是一个类

通信要素2:网络协议

TCP/IP协议将网络分为四层:物理层+数据链路层,网络层,传输层,应用层(会话层+表示层+应用层)

传输层有两个重要的协议:
(1)传输控制协议TCP(Transmission Control Protocol)
(2)用户数据报协议UDP(User Datagram Protocol)。

TCP/IP协议簇 = 传输层的TCP + 网络层的IP协议(网络互联协议)

TCP协议

(1)使用TCP协议前,须先建立TCP连接,形成传输数据通道
(2)传输前,采用***“三次握手”方式,点对点通信,是可靠
在这里插入图片描述
SYN同步序列编号,ACK确认字符。
简单说一下,seq号是随机的,ACK = 收到的seq + 1
(3)TCP协议进行通信的
两个应用进程***:客户端、服务端。
(4)在连接中可进行大数据量的传输
(5)传输完毕,需释放已建立的连接,效率低
在这里插入图片描述
我感觉步骤三的ack应该大写,结束连接的时候ACK = 收到的seq
(1)客户端A发送一个FIN,用为关团客户A到服务器B的数据传送。
(2) 服务器B收到这个FIN,它发回一个ACK,确认序号为收到序号加1。和SYN一样,一个FIN将占用一个序号。
(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。
(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。

类似于打电话,专用线路

UDP协议

(1)将数据、源、目的封装成数据包,不需要建立连接
(2)每个数据报的大小限制在64K
(3)发送不管对方是否准备好,接收方收到也不确认,故是不可靠
(4)可以广播发送
(5)发送数据结束时无需释放资源,开销小,速度快

应用:看视频(丢数据无所谓,但不能卡顿),类似于发短信

TCP网络编程

客户端的创建

我之前认为客户端和服务器端都应该是类,使用的时候都应该实例化。那要真是这样,他们的属性和方法应该是什么呢?直觉上这些东西很难固定下来。另外,客户端和服务器还有需要一直在线的需求,如果使用对象,该怎么实现这种需求呢??

1.创建Socket对象,指明服务器端的ip和端口号。
Socket是一个类,这一点没有提到。使用Socket类对象表示客户端。造好客户端之后,就要
尝试连接服务器,看看目标主机上哪个程序的端口号是目标端口号
2.获取一个输出流,用于输出数据。
但是这个输出字节流的创建方式和之前很不一样。先是创建了一个抽象基类的对象,然后通过调
用Socket对象的getOutputStream方法来实例化
3.写出数据的操作
这里通过客户端向服务器发送了字符串,但是输出流用的是字节流,所以对字符串调用了
getBytes方法将字符串改写成字节流的形式(有点稀里糊涂)
4.资源的关闭

我没看出来TCP在哪里,是封装到什么位置了吗?是由Socket对象去实现了TCP连接吗??感觉上是的,Socket对象定位了服务器的位置,但他怎么知道是TCP连接呢???

步骤2还有个反直觉的地方,在IO流一章,我们是将数据输出到文件中,所以节点流需要传入文件,然后再用节点流对象去不停地往文件写入数据。而这里是直接用输出流写数据。注意这里的区别,前者用的是节点流,后者是输出流
在这里插入图片描述

 //客户端
    @Test
    public void client()  {
        Socket socket = null;
        OutputStream os = null;
        try {
            //1.创建Socket对象,指明服务器端的ip和端口号
            InetAddress inet = InetAddress.getByName("192.168.14.100");
            socket = new Socket(inet,8899);
            //2.获取一个输出流,用于输出数据
            os = socket.getOutputStream();
            //3.写出数据的操作
            os.write("你好,我是客户端mm".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.资源的关闭

服务器的创建

1.创建服务器端的ServerSocket,指明自己的端口号。
服务器使用ServerSocket类对象来表示,对客户端只需要说明自己的端口是多少
2.调用accept()表示接收来自于客户端的socket
服务器需要去搜索试图连接自己的客户端,调用accept方法搜索用Socket类对象表示的客户端
3.获取输入流
找到客户端之后,需要接收客户端的输出流。创建输入流,使用抽象基类中的输入字节流
InputStream类。调用客户端的getInputStream方法创建输入流
4.读取输入流中的数据
客户端输出的是字节数组,输入的时候使用处理流里边的访问数组输出字节流
ByteArrayOutputStream类
5.关闭资源

		ServerSocket ss = null;
        Socket socket = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        try {
            //1.创建服务器端的ServerSocket,指明自己的端口号
            ss = new ServerSocket(8899);
            //2.调用accept()表示接收来自于客户端的socket
            socket = ss.accept();
            //3.获取输入流
            is = socket.getInputStream();

            //不建议这样写,可能会有乱码
//        byte[] buffer = new byte[1024];
//        int len;
//        while((len = is.read(buffer)) != -1){
//            String str = new String(buffer,0,len);
//            System.out.print(str);
//        }
            //4.读取输入流中的数据
            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[5];
            int len;
            while((len = is.read(buffer)) != -1){
                baos.write(buffer,0,len);
            }

            System.out.println(baos.toString());

            System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {

整体来看ServerSocket对象服务器只需要搜索Socket对象客户端,建立TCP连接。流的输入输出都是客户端来负责

总结

对于上面的过程我又几个疑问
(1)虽然我知道先启动服务器,再启动客户端,但是两端有很多指令,这些指令的执行顺序是什么????
在这里插入图片描述
【1】创建服务器端socket
【2】服务器socket监听连接,等待来自客户端socket的连接请求
【3】创建客户端socket。这个创建就是请求与服务器socket进行连接,不需要显式地发起什么连接
【4】创建流以及服务器和客户端进行数据交换所需要的语句

(2)没有看到TCP???
A:Socket类内部实现了TCP服务,这些是固定的内容,被封装隐藏了

(3)在上例中,客户端里的输出流和服务器端的输入流都是用socket创建,和之前IO流那一章不同,输入是从文件输入,输出是向文件输出,这一点很反直觉???
A:在客户端的编写中,我们看到数据来自于自定义的一个字节数组,socket创建的输出流调用了write方法,将字节数组传了进去,自然而然就会想数据输出到哪里去了。可以这样讲,TCP通信要对socket进行读写,也就是说数据跟随socket

(4)客户端和服务器都有一个while循环,客户端的输出和服务器的输入的执行顺序是什么???
A:回想IO流那一章,在复制文件这一例子中,如果只使用节点流,每输入一次内存,就要读一次硬盘;每输出一次内存,就要写一次硬盘。不是说全读到内存,再一次性写到硬盘。回到上例的情况,数据一开始就在内存,客户端socket每输出一部分数据,服务器socket就要输入一部分数据,不是说客户端socket一次性把数据读完,然后服务器那边一次性输入

事实上,客户端socket读一段数据,就要给服务器发过去,服务器收到后再写到终端上

(5)如果要实现交互需要注意什么???
A:到例题2回答

例题

需求一:客户端发送文件给服务端,服务端将文件保存在本地

输入流需要输入本地的文件,然后客户端将其写入输出流对象

   public void client() throws IOException {
        //1.
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
        //2.
        OutputStream os = socket.getOutputStream();
        //3.
        FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
        //4.
        byte[] buffer = new byte[1024];
        int len;
        while((len = fis.read(buffer)) != -1){
            os.write(buffer,0,len);
        }
        //5.
        fis.close();
        os.close();
        socket.close();
    }
 public void server() throws IOException {
        //1.
        ServerSocket ss = new ServerSocket(9090);
        //2.
        Socket socket = ss.accept();
        //3.
        InputStream is = socket.getInputStream();
        //4.
        FileOutputStream fos = new FileOutputStream(new File("beauty1.jpg"));
        //5.
        byte[] buffer = new byte[1024];
        int len;
        while((len = is.read(buffer)) != -1){
            fos.write(buffer,0,len);
        }
        //6.
        fos.close();
        is.close();
        socket.close();
        ss.close();

这里再总结一下,在IO流那一章,对于复制文件这一需求,数据的输入和输出分别由输入流和输出流对象来负责,数据的来源是传入输入流对象的文件。然后,输入流读取一部分数据,这部分数据用变量buffer来接收,输出流就向输出文件写入读到的那部分数据。输出流的数据来源是输入流,输入流的数据来源是文件

在这里,对于发送数据这个需求,客户端造了一个输出流来写数据,数据由客户端提供。那么这个数据写到哪里去了呢???至少是跟着客户端走的吧,可能,可能是存到客户端的输出流???!然后在服务器端这一边,客户端又造了一个输入流,那么这个输入流的数据来源呢???来自于客户端的输出流。然后单独又造了一个输出流,由这个输出流输出客户端的输入流读到的数据。输出到哪里去呢,没有输出文件作为去向啊???只能说存到输出流了

对于发送文件这个需求,综上,其实就是数据的初始来源发生了变化,去向也发生了变化,最终写到文件里了

需求二:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端,并关闭相应的连接。

需求分析:服务器也可以创建输出流??并不是!如前所说,由客户端来管理输入流输出流,socket读写

    public void client(){
        //1.
		Socket socket = null;
		//2.
		OutputStream os = null;
		//3.
		FileInputStream fis = null;
		
		InputStream is = null;
		
		ByteArrayOutputStream bos = null;
		try {
			
			socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
			os = socket.getOutputStream();
			fis = new FileInputStream(new File("C:\\Users\\JJCale\\Desktop\\123.mp4"));
			//4.
			byte[] buffer = new byte[1024];
			int len;
			System.out.println("客户端开始输出");
			while((len = fis.read(buffer)) != -1){
			    os.write(buffer,0,len);
			    Date d = new Date();
			    System.out.println("客户端输出了一句********" + d.toString());
			}
			System.out.println("客户端全部发完了,看到我就没有被阻塞");
			
			is = socket.getInputStream();
			System.out.println("客户端准备好接收来自服务器的信息");
			bos = new ByteArrayOutputStream();
			byte[] bufferr = new byte[20];
			int len1;
			System.out.println("马上开始接收");
			while((len1 = is.read(bufferr)) != -1){
				System.out.println("完成一次读取");
				bos.write(bufferr, 0 , len1);
			}
			System.out.println(bos.toString());
    public void server(){
        //1.
		ServerSocket ss = null;
		//2.
		
		Socket socket = null;
		//3.
		InputStream is = null;
		//4.
		FileOutputStream fos = null;
		
		OutputStream os = null;
		try {
			
			ss = new ServerSocket(9090);
			System.out.println("搜索客户端");
			socket = ss.accept();
			is = socket.getInputStream();
			fos = new FileOutputStream(new File("C:\\Users\\JJCale\\Desktop\\133.mp4"));
			//5.
			byte[] buffer = new byte[1024];
			int len;
			System.out.println("服务器开始输出");
			while((len = is.read(buffer)) != -1){
			    fos.write(buffer,0,len);
			    Date d = new Date();
			    System.out.println("服务器收到了一句" + d.toString());
			}
			System.out.println("服务器全部收完了,看到我就没有被阻塞");
			
			os = socket.getOutputStream();
			os.write("服务器已经收到了".getBytes());

测试一:在例题一的基础上,无论是客户端还是服务器,while循环之后,都是可以打印语句的
测试二:在例题二,加上服务器向客户端发送数据,客户端和服务器都阻塞在while循环

通过debug,测试一的执行顺序是要等到客户端socket创建的输出流释放之后才执行服务器端while循环之后的打印指令。我之前其实一直没搞清楚客户端,服务器中每条指令的执行顺序,但我又认为很重要
事实上,有了前面debug的结果,测试二就比较好解释了,我有理由认为,如果客户端socket创建的输出流这一资源不释放,那么,服务器是无法像客户端传输数据的

上面这一说法接近答案了,但并不准确。要解释测试二,需要一个新的知识

TCP的连接与关闭

TCP 是双向的,比如客户端到服务器端的方向,指的是客户端通过套接字接口,向服务器端发送 TCP 报文;而服务器端到客户端方向则是另一个传输方向。在绝大多数情况下,TCP 连接都是先关闭一个方向,此时另外一个方向还是可以正常进行数据传输。

是否可以这样认为,客户端socket创建的输出流规定了TCP连接的方向是从客户端到服务器??!!

如果上述猜想成立,那么为什么是由socket调用shutdown方法,而不是客户端socket创建的输出流调用close方法??
A:关闭连接的方法有两种:
(1)close方法:IO流对象可以调用他,表示释放资源;socket对象也可以调用他,表示TCP两个方向的数据流彻底关掉tcp
socket对象调用close往往是在最后。close方法的缺陷在于不能关闭TCP连接的一个方向

close 函数如何关闭两个方向的数据流呢?
在输入方向, 系统内核会将该套接字设置为不可读,任何读操作都会返回异常;
在输出方向, 系统内核 尝试将发送缓冲区的数据发送给对端,并最后向对端发送一个 FIN 报文, 接下来如果再对该套接字进行写操作会返回异常。

(2)shutdown方法:IO流对象不能调用这个方法,socket对象能调用。java提供了两个很清晰的方法shutdownInput(),
shutdownOutput()分别关闭TCP连接的一个方向

补充:socket的底层设计原理

这个部分等到java web去解决

浏览器访问Tomcat服务器

之前听说过tomcat,觉得这个名字好奇怪

客户端:(1)自定义,比如说最简单地写了个函数,可以当做服务器。(2)浏览器
服务器:(1)自定义。比如把自己的计算机当作服务器(2)Tomcat服务器

UDP网络编程

概述

类DatagramSocket和 DatagramPacket实现了基于UDP协议网络程序。

UDP数据报通过数据报套接字DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。

DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
A:TCP通信就不同,服务器监听网络,客户端去找目标IP地址和端口号。似乎客户端的IP地址和端口号没有显示,但是建立TCP连接的时候,通信两端的IP地址和端口号都确定下来了。UDP不建立连接,所以发出的数据需要定位发送端和接收端,就像快递一样

UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。如同发快递包裹一样。

UDP通信不像TCP通信,要求发送数据的双方扮演不同的角色,客户端和服务器,客户端负责进行服务的请求,而服务器进行请求的响应。只是将两者区分为发送者和接受者

例子

 //发送端
    @Test
    public void sender() throws IOException {

        DatagramSocket socket = new DatagramSocket();



        String str = "我是UDP方式发送的导弹";
        byte[] data = str.getBytes();
        InetAddress inet = InetAddress.getLocalHost();
        DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);

        socket.send(packet);

        socket.close();

    }
    //接收端
    @Test
    public void receiver() throws IOException {

        DatagramSocket socket = new DatagramSocket(9090);

		//buffer用来接收数据,实例化DatagramPacket对象的时候也要用
        byte[] buffer = new byte[100];
        DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);

		//接收数据,将数据存到DatagramPacket对象,实际上就是存到上面的buffer中
        socket.receive(packet);

        System.out.println(new String(packet.getData(),0,packet.getLength()));

        socket.close();
    }

感觉没有体现出DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号,没有看到发送者的IP地址和端口号,和TCP通信一样

URL编程

URL(Uniform Resource Locator):统一资源定位符,它表示Internet上某一资源的地址.也就是种子

java提供了一个URL类来进行URL编程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从服务器中下载资源

        HttpURLConnection urlConnection = null;
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            URL url = new URL("http://localhost:8080/examples/beauty.jpg");

			//这两步用来连接服务器,其实和TCP,UDP通信非常类似
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.connect();

            is = urlConnection.getInputStream();
            fos = new FileOutputStream("day10\\beauty3.jpg");

            byte[] buffer = new byte[1024];
            int len;
            while((len = is.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }

            System.out.println("下载完成");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(urlConnection != null){
                urlConnection.disconnect();
            }
        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值