活动地址:CSDN21天学习挑战赛
目录
一、网络编程
1.1 网络通信结构
1)C/S结构 :即 Client/Server 结构,是指客户端和服务器结构。常见有QQ、腾讯视频等软件。
2)B/S结构 :即 Browser/Server 结构,是指浏览器和服务器结构。常见有谷歌、火狐浏览器等。
总结:无论哪种架构,都要在联网状态下可运行。网络编程,就是在一定的协议下,实现两台计算机的通信的程序。
1.2 网络通信协议
TCP/IP协议: 传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。
- 定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。
- 采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
TCP/IP四层概念模型如下:
应用层 | HTTP、FTP、TFTP、SMTP、DNS等 |
传输层 | TCP、UDP |
网络层 | ICMP、IGMP、IP、ARP等 |
数据链路层 | 由底层网络定义的协议 |
物理层 |
具体协议如下:
HTTP: 超文本传输协议(Hyper Text Transfer Protocol)
FTP: 文件传输协议(File Transfer Protocol)
TFTP: 简单网络传输协议(Trivial File Transfer Protocol)
SMTP: 简单电子邮件传输协议(Simple Mail Transfer Protocol)
DNS: 域名系统(Domain Name System)
TCP: 传输控制协议(Transmission Control Protocol)
UDP: 用户数据报协议(User Datagram Protocol)
ICMP: Internet控制报文协议(Internet Control Message Protocol)
IGMP: Internet组管理协议(Internet Group Management Protocol)
IP: 网络互连协议(Internet Protocol)
ARP: 地址解析协议(Address Resolution Protocol)
1.3 协议分类
java.net 包中提供了两种常见的网络协议的支持:
1)TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。
2)UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等。
1.4 网络编程三要素
- IP地址:可以唯一标识网络中的设备
- 端口号:可以唯一标识设备中的进程(应用程序)
- 协议:双方通信的规则,上述介绍的协议都属于网络通信协议
1)IP地址
查看本机的IP地址:可在 命令行窗口 输入:
ipconfig
检查网络是否连通,可在 命令行窗口 输入:
ping IP地址
如:
2)端口号
不同的进程有不同的端口号,用以区分软件,规定端口号为用两个字节表示的整数,它的取值范围是0~65535。
- 公共端口:0~1023,如:HTTP:80;
HTTPS:443;
FTP:21;用于一些知名的网络服务和应用。
- 程序注册端口:1024~49151,用以分配给用户和普通的应用程序。
如:Tomcat:8080;MySQL:3306;Oracle:1521。
- 动态及私有端口:49152~65535
查看本机的所有端口,在 命令行窗口 输入
netstat -ano
查看特定的端口(如:查看端口号为5900的端口)
netstat -ano | findstr "5900"
二、TCP通信程序
2.1 概述
1)两端通信时步骤:
①服务端程序,需要事先启动,等待客户端的连接。
② 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
2)在Java中,提供了两个类用于实现TCP通信程序:
①客户端: java.net.Socket 类表示。创建 Socket 对象,向服务端发出连接请求,服务端响应请求,两者建
立连接开始通信。
②服务端: java.net.ServerSocket 类表示。创建 ServerSocket 对象,相当于开启一个服务,并等待客户端
的连接。
2.2 Socket 类
Socket 类:实现了客户端套接字,套接字指的是两台设备之间通讯的端点。
构造方法:public Socket(String host, int port) :创建套接字对象(地址为host)并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址(一旦使用回送地址发送数据,立即返回,不进行任何网络传输)。
举例:
Socket client = new Socket("127.0.0.1", 6666);
常用成员方法:
public InputStream getInputStream() : 返回此套接字的输入流
public OutputStream getOutputStream() : 返回此套接字的输出流。
public void close() :关闭此套接字
public void shutdownOutput() : 禁用此套接字的输出流。
2.3 ServerSocket类
ServerSocket 类:实现了服务器套接字,该对象等待通过网络的请求。
构造方法:public ServerSocket(int port) :在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上。
举例:
ServerSocket server = new ServerSocket(6666);
常用成员方法:
public Socket accept() :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法
会一直阻塞直到建立连接。
2.4 TCP通信流程
1. 【服务端】启动,创建ServerSocket对象,等待连接。
2. 【客户端】启动,创建Socket对象,请求连接。
3. 【服务端】接收连接,调用accept方法,并返回一个Socket对象。
4. 【客户端】Socket对象,获取OutputStream,向服务端写出数据。
5. 【服务端】Scoket对象,获取InputStream,读取客户端发送的数据。
到此,客户端向服务端发送数据成功。
自此,服务端向客户端回写数据。
6. 【服务端】Socket对象,获取OutputStream,向客户端回写数据。
7. 【客户端】Scoket对象,获取InputStream,解析回写数据。
8. 【客户端】释放资源,断开连接。
2.5 TCP通信示例
【举例1】客户端向服务器发送数据 “欢迎学习Java”
客户端的实现:TCPClientDemo1.java
package Net;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
//实现TCP发生消息
//客户端
public class TCPClientDemo1 {
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
try {
// 1.要知道服务器的地址,端口号
InetAddress serverIP = InetAddress.getByName("127.0.0.1");
int port = 999;
//2.创建一个socket连接,确定连接到端口
socket = new Socket(serverIP,port);
//3.发送消息IO流
os = socket.getOutputStream();
//写数据
os.write("欢迎学习Java".getBytes());
} catch (Exception e) {
e.printStackTrace();
}finally {
if(os != null) {
try {
//关闭资源
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务端的实现:TCPServerDemo1.java
package Net;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
//服务端
public class TCPServerDemo1 {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket =null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
// 1.要有一个服务器地址(这里设置为localhost/9999)
//创建ServerSocket对象,绑定端口,开始等待连接
serverSocket = new ServerSocket(999);
//2.accept方法 等待客户端连接过来,返回socket对象
socket = serverSocket.accept();
//3.读取客户端的消息(输入流)
is = socket.getInputStream();
//管道流读取数据
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024]; //创建字节数组
int len;
while((len=is.read(buffer)) != -1) {
baos.write(buffer,0,len);//打印数据流信息
}
System.out.println(baos.toByteArray());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
//关闭资源(先定义的对象后关闭)
if(baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
【举例2】客户端向服务器传输文件(以图片为例)
文件上传的流程:
1. 【客户端】输入流,从硬盘读取文件数据到程序中。
2. 【客户端】输出流,写出文件数据到服务端。
3. 【服务端】输入流,读取文件数据到服务端程序。
4. 【服务端】输出流,写出文件数据到服务器硬盘中。
客户端的实现:TCPClientDemo2.java
package Net;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
//实现TCP发送文件
public class TCPClientDemo2 {
public static void main(String[] args) throws Exception {
// 1.创建一个Socket连接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),888);
//2.创建一个输出流
OutputStream os = socket.getOutputStream();
//3.文件流:读取文件
//注意!!!根据自己存储图片的文件路径作为输入!!!
FileInputStream fis = new FileInputStream(new File("C:\\Users\\eclipse-workspace\\QQ图片.png"));
//4.写出文件
byte[] buffer = new byte[1024];
int len;
while((len=fis.read(buffer)) != -1) {
os.write(buffer,0,len);
}
//通知服务器,我已经结束了
socket.shutdownOutput();//我已经传输完了
//确定服务器接收完毕,才能断开连接
InputStream inputStream = socket.getInputStream();
//String byte[]
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer2 = new byte[2014];
int len2;
while((len2=fis.read(buffer2)) != -1) {
baos.write(buffer2,0,len2);
}
//5.关闭资源
baos.close();
inputStream.close();
fis.close();
os.close();
socket.close();
}
}
服务端的实现:TCPServerDemo2.java
package Net;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServerDemo2 {
public static void main(String[] args) throws Exception {
// 1.创建服务端ServerSocket
ServerSocket serverSocket = new ServerSocket(888);
//2.监听客户端的连接
Socket socket = serverSocket.accept(); //阻塞式监听,会一直等待客户端连接
//3.获取输入流
InputStream is = socket.getInputStream();
//4.文件输出
FileOutputStream fos = new FileOutputStream(new File("receive.png"));//保存在本项目工作空间
//读写数据
byte[] buffer = new byte[1024];
int len;
while((len=is.read(buffer)) != -1) {
fos.write(buffer,0,len);
}
//通知客户端,接收完毕
OutputStream os = socket.getOutputStream();
os.write("我接受完毕了,你可以断开了".getBytes());
//关闭资源
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}
运行后在工作路径下保存了“receive.png”
2.6 文件上传的优化分析
1)文件名称写死的问题
在服务端,保存文件的名称如果写死,那么最终导致服务器硬盘,只会保留一个文件,使用系统时间优
化,保证文件名称唯一,代码如下:
//4.文件输出
FileOutputStream fos = new FileOutputStream(new File("receive"+System.currentTimeMillis()+".png"));
2) 循环接收的问题
在服务端,保存一个文件就关闭了,之后的用户无法再上传,这是不符合实际的,使用循环改进,可以不断
的接收不同用户的文件,代码如下:
// 每次接收新的连接,创建一个Socket
while(true){
Socket accept = serverSocket.accept();
......
}
3)效率问题
在服务端,接收大文件时,可能耗费几秒钟的时间,此时不能接收其他用户上传,所以,使用多线程技术优
化,代码如下:
while(true){
Socket accept = serverSocket.accept();
// accept 交给子线程处理.
new Thread(() ‐> {
......
InputStream bis = accept.getInputStream();
......
}).start();
}
综上,优化后的服务端的实现:
package Net;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServerDemo2_better {
public static void main(String[] args) throws Exception {
// 1.创建服务
ServerSocket serverSocket = new ServerSocket(888);
//2.循环监听客户端的连接
while(true) {
Socket socket = serverSocket.accept(); //阻塞式监听,会一直等待客户端连接
//socket对象交给子线程处理,进行读写操作 Runnable接口中,只有一个run方法,使用lambda表达式简化格式
new Thread(()-> {
try {
//3.获取输入流
InputStream is = socket.getInputStream();
//4.文件输出
FileOutputStream fos = new FileOutputStream(new File("receive"+System.currentTimeMillis()+".png"));
byte[] buffer = new byte[1024];
int len;
while((len=is.read(buffer)) != -1) {
fos.write(buffer,0,len);
}
//通知客户端,接收完毕
OutputStream os = socket.getOutputStream();
os.write("我接受完毕了,你可以断开了".getBytes());
//关闭资源
fos.close();
is.close();
socket.close();
serverSocket.close();
}catch(IOException e) {
e.printStackTrace();
}
}).start();
}
}
}