JAVA学习11——网络编程

1.网络编程

1.1 软件结构

C/S结构:全称为Client/Server结构,是指客户端和服务器结构,常见程序有QQ、迅雷等软件。
B/S结构:Browser/Server结构,是指浏览器和服务器结构,常见浏览器有谷歌和火狐等。
在这里插入图片描述

两种架构各有优势,都需要网络的支持,网络编程,就是在一定的协议下,实现两台计算机的通信的程序。

1.2网络通信协议

网络通信协议,即通过计算机网络可以使多态计算机实现连接,位于同一个网络中的计算机在进行连接和通信时,需要遵守一定的规则,就好比在道路中行驶的汽车,一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。

TCP/IP协议:传输控制协议/因特网互联协议(Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连接因特网,以及数据如何在它们之间传输的标准,它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模式,每一层都呼叫它的下一层提供的协议来完成自己的需求。
在这里插入图片描述
链路层,是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
网络层:网络层是整个TCP/IP协议的核心,主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
运输层:主要使用网络编程进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。
应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。

1.3协议分类

java.net包提供了两种常见的网络协议的支持,UDP和TCP:
(1)UDP:用户数据协议(User Datagram Protocol),UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。即当一台PC向另一台PC发送数据时,发送端不会确认接收端是否存在,就发出数据,同样接收端在受到数据时,也不会像发送端反馈是否受到数据。由于使用UDP协议消耗资源小,通信效率高,所以通常会用于音频、视频和普通数据的传输,例如视频会议一般都使用UDP协议,因为这种情况偶尔丢失一两个数据包,也不会对接收结果带来很大影响。但在使用UDP协议传输数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时,不建议用UDP协议。其主要特点是,数据被限制在64kb以内,超出就不能发送了。数据报(Datagram),是网络传输的基本单位。

(2)TCP:传输控制协议(Transmission Control Protocol),是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,提供了两个PC间可靠无差错的数据传输。 在TCP连接中,必须要明确客户端与服务器端,由客户端向服务器端发出连接请求,每次连接的创建都需要经过“三次握手”,从而保证连接的可靠性。
具体三次握手过程,是在发送数据的准备阶段,客户端和服务器之间的三次交互,具体如下:
step1:第一次握手,客户端向服务器端发送连接请求,等待服务器确认;
step2:服务器向客户端回送一个响应,通知客户端接收到了连接请求;
step3:客户端再向服务器发送确认信息,确认连接,整个交互过程如下图所示。
在这里插入图片描述
三次握手后,客户端和服务器就可以开始进行数据传输了,由于它可靠且安全,所以广泛应用再下载文件、浏览网页等。

1.4网络编程三要素

网络编程三要素:协议、IP地址、端口号。
(1)协议。

(2)IP地址,互联网协议地址(Internet Protocol Address),俗称IP,IP地址用来给一个网络中的计算机设备做唯一的编号,例如我们把一个“个人电脑”比作电话,则IP地址相当于电话号码。IP地址分类,分为IPv4和IPv6。IPv4是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d形式,例如192.168.65.100,其中abcd都是0到255之间的十进制数,最多可以表示42亿个。由于互联网蓬勃发展,IP地址的需求量越来越大,但是网络地址资源有限,使得IP的分配越发紧张,为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网址,解决了网络地址资源数量不够的问题。

(3)端口号
端口号的作用是,保证数据能准确无误的发送到对方计算机的软件上。当我们使用网络软件一打开,那么操作系统就会为网络软件分配一个随机的端口号,或者网络软件在打开的时候,和系统要指定的端口号。端口号由两个字节组成,取值范围在0——65535之间。

注意:1024前的端口号一般是不能使用的,已经被系统分配给已知的网络软件了,网络软件的端口号是不能重复的。

常用端口号:80端口是网络端口;mysql数据库是3306端口;oracle是1521端口;Tomcat服务器是8080端口;QQ是5000端口;FeiQ是6000端口;MSN是7000端口。

当我们访问网址时,比如www.baidu.com默认端口号为80,所以可以访问www.baidu.com:80,但是无法访问www.baidu.com:70。

1.5 TCP通信

Client客户端和Server服务端通信的特点:服务端程序,需要实现启动,等待客户端的连接;客户端主动连接服务器端,连接成功了才能通信,服务端不可以主动连接客户端。

TCP通信,是面向连接的通信,客户端和服务器必须经过3次握手,建立逻辑连接,才能通信,保证安全。

通信的过程大概是,服务器端先启动,服务器端不会主动的请求客户端,必须使用客户端请求服务端,客户端和服务端就会建立一个逻辑连接,而这个连接中包含一个对象,这个对象是IO流对象,客户端和服务器端就可以使用IO对象进行通信,通信的数据不仅仅是字符,因此IO对象是字节流。

客户端和服务器进行一个数据交互,看似需要4个IO流对象,两个InputStream,两个OutputStream,但实际上服务器端是没有IO流的,见下图。
在这里插入图片描述

在多个客户端和服务器进行交互时,服务器要明确和哪个客户端进行交互,在服务器端都有一个方法叫accept,用来获取发出请求的客户端对象。同时,服务器端是使用客户端Socket中提供的IO流,进行与客户端的交互,服务器使用客户端的字节输入流读取客户端发送的数据,服务器也使用客户端的字节输出流给客户端回写数据。总而言之,就是服务器使用客户端的流和客户端进行交互。

1.6 TCP通信代码实现

客户端:java.net.Socket类表示,创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
服务端:java.net.ServerSocket类表示,创建ServerSocket对象,相当于开启了一个服务,并等待客户端的连接。

下面是客户端内容:

TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据;
表示客户端的类:
	java.net.Socket:此类实现客户端套接字,套接字是两台机器间通信的端点。套接字包含了IP地址和端口号的网络单位。
	构造方法:
		Socket(String host, int port) 创建一个流套接字,并将其连接到指定主机上的指定端口号。
	参数
		String host:服务器的IP地址,或者服务器主机的名称;
		int port:服务器的端口号;
	成员方法:
		OutputStream getOutputStream():返回此套接字的输出流;
		InputStream getInputStream():返回此套接字的输入流;
		void close() 关闭此套接字

实现步骤:
1.创建一个客户端对象Socket,构造方法指定服务器的IP地址和端口号;
2.使用Socket对象中的方法,getOutputStream()获取网络字节流OutputStream对象;
3.使用网络字节输出流OutputStream对象中的write方法,给服务器发送数据;
4.使用Socket对象中的方法getInputStream对象中的方法获取网络字节输入流InputStream对象;
5.使用网络字节输入流InputStream对象的方法read,读取服务器回写的数据;
6.释放资源(socket);

注意:
1.客户端和服务器进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象;
2.当我们创建客户端对象Socket的时候,就回去请求服务器和服务器经过3次握手建立连接通路,这时如果服务器没有启动,那么就会抛出异常,如果服务器已经启动,那么就可以进行交互了。

Socket socket = new Socket("127.0.0.1",8888);
OutputStream os = socket.getOutputStream();
os.write("你好服务器".getBytes());

下面是服务器端:

TCP通信的服务端,是接收客户端的请求,读取客户端发送的数据,给客户端回写数据。
表示服务器的类:
	java.net.ServerSocket,此类实现服务器套接字。
构造方法:
	ServerSocket(int port) 创建绑定到特定端口的服务器套接字。

服务器端必须明确知道是哪个客户端请求的服务器,因此需要用accept方法获取请求的客户端对象Socket。
成员方法:
	Socket accept():侦听并接收到此套接字连接。

服务器的实现步骤:
1.创建服务器ServerSocket对象,和系统要指定的端口号;
2.使用ServerSocket对象中的方法accept,获取到请求的客户端的Socket对象;
3.使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象;
4.使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据;
5.使用Sokcet对象中的方法getOutputStream()获取网络字节输出流OutputStream对象。
6.使用网络字节输出流OutputStream对象的方法write,给客户端回写数据;
7.释放资源(Socket,ServerSocket)

ServerSocket server = new ServerSocket(8888);
Socket socket = server.accept();
InputStream is = socket.getInputStreaam();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes,0,len));
OutputStream os = socket.getoutputStream();
os.write(“受到谢谢”.getBytes());
socket.close();
server.close();

1.7 综合案例——文件上传案例

分析:
(1)客户端输入流,从硬盘读取文件数据到程序中;
(2)客户端输出流,写出文件数据到服务端;
(3)服务端输出流,读取文件数据到服务端程序;
(4)服务端输出流,写出文件数据到服务器硬盘中;
在这里插入图片描述

注意:
客户端和服务器和本地硬盘进行读写,需要使用自己创建的字节流对象(本地流);客户端和服务器之间进行读写,必须使用Socket中提供的字节流对象(网络流)。

代码实现,客户端

public class TCPClient {
	public static void main(String[] args) throws IOException {
		//1.创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源;
		FileInputStream fis = new FileInputStream("C:\\1.jpg");
		//2.创建一个客户端Socket对象,构造方法中绑定服务器的IP地址和端口号
		Socket socket = new Socket("127.0.0.1",8888);
		//3.使用Socket中的方法getOutputStream,获取网络字节输出流OutputStream对象
		OutputStream os = socket.getOutputStream();
		//使用本地字节输入流FileInputStream对象中的方法read,读取本地文件
		int len = 0;
		byte[] bytes = new byte[1024];
		while(len = fis.read(bytes))!=-1) {
		//5.使用网络字节输出流OutputStream对象中的方法write,把读取到的文件上传到服务器
			os.write(bytes,0,len);//当到文件末尾时,读到文件结束符,并不会使用网络字节输出流把-1上传到服务器,因此服务器自然接收不到文件结束符,因此需要一个解决办法
		}
		/*
			解决:上传完文件,给服务器写一个结束标记
			void shutdownOutput()禁用此套接字的输出流
			对于TCP套接字,任何以前写入的数据都将被发送,并且后面跟上TCP的正常连接终止序列
		 */
		 socket.shutdownOutput();
		//6.使用Socket中的方法getInputStream,获取网络字节输入流InputStream对象 
		InputSteam is = socket.getInputStream();
		//7.使用网络字节输入流InputStream对象中的方法read读取服务回写的数据
		while(len = is.read(bytes)) != -1) {
			System.out.println(new String(bytes,0,len));
		}
		//8.释放资源
		fis.close();
		socket.close();
	}
}

代码实现,服务端

public class TCPServer {
	public static void main(String[] args) throws IOException {
		//1.创建一个服务器ServerSocket对象,和系统要指定的端口号
		ServerSocket server = new ServerSocket(8888);
		//2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
		Socket socket = server.accept();
		//3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
		InputStream is = socket.getInputStream();
		//4.判断文件夹是否存在,不存在要创建
		File file = new File("d:\\a.txt");
		if(!file.exists()) {
			file.mkdirs();
		}
		//5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
		FileOutputStream fos = new FileOutputStream(file + "\\1.jpg");
		//6.使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件;
		int len = 0;
		byte[] bytes = byte[1024];
		while((len = is.read(bytes))!=-1) { //读到文件结束符,才会返回-1,
			7.使用本地字节流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
			fos.write(bytes,0,len);
		}
		//8.释放资源
		fis.close();
		socket.close();
	}
}

1.8 综合案例提升——文件上传优化(文件命名,服务器保持监听,多线程处理多个客户端)

只用修改服务器端代码,代码:

public class TCPServer {
	public static void main(String[] args) throws IOException {
		//1.创建一个服务器ServerSocket对象,和系统要指定的端口号
		ServerSocket server = new ServerSocket(8888);
		//2.使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象

		while(true) { //服务器保持监听
			Socket socket = server.accept();
			/*
				使用多线程技术,提高程序的效率,有一个客户端上传文件就开启一个线程;
			*/
			new Thread(new Runnable() {
				//完成文件的上传
				@Override
				//注意:Runnable接口中的run抽象方法没有抛出异常,所以子类也不能抛出异常,所以只能用try catch     
				public void run() {
				try {
					//3.使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
					InputStream is = socket.getInputStream();
					//4.判断文件夹是否存在,不存在要创建
					File file = new File("d:\\a.txt");
					if(!file.exists()) {
						file.mkdirs();
					}
			
					String fileName = "itcast“ + System.currentTimeMills()+new Random().nextInt(999999)+".jpg";
					//5.创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
					FileOutputStream fos = new FileOutputStream(file + fileName);
					//6.使用网络字节输入流InputStream对象中的方法read,读取客户端上传的文件;
					int len = 0;
					byte[] bytes = byte[1024];
					while((len = is.read(bytes))!=-1) { //读到文件结束符,才会返回-1,
						7.使用本地字节流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
						fos.write(bytes,0,len);
					}
					//8.释放资源
					fis.close();
					socket.close();
				} catch (IOException e) {
					System.out.println(e);
				}
			}).start();
		}
	}
}

1.9 模拟BS服务器

在这里插入图片描述

 public class TCPServer {
 	public static void main(String[] args) throws IOException {
 		//创建一个服务器ServerSocket,和系统要指定的端口号
 		ServerSocket  server = new ServerSocket(8080);
 		/*
			注意:浏览器解析服务器回写的html页面中,页面如果有图片,那么浏览器就会单独开启一个线程,读取该服务器图片。
			因此,要让服务器已知处于监听状态,客户端请求一次,服务器回写一次
		*/
		while(true) {
			//使用accept方法获取到请求的客户端对象(浏览器)
	 		Socket socekt = server.accept();

			new Thread(new Runnable() {
				try {
					//使用Socket对象中的方法,getInputStream,获取到网络字节输入流InputStream对象
			 		InputStream is = socket.getInputStream();
			 		//把is网络字节输入流对象,转换为字符缓冲输入流
			 		BufferedReader br = new BufferedReader(new InputStreamReader(is));
			 		//把客户端请求信息第一行读出
			 		String line = br.readLine(); //为什么用BufferedReader,因为效率高,且有readLine这个函数,更方便 
			 		System.out.println(line);
			 		//只读取信息中间部分,再把该部分中前面的/去掉,进行截取
			 		String[] arr = line.split(" ");
			 		String htmlpath = arr[1].substring(1);
			
					//创建一个本地字节输入流,构造方法中绑定要读取的html路径
					FileInputStream fis = new FileInputStream(htmlpath);
					OutputStream os = socket.getOutputStream();
			
					//写入HTTP协议响应头,固定写法
					os.write("HTTP/1.1 200 OK\r\n".getBytes());
					os.write("Content-Type:text/html\r\n".getBytes());
					//必须要写入空行,否则浏览器不解析
					os.write("\r\n".getBytes());
			
					//一读一写,赋值文件,把服务器读取的html文件写回到客户端
					int len = 0;
					byte[] bytes = new byte[1024];
					while((len = fis.read(bytes))!=-1) {
						os.write(bytes,0,len);
					}
					fis.close();
					socket.close();
				} catch(IOException e) {
					e.printStackTrace();
				}
			}).start();
		}
 		
 	}
 }
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页