06 网络编程基础
目录
1.通信三要素
1. IP地址(Internet Protocol Address)
2. 端口号(Port Number)
3. 协议(Protocol)
2.TCP与UDP协议
三次握手(Three-Way Handshake)
四次挥手(Four-Way Waveoff)
TCP协议编程示例
UDP协议编程示例
1.通信三要素
在网络编程中,通信的三个基本要素是:IP地址、端口号和协议。这三个要素共同确定了一个网络连接的唯一性。下面是对这三个要素的详细解释:
1. IP地址(Internet Protocol Address)
IP地址是互联网协议(Internet Protocol)地址,用于标识网络上的设备。IP地址分为两种主要类型:
-
IPv4:32位地址,通常表示为四个十进制数,每个数之间用点号分隔,例如
192.168.1.1
。 -
IPv6:128位地址,通常表示为八组十六进制数,每组之间用冒号分隔,例如
2001:0db8:85a3:0000:0000:8a2e:0370:7334
。
2. 端口号(Port Number)
端口号是一个16位的数字,用于标识特定的应用程序或服务。端口号范围从0到65535,其中:
-
0-1023
:熟知端口(Well-Known Ports),这些端口号由IANA(Internet Assigned Numbers Authority)分配给特定的服务,例如:
-
HTTP:80
-
HTTPS:443
-
FTP:21
-
SSH:22
-
-
1024-49151:注册端口(Registered Ports),这些端口号可以由用户和应用程序注册使用。
-
49152-65535:动态或私有端口(Dynamic or Private Ports),这些端口号通常由操作系统动态分配给客户端应用程序。
3. 协议(Protocol)
协议定义了数据在网络上传输的方式和格式。常见的网络协议包括:
-
TCP(Transmission Control Protocol):一种面向连接的、可靠的传输协议,用于保证数据的完整性和顺序。TCP通过三次握手建立连接,通过四次挥手断开连接。
-
UDP(User Datagram Protocol):一种无连接的、不可靠的传输协议,适用于实时应用,如视频流和在线游戏。
-
HTTP(Hypertext Transfer Protocol):用于传输超文本的协议,通常在浏览器和Web服务器之间使用。
-
HTTPS(Hypertext Transfer Protocol Secure):HTTP的加密版本,使用SSL/TLS协议进行数据加密。
2.TCP与UDP协议
在网络通信中,TCP(传输控制协议)是一种面向连接的、可靠的传输协议。TCP连接的建立和断开分别通过三次握手和四次挥手来完成。
三次握手(Three-Way Handshake)
三次握手是TCP连接建立的过程,确保双方都准备好进行数据传输。以下是三次握手的步骤:
-
第一次握手:
-
客户端发送一个SYN(同步序列编号)包到服务器,并进入SYN_SEND状态,等待服务器确认。
-
SYN包中包含客户端的初始序列号(ISN),记为
Seq=A
。
-
-
第二次握手:
-
服务器收到客户端的SYN包后,回复一个SYN+ACK(同步确认)包,表示接受连接请求。
-
SYN+ACK包中包含服务器的初始序列号
Seq=B
,以及对客户端SYN包的确认号Ack=A+1
。 -
服务器进入SYN_RECV状态。
-
-
第三次握手:
-
客户端收到服务器的SYN+ACK包后,发送一个ACK(确认)包,确认收到服务器的SYN+ACK包。
-
ACK包中包含对服务器SYN包的确认号
Ack=B+1
,以及自己的序列号Seq=A+1
。 -
客户端进入ESTABLISHED状态。
-
服务器收到客户端的ACK包后,也进入ESTABLISHED状态,连接建立完成。
-
四次挥手(Four-Way Waveoff)
四次挥手是TCP连接断开的过程,确保双方都正确地关闭连接。以下是四次挥手的步骤:
-
第一次挥手:
-
客户端发送一个FIN(结束)包到服务器,表示客户端已经没有数据要发送了。
-
客户端进入FIN_WAIT_1状态。
-
-
第二次挥手:
-
服务器收到客户端的FIN包后,发送一个ACK(确认)包,确认收到客户端的FIN包。
-
ACK包中包含对客户端FIN包的确认号
Ack=A+1
,以及自己的序列号Seq=B
。 -
服务器进入CLOSE_WAIT状态,客户端进入FIN_WAIT_2状态。
-
-
第三次挥手:
-
服务器发送一个FIN包到客户端,表示服务器也没有数据要发送了。
-
服务器进入LAST_ACK状态。
-
-
第四次挥手:
-
客户端收到服务器的FIN包后,发送一个ACK包,确认收到服务器的FIN包。
-
ACK包中包含对服务器FIN包的确认号
Ack=B+1
,以及自己的序列号Seq=A+1
。 -
客户端进入TIME_WAIT状态,等待2MSL(最大段生命周期)后完全关闭连接。
-
服务器收到客户端的ACK包后,进入CLOSED状态,连接完全关闭。
-
TCP协议编程示例
客户端
public class SocketClient { public static void main(String[] args) throws IOException { // 创建socketdu对象,指明服务器地址和端口 Socket socket = new Socket("localhost", 9999); System.out.println("连接成功!"); // 向服务器发送数据 OutputStream outputStream = socket.getOutputStream(); outputStream.write("Hello, 经验宝宝!".getBytes()); System.out.println("数据发送成功!"); //给服务端写一个结束标记 socket.shutdownOutput(); System.out.println("======以下代码是读取响应的结果======"); // 接收服务器返回的数据 InputStream inputStream = socket.getInputStream(); byte[] buffer = new byte[1024]; int len; while ((len = inputStream.read(buffer))!= -1) { System.out.println(new String(buffer, 0, len)); } System.out.println("数据接收成功!"); // 关闭流 inputStream.close(); outputStream.close(); // 关闭socket连接 socket.close(); } }
服务端
public class SocketServer { public static void main(String[] args) throws Exception { // 创建服务器socket对象 ServerSocket sockServer = new ServerSocket(9999); System.out.println("服务器启动成功!"); // 等待客户端连接 Socket socket = sockServer.accept(); System.out.println("客户端连接成功!"); // 使用socket中的输入输出流进行通信,处理客户端请求 InputStream inputStream = socket.getInputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inputStream.read(buffer))!= -1) { System.out.println(new String(buffer, 0, len)); } // 发送响应数据 OutputStream outputStream = socket.getOutputStream(); String response = "Hello, 铁头娃!"; outputStream.write(response.getBytes()); // 关闭输入输出流 outputStream.close(); inputStream.close(); // 关闭服务器socket对象 socket.close(); sockServer.close(); } }
在网络编程中,客户端和服务器之间的通信需要明确的数据边界。如果客户端发送的数据没有明确的结束标记,服务器可能会一直等待更多的数据,而客户端则可能因为没有收到响应而卡住。解决这个问题的方法有几种:
-
固定长度的消息:客户端和服务器之间约定每条消息的固定长度。
-
特殊字符作为结束标记:客户端在消息末尾添加一个特殊的结束标记,服务器在读取到这个标记后停止读取。
-
数据包大小作为前缀:客户端在发送消息之前先发送消息的长度,服务器根据这个长度读取完整的消息。
通过这些方法,你可以确保客户端和服务器之间的通信具有明确的数据边界,避免因缺少结束标记而导致的问题。
UDP协议编程示例
发送端
public class DataGramSend { public static void main(String[] args) throws Exception { // 创建一个DatagramSocket,用于发送数据报 // 无参:默认创建的DatagramSocket的端口号是0,表示系统自动分配一个可用端口号 // 有参:创建的DatagramSocket的端口号是指定的端口号 DatagramSocket socket = new DatagramSocket(); System.out.println("DatagramSocket创建成功!"); // 创建DatagramPacket,用于封装要发送的数据 // 第一个参数:发送的数据 // 第二个参数:发送数据的长度 // 第三个参数:接收方的IP地址 // 第四个参数:接收方的端口号 byte[] data = "Hello, 菊花侠!".getBytes(); DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("127.0.0.1"), 8888); System.out.println("DatagramPacket创建成功!"); // 发送数据报 socket.send(packet); System.out.println("数据报发送成功!"); // 关闭DatagramSocket socket.close(); System.out.println("DatagramSocket关闭成功!"); } }
注意:UDP是不可靠的、无连接的通信,即使在没有接收端的情况下发送端也可以发送数据。
接收端
public class DataGramReceive { public static void main(String[] args) throws Exception { // 创建 DatagramSocket,用于接收数据报 DatagramSocket socket = new DatagramSocket(8888); // 创建 byte 数组,用于接收数据 byte[] buffer = new byte[1024]; // 接收数据报,并将数据写入 buffer DatagramPacket packet = new DatagramPacket(buffer, buffer.length); socket.receive(packet); // 打印接收到的内容 String message = new String(packet.getData(), 0, packet.getLength()); System.out.println("接收到的数据报:" + message); // 关闭 DatagramSocket socket.close(); } }