【Java基础】11.网络编程
11.1 网络编程内容
11.1.1 软件结构
C/S结构:用户/服务器结构。例如QQ等
B/S结构:浏览器/服务器结构。
11.1.2 网络通信协议
- 网络通信协议:通过计算机网络使多台计算机实现连接,连接通信时需要遵守一定的规则。
- TCP/IP协议:传输控制协议/因特网互联协议,是最基本最广泛的协议。采用四层分层模型。
11.1.3 协议分类
- UDP: User Datagram Protocol 用户数据报协议。【无连接通信】消耗资源少,通信效率高。不能保证数据完整性,在传输重要数据时不建议使用UDP协议。举例:广播电台,QQ的聊天、视频等功能。发送的数据限制在64kb以内。
- TCP: Transmission Control Protocol 传输控制协议。是【面向连接】的通信协议,是无差错的可靠数据传输。
- 三次握手
- 第一次握手:客户端向服务器发出请求,等待确认
- 第二次握手:服务器端向客户端发送响应
- 第三次握手:客户端再次向服务器发送确认请求,确认后完成连接
11.1.4 网络编程三要素
1.协议
网络通信必须遵守的规则
2.IP地址
相当于电话号码
IPv4: 4的意思是4个字节,故总共32位二进制数。常用4段10进制表示法
IPv6: 由于互联网的发展,IP地址需求量越来越大,为了扩大地址空间,采用128位地址长度。每16个字节一组,8段16进制表示。
特殊的IP地址:
本机IP地址:127.0.0.1 或者 localhost
3.端口号
端口号是一个逻辑端口,无法直接看到,可以使用软件查看。
当网络软件打开时,OS会为它分配一个随机端口号。
或者软件打开时向OS获取指定端口号
端口号由两个字节组成:0~65535之间
【注意】
- 1024之前的端口号不能使用,已经被OS分配掉了
- 网络软件的端口号不能重复
【打比方】IP地址是你的收货地址,端口号就是你家的门牌号。
【常用端口号】
- 80端口 网络端口
- 数据库 mysql : 3306 oracle :1521
- Tomcat服务器:8080
11.2 TCP通信程序
11.2.1 概述
TCP通信是【面向连接】的通信,客户端和服务器端必须经过3次握手才能建立逻辑连接,建立通信。
两端通信时的步骤:
- 服务器端程序:先启动,但不会主动请求客户端。
- 客户端程序:先向客户端发出请求,与服务器端建立逻辑连接,这个连接包含一个IO对象。
- 两端利用IO对象进行通信,数据不仅是字符,所以IO对象是字节流对象
【两端进行一次数据交互】需要【4个IO流对象】写读写读
服务器端必须明确两件事
- 多客户端同时与服务器交互,服务器必须明确和谁在交互。(accept方法)
- 多客户端与服务器进行交互,需要多个IO流对象。但服务器没有IO流,但可以利用server.accept方法抓取客户端的IO流对象,从而跟对应客户端完成交互。【抓别人的,用别人的】
11.2.2 Socket类【客户端】
此类实现客户端套接字。也就是表示客户端的类。
TCP通信客户端:向服务器发送连接请求,给服务器发送数据,读取服务器回写的数据。
【套接字】意思就是包含了IP地址和端口号的网络单位
构造方法:
public Socket(String host,int port)
host:服务器名称/服务器IP地址 port:端口号
成员方法:
public InputStream getInputStream()//返回此套接字的输入流。
public OutputStream getOutputStream()//返回此套接字的输出流。
public void close()//关闭该套接字
实现步骤:
- 创建客户端对象,构造方法绑定服务器和端口号
- 使用socket对象方法getOutputStream获取网络字节输出流对象
- 使用字节输出流对象方法write,给服务器发送数据
- 使用Socket对象中方法gIS获取网络字节输入流对象
- 使用字节输入流对象方法read,读取服务器回写的数据
- 释放资源
【注意】
- 交互时,必须使用Socket提供的网络流,不能自己创建
- 当创建客户端对象时,会主动进行三次握手。若服务器此时没有启动,会抛出异常。
11.2.3 ServerSocket类【服务器】
TCP通信的服务器端:接受客户端的请求,读取客户端发送的数据,给客户端回写数据。
表示服务器的类:ServerSocket
构造方法:
public ServerSocket(int port)//创建绑定到特定端口的服务器套接字
port:端口号
服务器端必须明确:是哪个客户端请求的服务器,所以可以使用accept方法来获取给自己发送请求的客户端对象。
成员方法:
public Socket accept()//侦听并接受到此套接字的连接
服务器的实现步骤
- 创建服务器对象ServerSocket,向系统要指定端口号
- 使用对象方法accept,获取发送请求的客户端对象
- 使用socket对象方法getInputStream获取网络字节输入流对象
- 使用字节输入流对象方法read,读取客户端写的数据
- 使用Socket对象中方法gOS获取网络字节输出流对象
- 使用字节输入流对象方法writ e,写下回写给客户端的数据
- 释放资源 Socket,ServerSocket
11.3 文件上传案例
11.3.1 需求分析
客户端
原理:客户端读取本地文件,把文件上传到服务器,服务器把上传的文件保存到服务器的硬盘上。
- 客户端使用本地的字节输入流,读取要上传的文件
- 客户端使用网络字节输出流,把读取到的文件上传到服务器
- 服务器使用网络节输入流,读取客户端上传的文件
- 服务器使用本地字节输出流,把读取到的文件保存到服务器的硬盘上
- 服务器使用网络字节输出流,给客户端回写一个“上传成功”
- 客户端使用网络字节输入流,读取服务器回写的数据
- 释放资源
【注意】
- 客户端和服务器和本地硬盘进行读写,需要使用自己创建的字节流对象(本地流)
- 客户端和服务器之间进行读写,必须使用Socket中提供的字节流对象(网络流)
文件上传的底层原理:就是文件的复制。需要明确【数据源】和【数据目的地】
实现步骤:
- 创建一个本地字节输入流FIS对象,构造方法中绑定要读取的数据源
- 创建一个客户端Socket对象,构造方法中绑定服务器IP地址和端口号
- 使用Socket中的方法gOS,获取网络字节输出流对象
- 使用本地字节输入流FIS对象方法read,读取本地文件
- 使用网络字节输出流OS对象中的方法write,把读取的文件上传到服务器
- 使用Socket中方法gIS,获取网络字节输入流IS对象
- 使用网络字节输入流IS对象中方法read读取服务器回写的数据
- 释放资源FIS 和Socket
服务器端
文件上传案例的服务器端:读取客户端上传文件,保存到服务器硬盘,给客户端回写"上传成功
数据源:客户端上传的文件
目的地:服务器的硬盘e:\gc.txt
【实现步骤】
- 创建一个服务器SS对象,指定一个端口号
- 使用SS对象中accept方法,抓取客户端对象
- 使用Socket对象方法gIS方法获取网络字节输入流IS对象
- 判断存取目的地路径是否存在
- 创建一个本地字节输出流对象,构造方法中指定输出目的地
- 使用网络字节输入流IS对象方法read,读取客户端上传好的文件
- 使用本地字节输出流FOS对象方法write,写到硬盘上
- 使用Socket对象方法gOS获取到网络字节输出流OS对象
- 使用网络字节输出流对象OS对象方法write,给客户端回写“上传成功”
- 释放资源(FOS,socket,SS)
11.3.2 代码实现
【客户端】
package cn.itcast.day18.demo01;
import java.io.*;
import java.net.Socket;
/*
客户端实现步骤:
1. 创建一个本地字节输入流FIS对象,构造方法中绑定要读取的数据源
2. 创建一个客户端Socket对象,构造方法中绑定服务器IP地址和端口号
3. 使用Socket中的方法gOS,获取网络字节输出流对象
4. 使用本地字节输入流FIS对象方法read,读取本地文件
5. 使用网络字节输出流OS对象中的方法write,把读取的文件上传到服务器
6. 使用Socket中方法gIS,获取网络字节输入流IS对象
7. 使用网络字节输入流IS对象中方法read读取服务器回写的数据
8. 释放资源FIS 和Socket
*/
public class Demo01FileUploadClient {
public static void main(String[] args) throws IOException {
//1
FileInputStream fis = new FileInputStream("D:\\GBK.txt");
//2
Socket socket = new Socket("127.0.0.1",8888);
//3
OutputStream oS = socket.getOutputStream();
//4
int len=0;
byte[] bytes = new byte[1024];
while ((len=fis.read(bytes))!=-1){
//5
oS.write(bytes,0,len);
}
//6
InputStream iS = socket.getInputStream();
//7
while ((len=fis.read(bytes))!=-1){
//5
iS.read(bytes,0,len);
}
/*
上传完文件,给服务器写一个结束标记
shutdownOutput() 禁用此套接字的输出流
*/
socket.shutdownOutput();
//8
fis.close();
socket.close();
}
}
【服务器端】
package cn.itcast.day18.demo01;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
文件上传案例的服务器端:读取客户端上传文件,保存到服务器硬盘,给客户端回写"上传成功
【实现步骤】
1. 创建一个服务器SS对象,指定一个端口号
2. 使用SS对象中accept方法,抓取客户端对象
3. 使用Socket对象方法gIS方法获取网络字节输入流IS对象
4. 判断存取目的地路径是否存在
5. 创建一个本地字节输出流对象,构造方法中指定输出目的地
6. 使用网络字节输入流IS对象方法read,读取客户端上传好的文件
7. 使用本地字节输出流FOS对象方法write,写到硬盘上
8. 使用Socket对象方法gOS获取到网络字节输出流OS对象
9. 使用网络字节输出流对象OS对象方法write,给客户端回写“上传成功”
10. 释放资源(FOS,socket,SS)
*/
public class Demo02FileUploadServer {
public static void main(String[] args) throws IOException {
//1
ServerSocket server = new ServerSocket(8888);
//2
Socket socket = server.accept();
//3
InputStream iS = socket.getInputStream();
//4
File file = new File("e:\\upload");
if (!file.exists()){
file.mkdirs();
}
//5
FileOutputStream fOS = new FileOutputStream(file + "\\gc.txt");//
//6
int len=0;
byte[] bytes =new byte[1024];
while ((len=iS.read(bytes))!=-1){
//7
fOS.write(bytes,0,len);
}
//89
OutputStream oS = socket.getOutputStream();
oS.write("上传成功".getBytes());
//10
fOS.close();
socket.close();
server.close();
}
}
11.3.3 代码优化
自定义一个文件命名规则:防止同名文件被覆盖
规则:域名+毫秒值+随机数
String fileName = "itcast"+System.currentTimeMillis()+ new Random().nextInt(1000000)+".txt";
FileOutputStream fOS = new FileOutputStream(file +"\\"+fileName);
让服务器一直处于监听状态
加入while死循环,不需要再关闭服务器close
为提高效率,死循环中捕捉到客户端后开启多线程
用try…catch来处理异常
11.4 B/S结构服务器
客户端:谷歌浏览器:http://127.0.0.1:8080/day18/web/index.html
服务器代码:
//创建一个服务器,指定端口号
ServerSocket server = new ServerSocket(8080);
//抓取客户端对象
Socket socket = server.accept();
//socket对象gIS获取网络字节输入流
InputStream iS = socket.getInputStream();
//使用网络字节输入流对象方法read读取客户端请求
byte[] bytes = new byte[1024];
int len =0;
while ((len=iS.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
读取客户端的请求信息如下:
GET /day18/web/index.html HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
接下来,服务器要给客户端回写一个信息,回写一个html页面
我们需要读取index.html文件,必须知道这个文件的地址。
而这个地址实际上就是我们读取客户端的请求信息中的第一行
GET /day18/web/index.html HTTP/1.1
我们可以使用BufferedReader中的方法:readLine来读取这第一行
new BufferedReader(new InputStreamReader(iS));//把网络字节输入流转换为字符缓冲输入流
我们只需要文件地址,可以使用String类中的方法split来切割字符串
切割标识为空格 split(" ")
那么array[1] 就是 /day18/web/index.html
再使用String类方法subString(1) ,获取html文件路径【去掉首个/字符】
day18/web/index.html
//写入HTTP协议响应头,固定写法
/* HTTP协议响应段代码 */
//写入空行,否则浏览器不解析
oS.write("\r\n".getBytes());
服务器将使用网络字节输出流把读取的文件,写到客户端(浏览器)显示