JAVASE——网络编程

一、 网络编程概述

网络编程:编写的应用程序可以与网络上其他设备中的应用程序进行数据交互。

网路通信的基本架构:

  1. CS架构(Client/Server):

    CS架构需要用户在自己的电脑或者手机上安装客户端软件,然后由客户端软件通过网络连接服务器程序,由服务器把数据发给客户端,客户端就可以在页面上看到各种数据了。

    1668262460269

  2. BS架构(Browser/Server):

    BS架构不需要开发客户端软件,用户只需要通过浏览器输入网址就可以直接从服务器获取数据,并由服务器将数据返回给浏览器,用户在页面上就可以看到各种数据了。

    1668262589242

以后从事的工作方向主要还是BS架构的。

二、 网络编程三要素

IP地址、端口号、通信协议

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

假设现在要从一台电脑中的微信上,发一句“你愁啥?”到其他电脑的微信上,流程如下

1
2
3
1.先通过ip地址找到对方的电脑
2.再通过端口号找到对方的电脑上的应用程序
3.按照双方约定好的规则发送、接收数据

2.1 IP地址

IP(Ineternet Protocol)全称互联网协议地址,是分配给网络设备的唯一表示。IP地址分为:IPV4地址、IPV6地址

PV4地址由32个比特位(4个字节)组成,采用点分十进制。经过不断的发展,现在越来越多的设备需要联网,IPV4地址已经不够用了,所以扩展出来了IPV6地址。IPV6采用128位二进制数据来表示(16个字节),号称可以为地球上的每一粒沙子编一个IP地址。

域名:我们在浏览器上访问某一个网站是,就需要在浏览器的地址栏输入网址,这个网址的专业说法叫做域名。

域名和IP其实是一一对应的,由运营商来管理域名和IP的对应关系。我们在浏览器上敲一个域名时,首先由运营商的域名解析服务,把域名转换为ip地址,再通过IP地址去访问对应的服务器设备。

本地回环测试地址:127.0.0.1

2.2 InetAddress类

Java中也有一个类用来表IP地址,这个类是InetAddress类

1668265337659

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. 获取本机IP地址对象
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAdress());

// 2. 获取指定IP或者指定域名的IP地址对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());

// 3. ping
System.out.println(ip2.isReachable(6000));

2.3 端口号

端口号:指的是计算机设备上运行的应用程序的标识,被规定为一个16位的二进制数据,范围(0~65535)

同一个计算机设备中,不能出现两个应用程序,用同一个端口号

2.4 协议

网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。

传输层有两个协议(UDP协议、TCP协议)

1668267975803

  • UDP协议特点:

1668268046104

  • TCP协议特点:

    1668268144938

三次握手:目的是确认通信双方,收发消息都是正常没问题的

1668268174867

四次挥手:目的是确保双方数据的收发已经完成,没有数据丢失

1668268230804

三、UDP通信

DatagramSocket类:完成基于UDP协议的收发数据,数据要以数据包的形式体现,一个数据包限制在64KB以内。

通信双方都需要有DatagramSocket(扔的行为),还需要有DatagramPacket(装数据的包)

案例:需要有两个程序,一个表示客户端程序,一个表示服务端程序。需求:客户端程序发一个字符串数据给服务端,服务端程序接收数据并打印。

3.1 客户端程序

数据bytes[] 装入 DatagramPacket packet

DatagramSocket socket 发送 packet

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
import java.io.IOException;
import java.net.*;
public class Client {
/**
*
* UDP通信:C方发消息
*/
public static void main(String[] args) throws Exception {
// 1. 创建客户端对象
DatagramSocket socket = new DatagramSocket(7777);

// 2. 创建数据包对象
byte[] bytes = "我是客户端abc".getBytes();
/*
* public DatagramPacket(byte buf[], int length,
InetAddress address, int port)
参数一:封装要发出去的数据。
参数二:发送出去的数据大小(字节个数)
参数三:服务端的IP地址(找到服务端主机)
参数四:服务端程序的端口。
*/
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 6666);

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

System.out.println("客户端数据发送完毕");
socket.close();






}
}

3.2 服务端程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.net.*;
public class Server {
public static void main(String[] args) throws Exception{
// 1. 创建服务端对象
DatagramSocket socket = new DatagramSocket(6666);
// 2. 创建数据包对象,接收数据
byte[] buffer = new byte[1024*64]; // 64kB
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 3. 接收数据
socket.receive(packet);
// 4. 从数组中把接收的数据打印出来
int len = packet.getLength();

String res = new String(buffer, 0, len);
System.out.println(res);
System.out.println(packet.getAddress().getHostAddress());
System.out.println(packet.getPort());
socket.close();


}
}

image-20241031203740158

四、UDP通信(多发多收)

需求:实现客户端不断的发数据,而服务端能不断的接收数据,客户端发送exit时客户端程序退出。

4.1 客户端程序

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
import javax.sound.midi.Soundbank;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class Client {
/**
*
* UDP通信:C方发消息
*/
public static void main(String[] args) throws Exception {
// 1. 创建客户端对象
DatagramSocket socket = new DatagramSocket(7777);

Scanner sc = new Scanner(System.in);
while(true){
System.out.println("请说:");
String msg = sc.nextLine();

//若输入exit,退出客户端
if("exit".equals(msg)){
System.out.println("通信结束!");
socket.close();
break;
}
// 2. 创建数据包对象
byte[] bytes = msg.getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 6666);

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

System.out.println("客户端数据发送中");

}









}
}

4.2 服务端程序

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
import java.net.*;
public class Server {
public static void main(String[] args) throws Exception{
// 1. 创建服务端对象
DatagramSocket socket = new DatagramSocket(6666);
// 2. 创建数据包对象,接收数据
byte[] buffer = new byte[1024*64]; // 64kB
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while(true){

// 3. 接收数据
socket.receive(packet);
// 4. 从数组中把接收的数据打印出来
int len = packet.getLength();

String res = new String(buffer, 0, len);
System.out.println(res);
System.out.println(packet.getAddress().getHostAddress());
System.out.println(packet.getPort());
System.out.println("--------------------------------------");

}



}
}

客户端:

image-20241031204642856

服务端:

image-20241031204705996

五、TCP通信(一发一收)

Java提供了一个java.net.Socket类来完成TCP通信。

  1. 当创建Socket对象时,就会在客户端和服务端创建一个数据通信的管道,在客户端和服务端两边都会有一个Socket对象来访问这个通信管道。
  2. 现在假设客户端要发送一个“在一起”给服务端,客户端这边先需要通过Socket对象获取到一个字节输出流,通过字节输出流写数据到服务端
  3. 然后服务端这边通过Socket对象可以获取字节输入流,通过字节输入流就可以读取客户端写过来的数据,并对数据进行处理。
  4. 服务端处理完数据之后,假设需要把“没感觉”发给客户端端,那么服务端这边再通过Socket获取到一个字节输出流,将数据写给客户端
  5. 客户端这边再获取输入流,通过字节输入流来读取服务端写过来的数据。

1668270124031

5.1 TCP客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.net.*;
import java.io.*;
public class Client1 {
public static void main(String[] args) throws Exception {
// 1. 创建socket对象,并同时请求与服务端连接
Socket socket = new Socket("127.0.0.1", 8888);
// 2. 从socket通信管道里得到一个字节输出流,用于发送数据到服务端
OutputStream os = socket.getOutputStream();
// 3. 把字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
// 4. 写数据出去
dos.writeUTF("hello");
dos.close();
socket.close();
}
}

5.2 TCP服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.net.*;
import java.io.*;

public class Server1 {
public static void main(String[] args) throws Exception {
// 1. 创建ServerSocket对象,同时为服务端注册端口
ServerSocket serverSocket = new ServerSocket(8888);
// 2. 等待客户端请求
Socket socket = serverSocket.accept();
// 3. 从socket中得到字节输入流
InputStream is = socket.getInputStream();
// 4. 封装成数据输入流
DataInputStream dis = new DataInputStream(is);
// 5. 读取数据
String rs = dis.readUTF();
System.out.println(rs);
System.out.println(socket.getRemoteSocketAddress());

dis.close();
socket.close();
}
}

六、TCP通信(多发多收)

把客户端代码改写一下,采用键盘录入的方式发消息,为了让客户端能够一直发,我们只需要将发送消息的代码套一层循环就可以了,当用户输入exit时,客户端退出循环并结束客户端。

6.1 TCP客户端

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
import java.net.*;
import java.io.*;
import java.util.Scanner;

public class Client1 {
public static void main(String[] args) throws Exception {
// 1. 创建socket对象,并同时请求与服务端连接
Socket socket = new Socket("127.0.0.1", 8888);
// 2. 从socket通信管道里得到一个字节输出流,用于发送数据到服务端
OutputStream os = socket.getOutputStream();
// 3. 把字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);

Scanner sc = new Scanner(System.in);
while(true){
System.out.println("请说:");
String msg = sc.nextLine();
if("exit".equals(msg)){
System.out.println("通信结束!");
dos.close();
socket.close();
break;
}
// 4. 写数据出去
dos.writeUTF(msg);
dos.flush();
}

}
}

6.2 TCP服务端

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
import java.net.*;
import java.io.*;

public class Server1 {
public static void main(String[] args) throws Exception {
// 1. 创建ServerSocket对象,同时为服务端注册端口
ServerSocket serverSocket = new ServerSocket(8888);
// 2. 等待客户端请求
Socket socket = serverSocket.accept();
// 3. 从socket中得到字节输入流
InputStream is = socket.getInputStream();
// 4. 封装成数据输入流
DataInputStream dis = new DataInputStream(is);
while (true){
try{
String rs = dis.readUTF();
System.out.println(rs);

}catch (Exception e){
System.out.println(socket.getRemoteSocketAddress() + "离线了");
dis.close();
socket.close();
break;
}
}



}
}

七、TCP通信(多线程改进)

7.1 多线程改进

为了让服务端能够支持多个客户端通信,就需要用到多线程技术。

每当有一个客户端连接服务端,在服务端这边就为Socket开启一条线程取执行读取数据的操作,来多少个客户端,就有多少条线程。

1668315156739

服务端读取数据的线程

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
import java.net.*;
import java.io.*;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}

public void run(){
try{
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while(true){
try{
String msg = dis.readUTF();
System.out.println(msg);
}catch (Exception e){
System.out.println("有人下线了:"+socket.getRemoteSocketAddress());
dis.close();
socket.close();
break;
}
}

}catch(Exception e){
e.printStackTrace();
}
}




}

服务端主程序:

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

public class Server1 {
public static void main(String[] args) throws Exception {
// 1. 创建ServerSocket对象,同时为服务端注册端口
ServerSocket serverSocket = new ServerSocket(8888);

while(true){
// 2. 等待客户端请求
Socket socket = serverSocket.accept();

System.out.println("有人上线了:"+socket.getRemoteSocketAddress());

new ServerReaderThread(socket).start();

}




}
}

八、BS架构程序

1668316417640

在BS结构的程序中,浏览器和服务器通信是基于HTTP协议来完成的,浏览器给客户端发送数据需要按照HTTP协议规定好的数据格式发给服务端,服务端返回数据时也需要按照HTTP协议规定好的数据给是发给浏览器,只有这两双方才能完成一次数据交互。

HTTP协议格式:

1668316630797

8.1 服务端程序

先写一个线程类,用于按照HTTP协议的格式返回数据

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
import java.net.*;
import java.io.*;
public class ServerReadThread1 extends Thread{
private Socket socket;
public ServerReadThread1(Socket socket){
this.socket = socket;
}

public void run(){
//响应:”黑马程序员“给浏览器
try{
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type:text/html;charset=UTF-8");
ps.println();
ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员<div>");
ps.close();
socket.close();



}catch (Exception e){
e.printStackTrace();
}
}



}

服务端主程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.net.*;

public class Server3 {
public static void main(String[] args) throws Exception{
ServerSocket serverSocket = new ServerSocket(8088);
while (true){
Socket socket = serverSocket.accept();
System.out.println("有人上线了:"+socket.getRemoteSocketAddress());
new ServerReadThread1(socket).start();
}
}
}

8.2 服务端主程序用线程池改进

先写一个给浏览器响应数据的线程任务

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
import java.net.*;
import java.io.*;
public class ServerReaderRunnable implements Runnable{
private Socket socket;

public ServerReaderRunnable(Socket socket){
this.socket = socket;


}


@Override
public void run() {
try{
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type:text/html;charset=UTF-8");
ps.println(); // 必须换行
ps.println("<div style='color:red;font-size:120px;text-align:center'>黑马程序员666<div>");
ps.close();
socket.close();

}catch (Exception e){
e.printStackTrace();
}

}
}

服务端的主程序,使用ThreadPoolExecutor创建一个线程池,每次接收到一个Socket就往线程池中提交任务就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.net.ServerSocket;
import java.util.concurrent.*;
import java.net.Socket;

public class Server4 {

public static void main(String[] args) throws Exception{
ServerSocket serverSocket = new ServerSocket(8088);
ThreadPoolExecutor pool = new ThreadPoolExecutor(16*2, 16*2, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(8),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
while(true){
Socket socket = serverSocket.accept();
pool.execute(new ServerReaderRunnable(socket));
}

}
}


JAVASE——网络编程
https://wendyflv.github.io/2024/10/29/JAVASE——网络编程/
作者
Wendyflv
发布于
2024年10月29日
许可协议