网络编程详解

网络通信的要素

  1. 通信双方的地址

    • IP地址
    • 端口号
  2. 通信协议

    • TCP/IP 参考模型

      • 应用层

        HTTP、FTP、SMTP

      • 传输层

        TCP、UDP

      • 网络层

        IP

      • 数据链路层

Java 中的网络支持:

  • InetAddress:用于表示网络上的硬件资源,即 IP 地址;
  • URL:统一资源定位符;
  • Sockets:使用 TCP 协议实现网络通信;
  • Datagram:使用 UDP 协议实现网络通信。

IP

IP地址的类:InetAddress

  • IP地址能唯一定位一台网络上的计算机

  • 127.0.0.1:本机 localhost

  • IP地址的分类

    • IPV4/IPV6

    • 公网/局域网

      192.168.xx.xx(专门给组织内部使用)

  • 域名

InetAddress没有公有的构造函数,只能通过静态方法来创建实例

1
2
InetAddress.getByName(String host);
InetAddress.getByAddress(byte[] address);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//测试IP
public class TestIP {
public static void main(String[] args) {
try {
//查询本机地址
InetAddress inetAddress1 = InetAddress.getByName("localhost");
System.out.println(inetAddress1);
InetAddress inetAddress2 = InetAddress.getLocalHost();
System.out.println(inetAddress2);

//查询网站ip地址
InetAddress inetAddress3 = InetAddress.getByName("www.baidu.com");
System.out.println(inetAddress3);

//常用方法
System.out.println(inetAddress3.getCanonicalHostName()); //规范的主机名
System.out.println(inetAddress3.getHostAddress()); // IP
System.out.println(inetAddress3.getHostName()); // 域名或自己的计算机名
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}

端口

端口表示计算机上一个程序的对外的接口

  • 不同的进程有不同的端口号,用来区分软件

  • 规定端口号范围:0-65535

  • 相同协议下,端口号不能冲突(不同协议,可以使用相同端口号)

  • 端口分类:

    • 公有端口:0-1023

      一般会被内置的进程、服务器使用,尽量不要使用

      • HTTP端口: 80
      • HTTPS端口: 443
      • FTP端口: 21
      • SSH: 22
      • Telent: 23
    • 程序注册端口:1024-49151

      分配给用户或程序

      • Tomcat: 8080
      • MySQL: 3306
      • Oracle: 1521
      • SQL Server:1433
    • 动态、私有端口:49152-65535

      1
      2
      3
      netstat -ano #查看端口命令
      netstat -ano|findstr "5900" #查看指定的端口
      tasklist|findstr "1584" #查看指定端口的进程
1
2
3
4
5
6
7
8
9
10
public class TestSocketAddress {
public static void main(String[] args) {
InetSocketAddress socketAddress = new InetSocketAddress("localhost", 8080);

System.out.println(socketAddress);
System.out.println(socketAddress.getAddress());
System.out.println(socketAddress.getHostName());
System.out.println(socketAddress.getPort()); //获取端口
}
}

URL

URL(统一资源定位符)用于定位网络资源,可以直接从URL中读取字节流数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) throws IOException {

URL url = new URL("http://www.baidu.com");

/* 字节流 */
InputStream is = url.openStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, "utf-8"));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}

br.close();
}

下载网络资源

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
URL url = new URL("https://pic3.zhimg.com/v2-77fd9b17781556ee25be582af6a9cd4c_r.jpg?source=1940ef5c"); // 资源地址(图片)

// 连接到该资源
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

InputStream is = urlConnection.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is); // 图片、音频、视频等类型使用缓冲字节流,含中文的文档类资源使用缓冲字符流


// 判断文件夹是否存在
File file = new File("C:\\Users\\Hunter\\Downloads");
if (!file.exists()) {
file.mkdirs();
}
FileOutputStream fos = new FileOutputStream(file + File.separator + "Hunter" + System.currentTimeMillis() + new Random().nextInt(999999) + ".jpg");

BufferedOutputStream bos = new BufferedOutputStream(fos);

byte[] bytes = new byte[4 * 1024]; // 存储每次读取的数据
int len = 0;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bos.close();
bis.close();
urlConnection.disconnect();

通信协议

TCP/IP协议簇

  • TCP(Transmission Control Protocol) 传输控制协议

  • UDP(User Datagram Protocol) 用户数据报协议

  • IP(Internet Protocol) 网络互连协议

TCP 和 UDP 的对比

TCP(类比:打电话)

  • 需要连接,稳定

  • 三次握手四次挥手

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 至少需要三次,保证稳定连接
    C:我喜欢你
    S:我也喜欢你
    C:我们在一起吧

    // 四次挥手
    C:我要走了
    S:我知道你要走了
    S:你已经走了吗?
    C:我已经走了
  • 有明确的客户端和服务端角色

  • 传输完成后释放连接,效率低

UDP(类比:发短信)

  • 不需要连接,不稳定

TCP

Socket:套接字,指两台设备之间通讯的端点,是包含了IP地址和端口号的网络单位

  • java.net.ServerSocket

    服务器端类,相当于开启一个服务,并等待客户端的连接

  • java.net.Socket

    客户端类,向服务器发出连接请求,服务端响应请求后,两者建立连接,开始通信(三次握手)


服务端

java.net.ServerSocket

构造方法

ServerSocket(int port):创建绑定到指定端口的服务端套接字


成员方法

Socket accept()侦听并接收客户端的连接


实现步骤

  1. 创建服务器端对象ServerSocket,指定端口
  2. 等待用户的连接,通过accept方法获取请求的客户端对象
  3. 使用获取到的客户端对象的getInputStream方法获取网络字节输入流InputStream对象
  4. 使用网络字节输入流对象的read方法读取客户端发来的数据
  5. 使用获取到的客户端对象的getOutputStream获取网络字节输出流OutputStream对象
  6. 使用网络字节输出流对象的write方法给客户端发送数据传输结束标记
  7. 释放资源

服务器没有IO流,而是通过获取请求的客户端对象Socket,使用Socket提供的IO流和客户端进行交互

  • 服务器使用客户端的字节输入流InputStream读取客户端发送的数据
  • 服务器使用客户端的字节输出流OutputStream向客户端写回数据
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
ServerSocket serverSocket = new ServerSocket(2000);
while (true) { // 服务端的持久运行
// 2. 获取请求的客户端对象
Socket socket = serverSocket.accept();

// 3. 读取客户端消息
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}

// 向客户端发送数据
OutputStream os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write("你在教我做事?");
bw.newLine();
bw.flush(); // 重要

// 网络流的传输不同于本地,不会传输结束的标记
socket.shutdownOutput(); // 禁用套接字的输出流,同时会传输一个结束标记

br.close();
bw.close();
socket.close();
}

客户端

java.net.Socket

构造方法

Socket(String host, int port):创建一个套接字,并连接到指定主机上指定的端口号

  • String host:服务器主机的名称/服务器的IP地址

成员方法

  • OutputStream getOutputStream():返回套接字的输出流
  • InputStream getInputStream():返回套接字的输入流
  • void close():关闭套接字

实现步骤

  1. 创建一个客户端对象Socket
  2. 使用getOutputStream方法获取网络字节输出流OutputStream对象
  3. 使用网络字节输出流对象的write方法给服务器发送数据传输结束标记
  4. 使用getInputStream获取网络字节输入流InputStream对象
  5. 使用网络字节输入流对象的read方法读取服务器传回的数据
  6. 释放资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Socket socket = new Socket("127.0.0.1", 2000);

OutputStream os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write("教练,我想打篮球");
bw.newLine();
bw.flush(); // 重要

// 网络流的传输不同于本地,不会传输结束的标记
socket.shutdownOutput(); // 禁用套接字的输出流,同时会传输一个结束标记

// 3. 读取服务端消息
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}

bw.close();
br.close();
socket.close();

文件上传

注意事项

  1. 为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如WEB-INF目录下。
  2. 为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名(时间戳 + uuid + md5加密)
  3. 限制上传文件的最大值。
  4. 限制上传文件的类型,判断后缀名是否合法。

服务端

  1. 创建服务器ServerSocket对象
  2. 使用ServerSocket对象中的accept方法,获取到请求的客户端Socket对象
  3. 使用Socket对象的getInputStream方法,获取到网络字节输入流
  4. 判断指定存储的文件夹是否存在,不存在则创建
  5. 创建一个本地字节输出流FileOutputStream对象
  6. 读取客户端上传的文件,写入至服务器中指定的文件夹下
  7. 获取网络字节输出流对象,返回上传成功的消息(传输结束标记
  8. 释放资源
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
ServerSocket serverSocket = new ServerSocket(8888);

while (true) {
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 判断文件加是否存在
File file = new File("C:\\Users\\Hunter\\Downloads");
if (!file.exists()) {
file.mkdirs();
}

// 使用【域名 + 系统时间 + 随机数】用于命名
FileOutputStream fos = new FileOutputStream(file + File.separator + "Hunter" + System.currentTimeMills() + new Random().nextInt(999999) + ".md");
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos));
// 将本地上传的文件内容输出至指定文件
String line;
while ((line = br.readLine()) != null) { // 客户端socket.shutdownOutput()的操作,会传来文件的结束标记
bw.write(line);
bw.newLine(); // 换行(根据不同的操作系统,获取不同的行分隔符)
}
bw.close();

// 返回上传成功的消息
OutputStream os = socket.getOutputStream();
BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(os));
bw2.write("上传成功");

bw2.flush();
socket.shutdownOutput(); // 禁用套接字的输出流,同时会传输一个结束标记

// 释放资源
bw2.close();
br.close();
socket.close();
}

客户端

  1. 创建一个本地字节输入流FileInputStream对象,读取本地文件
  2. 创建客户端Socket对象
  3. 使用Socket对象的getOutputStream方法,获取到网络字节输出流
  4. 通过网络字节输出流传输本地文件内容至服务器(传输结束标记
  5. 读取服务端返回的消息
  6. 释放资源
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
// 创建本地字节输入流
FileInputStream fis = new FileInputStream("C:\\Users\\Hunter\\Downloads\\xx.md");
BufferedReader br = new BufferedReader(new InputStreamReader(fis));

// 创建客户端Socket对象
Socket socket = new Socket("127.0.0.1", 8888);
// 获取网络字节输出流
OutputStream os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
// 将本地的文件上传至网络字节输出流
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine(); // 换行(根据不同的操作系统,获取不同的行分隔符)
}
bw.flush();
socket.shutdownOutput(); // 上传完文件, 给服务端一个结束标记,否则服务端将阻塞

// 获取上传结果
InputStream is = socket.getInputStream();
BufferedReader br2 = new BufferedReader(new InputStreamReader(is));
while ((line = br2.readLine()) != null) {
System.out.println(line);
}

// 释放资源
br.close();
br2.close();
socket.close();

文件上传优化 —— 多线程

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
44
45
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
Socket socket = serverSocket.accept();
/*
* 使用多线程,提高程序效率
* 有一个客户端上传文件,就开启一个线程,完成文件的上传
* */
new Thread(() -> {
try {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 判断文件加是否存在
File file = new File("C:\\Users\\Hunter\\Downloads");
if (!file.exists()) {
file.mkdirs();
}

// 使用【域名 + 系统时间 + 随机数】用于命名
FileOutputStream fos = new FileOutputStream(file + File.separator + "Hunter" + System.currentTimeMillis() + new Random().nextInt(999999) + ".md");
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos));
// 将本地上传的文件内容输出至指定文件
String line;
while ((line = br.readLine()) != null) { // 客户端socket.shutdownOutput()的操作,会传来文件的结束标记
bw.write(line);
bw.newLine(); // 换行(根据不同的操作系统,获取不同的行分隔符)
}
bw.close();

// 返回上传成功的消息
OutputStream os = socket.getOutputStream();
BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(os));
bw2.write("上传成功");

bw2.flush();
socket.shutdownOutput(); // 禁用套接字的输出流,同时会传输一个结束标记

// 释放资源
bw2.close();
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}

进阶

对于文件上传,浏览器将文件以流的形式提交到服务器,使用原生态的文件上传流request.getInputStream()获取十分麻烦,一般采用apache的开源工具commons-fileupload,其依赖于commons-io

ServletFileUpload类

ServletFileUpload负责处理上传的文件数据,使用其parseRequest(HttpServletRequest)方法,会将表单中每个HTML标签封装成一个FileItem对象,以List的形式返回

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package com.hunter.servlet;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;

public class FileServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 判断上传的文件是否普通表单
if (!ServletFileUpload.isMultipartContent(req)) {
return;
}

// 创建上传文件的保存路径,建议在WEB-INF路径下,安全,用户无法直接访问上传的文件
String uploadPath = this.getServletContext().getRealPath("/WEB-INF/upload");
File uploadFile = new File(uploadPath);
if (!uploadFile.exists()) {
uploadFile.mkdir();
}

// 缓存临时文件
// 临时路径,假如文件超过了预期的大小,就放到临时文件中,超过设定的时间后自动删除,或提醒用户转存为永久
String tmpUploadPath = this.getServletContext().getRealPath("/WEB-INF/tmp");
File tmpUploadFile = new File(tmpUploadPath);
if (!tmpUploadFile.exists()) {
tmpUploadFile.mkdir();
}

/*
* ServletFileUpload负责处理上传的文件数据,使用其parseRequest(HttpServletRequest)方法,
* 会将表单中每个HTML标签封装成一个FileItem对象,以List的形式返回。
* 而使用ServletFileUpload对象解析请求时,需要DiskFileItemFactory对象。
*/
try {
// 1. 创建DiskFileItemFactory对象,处理文件上传 路径或大小限制
DiskFileItemFactory diskFileItemFactory = getDiskFileItemFactory(tmpUploadFile);
// 2. 获取ServletFileUpload
ServletFileUpload servletFileUpload = getServletFileUpload(diskFileItemFactory);
// 3. 处理上传的文件
String msg = uploadParseRequest(req, uploadPath, servletFileUpload);

req.setAttribute("msg", msg);
req.getRequestDispatcher("info.jsp").forward(req, resp);
} catch (FileUploadException e) {
e.printStackTrace();
}
}

private String uploadParseRequest(HttpServletRequest request, String uploadPath, ServletFileUpload servletFileUpload)
throws FileUploadException, IOException {
String msg = "";
// parseRequest(HttpServletRequest)方法,会将表单中每个HTML标签封装成一个FileItem对象,以List的形式返回。
List<FileItem> fileItems = servletFileUpload.parseRequest(request);
for (FileItem fileItem : fileItems) {
// 是否普通表单
if (fileItem.isFormField()) {
// 前端表单控件的name
String fieldName = fileItem.getFieldName();
// UTF-8 处理乱码
String value = fileItem.getString("UTF-8");
System.out.println(fieldName + ": " + value);
} else {
String uploadFileName = fileItem.getName();
// 文件名可能不合法
if (uploadFileName == null || uploadFileName.trim().equals("")) {
continue;
}
// 获得文件名
String fileName = uploadFileName.substring(uploadFileName.lastIndexOf("/") + 1);
System.out.println("文件信息【文件名:" + fileName + "】");

// 使用UUID,保证文件名唯一。网络传输的东西,都需要序列化。
String uuidPath = UUID.randomUUID().toString();
String realPath = uploadPath + "/" + uuidPath;
// 给每个文件创建一个对应的文件夹
File realPathFile = new File(realPath);
if (!realPathFile.exists()) {
realPathFile.mkdir();
}

// 获得文件上传的流
InputStream inputStream = fileItem.getInputStream();

// 创建一个文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(realPath + "/" + fileName);
// 创建一个缓冲区
byte[] buffer = new byte[1024 * 1024];
int len = 0;
while ((len = inputStream.read(buffer)) > 0) {
fileOutputStream.write(buffer, 0, len);
}

// 关闭流
fileOutputStream.close();
inputStream.close();
msg = "文件上传成功";
// 清除临时文件
fileItem.delete();
}
}
return msg;
}

/**
* 获取ServletFileUpload
*
* @param diskFileItemFactory diskFileItemFactory
* @return ServletFileUpload
*/
private ServletFileUpload getServletFileUpload(DiskFileItemFactory diskFileItemFactory) {
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);

// 监听文件上传进度
servletFileUpload.setProgressListener(
(pBytesRead, pContentLength, pItems) ->
System.out.println("总大小:" + pContentLength + " B,已上传:" + pBytesRead + " B。"));

// 处理乱码问题
servletFileUpload.setHeaderEncoding("UTF-8");
// 设置单个文件的最大值,10MB
servletFileUpload.setFileCountMax(1024 * 1024 * 10);
// 设置总共能上传文件的大小,10MB
servletFileUpload.setSizeMax(1024 * 1024 * 10);
return servletFileUpload;
}

/**
* 创建DiskFileItemFactory对象,处理文件上传 路径或大小限制
*
* @param tmpUploadFile tmpUploadFile
* @return DiskFileItemFactory
*/
private DiskFileItemFactory getDiskFileItemFactory(File tmpUploadFile) {
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();

// 通过这个工厂类,设置一个大小为1MB的缓冲区,当上传文件大于这个缓冲区时,放到临时文件中
diskFileItemFactory.setSizeThreshold(1024 * 1024);
diskFileItemFactory.setRepository(tmpUploadFile);
return diskFileItemFactory;
}
}

UDP

java.net.DatagramPacket:数据报包,包括目的地址和传递的数据

java.net.DatagramSocket:数据包套接字,通过send方法发送数据报包DatagramPacket对象


发送端

1
2
3
4
5
6
7
8
9
10
11
12
// 1. 建立一个socket
DatagramSocket socket = new DatagramSocket();

// 2. 建一个包
String msg = "教练,我想打球";
InetAddress localhost = InetAddress.getByName("localhost");
DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, localhost, 9090);

// 3. 发送包
socket.send(packet);

socket.close();

接收端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 开放端口
DatagramSocket socket = new DatagramSocket(9090);

// 接收数据包
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);

// 3. 接收包
socket.receive(packet);

// 获取发送端的相关信息
System.out.println(packet.getAddress().getHostAddress());
System.out.println(new String(packet.getData(), 0, packet.getLength())); // 实际接收到的数据长度

// 4. 关闭连接
socket.close();

互相聊天

实现了Runnable接口的接收类和发送类:

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
44
45
46
public class TalkSend implements Runnable {

DatagramSocket socket = null;
BufferedReader reader = null;

private int fromPort;
private String toIp;
private int toPort;

public TalkSend(int fromPort, String toIp, int toPort) {
this.fromPort = fromPort;
this.toIp = toIp;
this.toPort = toPort;

try {
// 1. 建立一个socket
socket = new DatagramSocket(this.fromPort);
// 控制台读取System.in
reader = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
}

@Override
public void run() {
while (true) {
try {
// 2. 建一个包
byte[] msg = reader.readLine().getBytes();
DatagramPacket packet = new DatagramPacket(msg, 0, msg.length,
new InetSocketAddress(this.toIp, this.toPort));

// 3. 发送包
socket.send(packet);
if (new String(msg).equals("bye")) {
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 4. 关闭连接
socket.close();
}
}
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
public class TalkReceiver implements Runnable {

DatagramSocket socket = null;
private int port;
private String msgFrom;

public TalkReceiver(int port, String msgFrom) {
this.port = port;
this.msgFrom = msgFrom;

try {
// 开放端口
socket = new DatagramSocket(this.port);
} catch (SocketException e) {
e.printStackTrace();
}
}

@Override
public void run() {
while (true) {
try {
// 接收数据包
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
socket.receive(packet);

// 判断是否断开连接
String receiveData = new String(packet.getData(), 0, packet.getLength());
System.out.println(msgFrom + ": " + receiveData);
if (receiveData.equals("bye")) {
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

创建有发送和接收信息功能的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TalkTeacher {
public static void main(String[] args) {
// 发送进程一个端口,接收进程一个端口;发送的目的端口和接收的端口要与聊天的对象对应起来
new Thread(new TalkSend(5555, "localhost", 8888)).start();
new Thread(new TalkReceiver(9999,"student")).start();
}
}

public class TalkStudent {
public static void main(String[] args) {
// 开启两个线程,发送进程一个端口,接收进程一个端口;发送的目的端口和接收的端口要与聊天的对象对应起来
new Thread(new TalkSend(7777, "localhost", 9999)).start();
new Thread(new TalkReceiver(8888, "teacher")).start();
}
}


SMTP与POP3协议

  • SMTP

    发送邮件。

  • POP3

    接收邮件。

  • MIME

    附件。


电子邮件

要在网络上实现邮件功能,必须有专门的邮件服务器

SMTP服务器地址一般是smtp.xx.com

image-20230422174908282


要发送邮件,需要获得协议和支持,开启服务POP3/SMTP

例如网易邮箱:

image-20230422224516310