目录
一、网络编程基础
1.1 软件结构
- C/S结构:全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件。
- B/S结构:全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等。
两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机的通信的程序。
1.2 网络通信协议
- 网络通信协议:通信协议是对计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信。这就
好比在道路中行驶的汽车一定要遵守交通规则一样,协议中对数据的传输格式、传输速率、传输步骤等做了
统一规定,通信双方必须同时遵守,最终完成数据交换。 - TCP/IP协议:传输控制协议/因特网互联协议( Transmission Control Protocol/Internet Protocol),是Internet最基本、最广泛的协议。它定义了计算机如何连入因特网,以及数据如何在它们之间传输的标准。它的内部包含一系列的用于处理数据通信的协议,并采用了4层的分层模型,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
1.3 协议分类
java.net
包中提供了两种常见的网络协议的支持:
- TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的可靠的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
- 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
- 第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
- 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
- 第三次握手,客户端再次向服务器端发送确认信息,确认连接。
- 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可
以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等。
特点:
- 传输数据之前,两端(客户端和服务端)首先要建立连接。
- 提供的是可靠的数据传输,传输速度有限,端到端的数据传输。
使用场景:传输的数据要求可靠。
- UDP:用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的不保证可靠性的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个
数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等。
特点:
- 无连接的,直接发送数据,不管接收方是否收到的,也不确保收到的数据是否完全。
- 不可靠,传输速度很快,支持一对一、一对多、多对多多的数据传输。
使用场景:对传输速度要求高,对结果不可靠影响不大的。、
1.4 网络编程三要素
- 协议
- 协议:计算机网络通信必须遵守的规则。
- IP地址
- IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。
特殊的IP地址
本机IP地址: 127.0.0.1
、 localhost
。
- 端口号
网络的通信,本质上是两个进程(应用程序)的通信。IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
- 端口号:用两个字节表示的整数,它的取值范围是
0~65535
。其中,0~1023
之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会
导致当前程序启动失败。
利用协议 + IP地址 + 端口号
三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
二、TCP通信程序
2.1 概述
TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。
两端通信时步骤:
- 服务端程序,需要事先启动,等待客户端的连接。
- 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
在Java中,提供了两个类用于实现TCP通信程序:
- 客户端:
java.net.Socket
类表示。创建Socket
对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。 - 服务端:
java.net.ServerSocket
类表示。创建ServerSocket
对象,相当于开启一个服务,并等待客户端的连接。
2.2 Socket类
Socket
类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。
构造方法:
public Socket(String host, int port)
:创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址(本机)。
回送地址(127.0.0.1) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。
成员方法:
public InputStream getInputStream()
: 返回此套接字的输入流。- 如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
- 关闭生成的InputStream也将关闭相关的Socket。
public OutputStream getOutputStream()
: 返回此套接字的输出流。- 如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
- 关闭生成的OutputStream也将关闭相关的Socket。
public void close()
:关闭此套接字。- 一旦一个socket被关闭,它不可再使用。
- 关闭此socket也将关闭相关的InputStream和OutputStream 。
public void shutdownOutput()
: 禁用此套接字的输出流。- 任何先前写出的数据将被发送,随后终止输出流。
2.3 ServerSocket类
ServerSocket
类:这个类实现了服务器套接字,该对象等待通过网络的请求。
构造方法
public ServerSocket(int port)
:使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
成员方法
public Socket accept()
:侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。
2.4 发送消息案例
服务器端代码:
public class TCPServer {
public static void main(String[] args) throws IOException {
//创建服务器端的socket对象
ServerSocket server=new ServerSocket(7890);
//监听客户端的请求
Socket client=server.accept();
//接收数据
InputStream is=client.getInputStream();
byte[] buf=new byte[2];
int len=0;//在输入数据可用、检测到文件末尾或者抛出异常前,此方法一直阻塞。
while((len=is.read(buf))!=-1) {
System.out.print(new String(buf,0,len));
}
//回复数据
OutputStream out=client.getOutputStream();
String str="Nice to meet you too.";
out.write(str.getBytes());
client.shutdownOutput();
//释放资源 后创建的先关闭
out.close();
is.close();
client.close();
server.close();
}
}
客户端代码:
public class TCPClient {
public static void main(String[] args) throws UnknownHostException, IOException {
//创建客户端Socket对象,建立连接
//本机 127.0.0.1 回环地址 localhost
Socket client=new Socket("127.0.0.1", 7890);
//数据传输
//给服务器发送:Nice to meet you.
OutputStream out=client.getOutputStream();
String str="Nice to meet you.";
out.write(str.getBytes());
client.shutdownOutput();
//接收数据
InputStream is=client.getInputStream();
byte[] buf=new byte[1024];
int len=0;//在输入数据可用、检测到文件末尾或者抛出异常前,此方法一直阻塞。
while((len=is.read(buf))!=-1) {
System.out.print(new String(buf,0,len));
}
//释放资源 后创建的先关闭
is.close();
out.close();
client.close();
}
}
注意点:发送文本信息时要在输出流发送数据完毕后调用shutdownOutput()
方法来发送完整的数据,即在文件末尾为-1
,否则服务器端在读取客户端发送的数据时,一直会卡在while(){}
循环的读取中,因为信息发送不完整,一直读不到-1
的结束条件。
2.5 文件上传案例
服务端代码:
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("文件上传已保存");
}
}
客户端代码:
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("文件上传完毕 ");
}
}
2.6 文件上传优化
文件上传问题:
- 文件名称写死的问题
- 服务端,保存文件的名称如果写死,那么最终导致服务器硬盘,只会保留一个文件,可以使用系统时间优化,保证文件名称唯一。
- 不可以循环接收文件
- 服务端,指保存一个文件就关闭了,之后的用户无法再上传,这是不符合实际的。
- 效率问题
- 服务端,在接收大文件时,可能耗费几秒钟的时间,此时不能接收其他用户上传,所以,可以使用多线程技术优化。
服务端代码:
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();
}
}
}
客户端代码
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);
}
// 关闭输出流,通知服务端,写出数据完毕
socket.shutdownOutput();
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.释放资源
socket.close();
bis.close();
}
}
也可以在服务器端使用线程池来保存上传的文件
三、UDP通信
3.1 概述
UDP
:是一种面向无连接的、不可靠的、网络连接,特点是速度快、支持一对一、一对多、多对一、多对多,一般用于视频会议、语音通话。
3.2 DatagramSocket类
DatagramSocket
:来发送和接收数据报包的套接字,每个在数据报套接字上发送或接收的包都是单独编址和路由的。
构造方法:
DatagramSocket()
:构造数据报套接字并将其绑定到本地主机上任何可用的端口,用于发送。DatagramSocket(int port)
:创建数据报套接字并将其绑定到本地主机上的指定端口,用于接收。DatagramSocket(int port, InetAddress laddr)
:创建数据报套接字,将其绑定到指定的本地地址。
成员方法:
void receive(DatagramPacket p)
:从此套接字接收数据报包。void send(DatagramPacket p)
:从此套接字发送数据报包。 此方法在接收到数据报前一直阻塞。
3.3 DatagramPacket类
构造方法:
DatagramPacket(byte[] buf, int length)
:构造 DatagramPacket,用来接收长度为 length 的数据包。DatagramPacket(byte[] buf, int length, InetAddress address, int port)
:构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。
成员方法:
byte[] getData()
:返回数据缓冲区。int getLength()
:返回将要发送或接收到的数据的长度。int getPort()
:返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。InetAddress getAddress()
:返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。
InetAddress类:
InetAddress
:此类表示互联网协议 (IP) 地址。IP 地址是 IP 使用的 32 位或 128 位无符号数字。
主要方法如下:
static InetAddress getByName(String host)
:在给定主机名的情况下确定主机的 IP 地址。String getHostName()
:获取此 IP 地址的主机名。
3.4 模拟聊天案例
客户端A:
public class AClient {
public static void main(String[] args) {
//开启接收数据的线程
new Thread(new ReceiveTask()).start();
// 发送
try {
// 创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket();
System.out.println("可以开始聊天啦----------输入886退出聊天");
Scanner input = new Scanner(System.in);
while (true) {
// 创建数据报包
String str = input.next();
if ("886".equals(str)) {
break;
}
byte[] buf = str.getBytes();
DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 9999);
// 发送
socket.send(dp);
}
// 关闭资源
socket.close();
System.out.println("下线");
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ReceiveTask implements Runnable {
@Override
public void run() {
try {
// 创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket(7890);
while(true) {
// 创建数据报包
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
// 接收 等待数据
socket.receive(dp);
// 查看数据
byte[] data = dp.getData();
int len = dp.getLength();
System.out.println("来自:"+dp.getAddress().getHostName()+":"+dp.getPort()+":" + new String(data, 0, len));
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端B:
public class BClient {
public static void main(String[] args) {
//开启接收数据的线程
new Thread(new ReceiveTaskB()).start();
// 发送
try {
// 创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket();
System.out.println("可以开始聊天啦----------输入886退出聊天");
Scanner input = new Scanner(System.in);
while (true) {
// 创建数据报包
String str = input.next();
if ("886".equals(str)) {
break;
}
byte[] buf = str.getBytes();
DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 7890);
// 发送
socket.send(dp);
// System.out.println("发送成功");
}
// 关闭资源
socket.close();
System.out.println("下线");
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ReceiveTaskB implements Runnable {
@Override
public void run() {
try {
// 创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket(9999);
while(true) {
// 创建数据报包
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
// 接收 等待数据
socket.receive(dp);
// 查看数据
byte[] data = dp.getData();
int len = dp.getLength();
System.out.println("来自:"+dp.getAddress().getHostName()+":"+dp.getPort()+":" + new String(data, 0, len));
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
四、Http通信
使用步骤:
- 通过在 URL 上调用 openConnection 方法创建连接对象URLConnection。
- 为连接对象设置参数和一般请求属性。
- 使用连接对象的 connect 方法建立到远程对象的实际连接。
- 远程对象变为可用,远程对象的头字段和内容变为可访问。
4.1 URLConnection类
常用方法:
abstract void connect()
:打开到此 URL 引用的资源的通信链接(如果尚未建立这样的连接)。InputStream getInputStream()
:返回从此打开的连接读取的输入流。void setUseCaches(boolean usecaches)
:将此 URLConnection 的 useCaches 字段的值设置为指定的值,是否使用缓存。void setConnectTimeout(int timeout)
:设置一个指定的超时值(以毫秒为单位),该值将在打开到此 URLConnection 引用的资源的通信链接时使用。void setRequestProperty(String key, String value)
:设置一般请求属性。
一般将URLConnection
类转换为他的子类HttpURLConnection
,他扩展了父类,拥有更多的方法。
HttpURLConnection类常用方法:
abstract void disconnect()
:指示近期服务器不太可能有其他请求。int getResponseCode()
:从 HTTP 响应消息获取状态码。
4.2 使用案例
public class TestHttp {
public static void main(String[] args) throws IOException {
//1、通过在 URL 上调用 openConnection 方法创建连接对象。
URL url=new URL("https://www.baidu.com");
//获取连接对象
HttpURLConnection conn=(HttpURLConnection)url.openConnection();
//2、处理设置参数和一般请求属性。 --不是必须的,根据需求
conn.setUseCaches(false);
conn.setConnectTimeout(5000);
conn.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36");
//3、使用 connect 方法建立到远程对象的实际连接。
conn.connect();
//4、远程对象变为可用。远程对象的头字段和内容变为可访问。
System.out.println("状态码:"+conn.getResponseCode());
InputStream is=conn.getInputStream();
FileOutputStream fos=new FileOutputStream("baidu.txt");
byte[] buf=new byte[1024];
int len;
while((len=is.read(buf))!=-1) {
fos.write(buf, 0, len);
}
//5、断开连接
fos.close();
is.close();
conn.disconnect();
}
}