网络编程概念

  • 可以让设备中的程序与网络上其他设备中的程序进行数据交互的技术(实现网络通信)
  • 基本的通信架构分为两种:CS架构(Client客户端/Server服务端)、BS架构(Browser浏览器/Server服务端)
  • 无论是CS架构还是BS架构,都必须依赖网络编程
  • Java中提供了java.net.*包用于进行网络编程

网络编程三要素

三要素为:IP地址、端口、协议

  1. IP地址:设备在网络中的地址,是设备在网络中的唯一标识
  2. 端口:应用程序在设备中的唯一标识
  3. 协议:连接和数据在网络中传输的规则

IP地址

  • IP(Internet Protocol):全称“互联网那个协议地址”,是分配给上网设备的唯一标识
  • 目前,被广泛采用的IP地址形式有两种:IPv4、IPv6

IPv4:是Internet Protocol Version 4的缩写,它使用32位地址,通常以点分十进制表示,如192.168.100.200

IPv6:是Internet Protocol Version 6的缩写,它使用128为地址。IPv6分为8段,每段每四位编码成一个十六进制表示,每段之间用冒号分开。这种形式又称为冒分十六进制,比如:2001:0db8:0000:0023:0008:0800:200c:417a

端口号

由两个字节表示的整数,取值范围:0~65535

其中,0~1023之间的端口号用于一些知名的网络服务或者应用。开发中一般都使用1024或以上的端口号

注意:端口号只能被一个应用程序使用

协议

UDP协议:

  • 用户数据包协议(User Datagram Protocol)
  • UDP面向无连接通信协议。速度快,有大小限制一次最多发送64K,数据不安全,易丢失(只管发送,不管对方是否接收成功)

TCP协议:

  • 传输控制协议(Transmission Control Protocol)
  • TCP协议是面向连接的通信协议。速度慢,没有大小限制,数据安全(发送后还要确认对方是否接收成功)

网络编程

InetAddress对象

为了方便获取和操作IP地址,Java提供了一个类InetAddress,此类表示Internet协议(IP)地址

方法名 说明
static InetAddress getByName(String host) 确定主机名称的IP地址
主机名称可以是机器名称,也可以是IP地址
String getHostName() 获取此IP地址的主机名
String getHostAddress() 返回文本显示中的IP地址字符串

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package net;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class InetAddressDemo01 {
public static void main(String[] args) throws UnknownHostException {
// 将需要操作的IP或是主机名封装为InetAddress对象
InetAddress address = InetAddress.getByName("192.168.190.129");
System.out.println(address); // /192.168.190.129

// 获取此IP地址的主机名
String hostName = address.getHostName(); // 如果本地hosts文件没有配置IP地址与主机名映射,可能会导致返回结果依然为IP地址
System.out.println(hostName); // DESKTOP-U9GJ3JJ

String hostAddress = address.getHostAddress();
System.out.println(hostAddress);
}
}

UDP收发数据

服务端(发送端)总体流程:

  1. 创建发送端DatagramSocket对象,指定发送端口
  2. 数据打包(DatagramPacket)
  3. 发送数据
  4. 释放资源

发送端示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package net;

import java.io.IOException;
import java.net.*;

public class UDPSend01 {
public static void main(String[] args) throws IOException {
// 创建DatagramSocket发送数据
DatagramSocket ds = new DatagramSocket(8888); // 参数:指定端口号,不指定会选择随机端口号

// 准备发送的数据
String string = "Hello World";
byte[] bytes = string.getBytes();

// 将数据打包
DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("127.0.0.1"),9999);

// 发送数据
ds.send(dp);

// 释放资源
ds.close();
}
}

客户端(接收端)总体流程:

  1. 创建DatagramSocket对象,指定接收端口
  2. 创建DatagramPacket对象准备接收数据
  3. 接收数据,程序会在这一步阻塞运行,直到接收到数据
  4. 对包裹进行拆封,获取真实数据
  5. 释放资源

接收端示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package net;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Arrays;

public class UDPReceive01 {
public static void main(String[] args) throws IOException {
// 创建DatagramSocket接收数据
DatagramSocket ds = new DatagramSocket(9999);

// 准备接收端packet对象
DatagramPacket dp = new DatagramPacket(new byte[1024],1024);

// 接收数据
ds.receive(dp); // 阻塞运行,直到接收到数据

// 拆包
byte[] data = dp.getData();
String string = new String(data, 0, dp.getLength()); // dp.getLength()返回真实的有效数据
String hostAddress = dp.getAddress().getHostAddress();
System.out.println("接收到" + hostAddress + "发送的" + string);

// 释放资源
ds.close();
}
}

TCP收发数据

客户端流程如下:

  1. 创建Socket对象指定ip和端口:Socket(String host,int port);
  2. 通过socket对象获取传输数据的流对象:OutputStream getOutputStream();InputStream getInputStream();
  3. 通过流对象收发数据
  4. 释放资源

这里的流对象不是读写文件中的数据

输出流:写出数据到服务端

输入流:读取服务端发送的数据

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package net;

import java.io.*;
import java.net.Socket;

public class TCPSend01 {
public static void main(String[] args) throws IOException {
// 使用File对象封装上传文件
File src = new File("C:\\Users\\Zhao\\Desktop\\test01.png");

// 1. 创建客户端的站点指定服务端的IP和端口
Socket socket = new Socket("127.0.0.1",19999);

// 2. 通过Socket获取传输数据的输入输出流
InputStream is = socket.getInputStream(); // 读取服务端发送的数据
OutputStream os = socket.getOutputStream(); // 写出本地数据到服务端

// 3. 客户端创建本地的流对象,读取要上传的文件
FileInputStream fis = new FileInputStream(src);
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
// 将读取到的字节通过网络流写出给服务器
os.write(bytes,0,len);
}
// 客户端给服务端结束的标记,否则服务端会认为客户端还有数据要发送,就不会停止读取数据
socket.shutdownOutput();
fis.close();

// 4. 读取服务端发送回来的消息
byte[] receiveData = new byte[1024];
int receiveDataLen = is.read(receiveData);
String msg = new String(receiveData,0,receiveDataLen);

System.out.println("读取到服务端发送过来的消息为:" + msg);

// 5. 关流释放资源
is.close();
os.close();
socket.close();
}
}

Socket中指定的是服务端的IP地址+端口号

当数据写出完成后,应该向服务端发送完成标记(socket调用shutdownOutput方法),否则服务端会认为客户端还有数据需要发送,就不会停止服务端的写入操作

记得读取服务端发送过来的响应数据(比如数据接收成功这类的消息)

服务端流程如下:

  1. 创建ServerSocket对象指定端口:ServerSocket(int port);
  2. 响应客户端发送的请求:Socket accpet();
  3. 通过socket对象获取传输数据的流对象:OutputStream getOutputStream();InputStream getInputStream();
  4. 通过流对象收发数据
  5. 释放资源

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package net;

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 TCPReceive01 {
public static void main(String[] args) throws IOException {
// 创建ServerSocket对象
ServerSocket server = new ServerSocket(19999);

// 响应客户端发送的请求
Socket socket = server.accept(); // 阻塞,等待客户端连接
System.out.println("响应成功");

// 通过socket获取传输数据的输入输出流
InputStream is = socket.getInputStream(); // 读取客户端发送的数据
OutputStream os = socket.getOutputStream(); // 写出数据到客户端

// 读取客户端发送回来的消息
byte[] bytes = new byte[1024];
int len;
FileOutputStream fos = new FileOutputStream("E:\\test01.png");
while ((len = is.read(bytes)) != -1) {
fos.write(bytes,0,len);
}
fos.close();

// 响应给客户端上传成功的消息
os.write("上传成功!!!".getBytes());

// 关流释放资源
is.close();
os.close();
socket.close();
server.close();
}
}

服务端相比客户端,需要单独创建一个ServerSocket对象

如果客户端没有发送完成标记,那么程序会卡在循环读取客户端发送的数据这一步

利用OutputStream可以向客户端发送响应信息