Java网络通信-第21章
1.网络程序设计基础
网络程序设计基础涵盖了许多方面,包括网络协议、Web开发、数据库连接、安全性等。
1.1局域网与互联网
局域网(LAN)与互联网(Internet)是两个不同的概念,它们分别用于描述不同范围内的网络连接。
- 局域网(LAN):
- 定义: 局域网是指在一个相对较小的地理范围内连接在一起的计算机网络,如家庭、学校、办公室或公司。
- 范围: 通常,局域网覆盖的范围限制在一个建筑物或者一组相邻的建筑物内。
- 连接设备: 局域网内的设备可以通过局域网技术(如以太网、Wi-Fi等)相互通信,共享资源,比如打印机、文件等。
- 互联网(Internet):
- 定义: 互联网是一个全球性的计算机网络,它连接了世界上的数以亿计的设备,允许它们之间进行通信和数据交换。
- 范围: 互联网没有地理限制,覆盖全球范围。
- 连接设备: 互联网连接了各种设备,包括个人计算机、智能手机、服务器等。它是通过一系列的互联网服务提供商(ISP)相互连接起来的。
- 关系:
- 局域网是构建在较小的地理范围内,用于组织内部设备之间的通信。
- 互联网是一个覆盖全球的网络,将不同地理位置的局域网连接起来,使得全球范围内的设备能够相互通信。
- 通信方式:
- 局域网内部的通信通常更快、更可靠,而且通常不需要经过公共互联网。
- 互联网通信需要经过公共的基础设施,如因特网服务提供商(ISP),数据在全球范围内传输。
1.2网络协议
网络协议是计算机网络中设备之间进行通信和数据交换的规则和约定。它们定义了数据的格式、传输方法、错误检测等方面的规范,确保不同厂商和不同类型的设备能够在网络中互相通信。
1.IP协议
IP(Internet Protocol)是计算机网络中的一种基础协议,用于在网络中传输数据包。IP协议定义了数据包的格式和规则,确保数据在网络中正确传递。目前广泛使用的IP版本有两个:IPv4(Internet Protocol version 4)和IPv6(Internet Protocol version 6)。
IPv4(Internet Protocol version 4):
- 地址格式: IPv4地址是32位的,通常以点分十进制表示,如
192.168.0.1
。 - 地址空间: IPv4提供了大约42亿个唯一的地址,由于互联网的快速发展,IPv4地址空间已经不够用,导致IPv4地址枯竭问题。
- 子网: 为了更好地管理IP地址,IPv4引入了子网的概念,允许将一个大的IP地址块划分成多个小的子网。
- 私有地址范围: IPv4定义了一些私有地址范围,例如
192.168.0.0/16
,这些地址通常用于内部网络。
IPv6(Internet Protocol version 6):
- 地址格式: IPv6地址是128位的,通常以冒号分隔的十六进制表示,如
2001:0db8:85a3:0000:0000:8a2e:0370:7334
。 - 地址空间: IPv6提供了远远超过IPv4的地址空间,理论上足够支持未来互联网的发展需求。
- 简化报头: IPv6在报头中做了一些简化,减少了一些字段,提高了路由和转发效率。
- 无需NAT: 由于IPv6地址空间足够大,通常无需使用网络地址转换(NAT)技术,简化了网络配置和管理。
共同点:
- 数据包传输: IPv4和IPv6都是网络层协议,负责将数据包从源主机传输到目标主机。
- 逐跳路由: 在网络中,数据包通过一系列的路由器逐跳传输,最终到达目标主机。
由于IPv4地址枯竭的问题,全球范围内正在逐渐过渡到IPv6。IPv6提供了更大的地址空间,更好的路由和转发效率,以及更简化的网络配置。在实际应用中,IPv4和IPv6可能同时存在,而且网络设备和应用程序需要同时支持这两种协议,这被称为双栈(Dual Stack)支持。
2.TCP与UDP协议
TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是两种常见的传输层协议,它们定义了在计算机网络中数据如何被传输和接收的规则。以下是它们的主要特点和区别:
TCP(Transmission Control Protocol):
- 连接导向: TCP是面向连接的协议,建立了一条双向的通信路径,确保数据的可靠传输。
- 可靠性: TCP提供可靠的数据传输,通过使用确认机制和重传机制来确保数据的完整性和顺序性。
- 流控制: TCP通过流控制机制来控制数据的发送速率,防止接收方被过多的数据淹没。
- 拥塞控制: TCP使用拥塞控制算法,当网络拥塞时会调整发送速率,以避免网络的过载。
- 面向字节: TCP将数据视为一连串的字节流,而非独立的数据包。
UDP(User Datagram Protocol):
- 无连接: UDP是面向无连接的协议,通信双方在传输数据前不需要建立连接。
- 不可靠性: UDP不提供可靠的数据传输,不保证数据的完整性和顺序性,也不提供重传机制。
- 无流控制: UDP不提供流控制机制,因此发送方会一直以固定速率发送数据,而不会根据接收方的处理能力进行调整。
- 无拥塞控制: UDP不具备拥塞控制机制,因此在网络拥塞时可能会丢失数据包。
- 面向数据报: UDP将数据视为独立的数据包,而非字节流。
共同点:
- 端口: TCP和UDP都使用端口来标识应用程序,使得数据能够正确地交付到目标应用。
- 传输层协议: TCP和UDP都属于传输层协议,负责将数据从一个端点传输到另一个端点。
选择使用场景:
- TCP适用于:
- 需要可靠数据传输的应用,如文件传输、电子邮件等。
- 需要确保数据按顺序到达的应用,如视频流、Web页面加载等。
- UDP适用于:
- 实时性要求较高,能够容忍一定程度的数据丢失的应用,如实时语音、视频通话等。
- 需要较低的通信延迟,对数据可靠性要求不高的应用,如在线游戏。
3.端口与套接字
端口(Port)和套接字(Socket)是计算机网络中重要的概念,它们在网络通信中起到关键的作用。
端口(Port):
- 定义: 端口是一个逻辑上的概念,用于标识一台计算机中运行的特定应用程序或服务。它允许一台计算机上的不同应用程序通过网络进行通信,每个应用程序都被分配一个唯一的端口号。
- 范围: 端口号范围从0到65535,其中0到1023的端口号被称为「系统端口」或「知名端口」,它们通常用于一些常见的服务(如HTTP使用的80端口,FTP使用的21端口等)。1024到49151的端口号是「注册端口」,用于用户应用。49152到65535的端口号是「动态或私有端口」,它们用于动态分配,通常由客户端应用程序使用。
套接字(Socket):
- 定义: 套接字是支持网络通信的编程接口,它允许进程通过网络发送和接收数据。套接字由IP地址和端口号组成,标识了通信中的两个端点。套接字可以用于在同一台计算机上的进程间通信,也可以用于在不同计算机之间进行网络通信。
- 类型: 套接字可以分为两种类型:流套接字(Stream Socket)和数据报套接字(Datagram Socket)。
- 流套接字: 提供面向连接的、可靠的、基于字节流的通信,使用TCP协议。
- 数据报套接字: 提供无连接的、不可靠的、基于数据包的通信,使用UDP协议。
套接字编程通常包括创建套接字、绑定到一个特定的IP地址和端口号、监听连接请求(对于服务器端)、接受连接(对于服务器端)、连接到服务器(对于客户端)、发送和接收数据等步骤。
2.TCP程序
TCP(Transmission Control Protocol)是一种面向连接的、可靠的协议,常用于网络通信中。
2.1InetAddress类
InetAddress
类是 Java 中用于表示 IP 地址的类。它位于 java.net
包中,提供了一种在网络上标识主机的方法。InetAddress
类主要用于获取主机的 IP 地址和主机名。
以下是 InetAddress
类的一些主要方法和用法:
-
获取本地主机的 InetAddress 对象:
InetAddress localHost = InetAddress.getLocalHost();
-
根据主机名获取 InetAddress 对象:
String hostName = "example.com"; InetAddress address = InetAddress.getByName(hostName);
-
获取主机的 IP 地址:
byte[] ipAddress = address.getAddress(); // 返回字节数组形式的 IP 地址 String ipAddressStr = address.getHostAddress(); // 返回字符串形式的 IP 地址
-
获取主机名:
String hostName = address.getHostName();
-
判断是否是 IP 地址:
boolean isIP = InetAddressUtils.isIPv4Address(ipAddressStr); // 可以使用 InetAddressUtils 类中的方法判断 IPv4 或 IPv6 地址
InetAddress
类的使用可以帮助我们进行网络编程中的主机信息获取和处理。请注意,它主要用于获取网络上的主机信息,而不涉及与网络通信的具体操作。如果需要进行网络通信,通常需要使用 Socket
或 URL
等类。
2.2ServerSocket类
ServerSocket
类是 Java 中用于创建服务器套接字的类,它位于 java.net
包中。通过 ServerSocket
,你可以监听特定端口,接受客户端的连接请求,并与客户端进行通信。以下是一些 ServerSocket
类的常见用法:
创建 ServerSocket 对象:
int port = 8080;
ServerSocket serverSocket = new ServerSocket(port);
接受客户端连接:
Socket clientSocket = serverSocket.accept();
这个方法会阻塞程序的执行,直到有客户端请求连接。一旦有连接请求,它会返回一个新的 Socket
对象,该对象用于与客户端进行通信。
获取输入输出流进行通信:
javaCopy codeInputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream();
使用这些流,你可以读取来自客户端的数据和向客户端发送数据。
关闭 ServerSocket:
javaCopy code
serverSocket.close();
当服务器不再需要监听新的连接时,可以关闭 ServerSocket
。
以下是一个简单的示例,演示了 ServerSocket
的基本用法:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleServer {
public static void main(String[] args) {
int port = 8080;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("Server is listening on port " + port);
// 等待客户端连接
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected");
// 获取输入输出流
InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream();
// 读取客户端发送的数据
byte[] buffer = new byte[1024];
int bytesRead = inputStream.read(buffer);
String clientMessage = new String(buffer, 0, bytesRead);
System.out.println("Client message: " + clientMessage);
// 向客户端发送响应
String responseMessage = "Hello, client!";
outputStream.write(responseMessage.getBytes());
// 关闭连接
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意,这只是一个简单的例子,实际上,服务器通常需要在不同的线程中处理多个客户端连接,以避免阻塞。此外,网络编程涉及异常处理等更复杂的问题。
2.3TCP网络程序设计
设计 TCP 网络程序通常涉及两个主要角色:服务器端和客户端。TCP(传输控制协议)是一种面向连接的协议,它提供可靠的、有序的、基于字节流的双向数据传输。下面分别介绍服务器端和客户端的设计。
服务器端(TCP Server)
-
创建 ServerSocket 对象:
ServerSocket serverSocket = new ServerSocket(port);
-
等待客户端连接:
Socket clientSocket = serverSocket.accept();
-
获取输入输出流:
InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream();
-
处理客户端发送的数据:
// 读取数据 byte[] buffer = new byte[1024]; int bytesRead = inputStream.read(buffer); String clientMessage = new String(buffer, 0, bytesRead); // 处理数据,执行业务逻辑 // 发送响应 String responseMessage = "Hello, client!"; outputStream.write(responseMessage.getBytes());
-
关闭连接:
clientSocket.close();
-
异常处理: 在实际应用中,需要添加适当的异常处理,以确保程序在发生异常时能够正确地处理和关闭资源。
客户端(TCP Client)
-
创建 Socket 对象连接服务器:
Socket socket = new Socket(serverAddress, serverPort);
-
获取输入输出流:
InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream();
-
发送数据给服务器:
// 发送数据 String message = "Hello, server!"; outputStream.write(message.getBytes());
-
接收服务器响应:
// 接收数据 byte[] buffer = new byte[1024]; int bytesRead = inputStream.read(buffer); String serverResponse = new String(buffer, 0, bytesRead); System.out.println("Server response: " + serverResponse);
-
关闭连接:
socket.close();
-
异常处理: 同样,需要适当地处理可能出现的异常。
示例
以下是一个简单的示例,演示了一个简单的 TCP 服务器和客户端:
TCP Server:
import java.io.*;
import java.net.*;
public class TCPServer {
public static void main(String[] args) {
int port = 8080;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("Server is listening on port " + port);
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected");
InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream();
byte[] buffer = new byte[1024];
int bytesRead = inputStream.read(buffer);
String clientMessage = new String(buffer, 0, bytesRead);
System.out.println("Client message: " + clientMessage);
String responseMessage = "Hello, client!";
outputStream.write(responseMessage.getBytes());
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
TCP Client:
import java.io.*;
import java.net.*;
public class TCPClient {
public static void main(String[] args) {
String serverAddress = "localhost";
int serverPort = 8080;
try (Socket socket = new Socket(serverAddress, serverPort)) {
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
String message = "Hello, server!";
outputStream.write(message.getBytes());
byte[] buffer = new byte[1024];
int bytesRead = inputStream.read(buffer);
String serverResponse = new String(buffer, 0, bytesRead);
System.out.println("Server response: " + serverResponse);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这只是一个简单的例子,实际上,我们可能需要在不同的线程中处理多个客户端连接,以及添加更复杂的业务逻辑和异常处理。
运行结果
TCP Server:
TCP Client:
3.UDP程序
UDP(User Datagram Protocol)是一种无连接、不可靠、简单的面向数据报的协议。UDP 提供了一种轻量级的数据传输机制,适用于一些对实时性要求较高、容忍数据丢失的应用场景。
3.1DatagramPacket类
DatagramPacket
类是 Java 中用于表示数据报包的类,通常用于 UDP 协议的通信。DatagramPacket
封装了数据和目标主机信息,包括目标主机的 IP 地址和端口号。这类被用于在网络上发送和接收数据报。
构造方法:
-
发送数据时的构造方法:
DatagramPacket(byte[] data, int length, InetAddress address, int port)
data
:要发送的数据,以字节数组形式提供。length
:要发送的数据的长度。address
:目标主机的 IP 地址。port
:目标主机的端口号。
-
接收数据时的构造方法:
javaCopy code DatagramPacket(byte[] data, int length)
data
:用于接收数据的字节数组。length
:接收数据的最大长度。
常用方法:
-
获取数据:
byte[] getData()
-
获取数据长度:
int getLength()
-
获取数据报包的源地址:
InetAddress getAddress()
-
获取数据报包的源端口号:
int getPort()
-
设置数据报包的目标地址和端口号:
setAddress(InetAddress address) setPort(int port)
3.2DatagramSocket类
DatagramSocket
类是 Java 中用于进行 UDP(User Datagram Protocol)通信的套接字类。它提供了在网络上发送和接收数据报(DatagramPacket
)的方法。与 TCP 不同,UDP 是一种无连接、不可靠的协议,但它更加轻量且适用于一些实时性要求较高的应用场景。
构造方法:
-
创建 DatagramSocket 实例:
javaCopy code DatagramSocket socket = new DatagramSocket();
创建一个新的
DatagramSocket
实例,用于与其他主机进行 UDP 通信。 -
指定端口号创建 DatagramSocket:
DatagramSocket socket = new DatagramSocket(port);
创建一个监听指定端口的
DatagramSocket
实例,用于接收数据。 -
指定本地地址和端口创建 DatagramSocket:
DatagramSocket socket = new DatagramSocket(port, InetAddress.getLocalHost());
创建一个绑定到指定本地地址和端口的
DatagramSocket
实例。
常用方法:
-
发送数据报:
send(DatagramPacket packet)
通过
DatagramSocket
发送数据报。 -
接收数据报:
receive(DatagramPacket packet)
通过
DatagramSocket
接收数据报。 -
设置超时时间:
setSoTimeout(int timeout)
设置
DatagramSocket
的超时时间,用于控制在接收时等待数据的最长时间。 -
关闭 DatagramSocket:
close()
关闭
DatagramSocket
,释放相关资源。
3.3UDP网络序设计
设计 UDP(User Datagram Protocol)网络程序通常涉及两个主要角色:服务器端和客户端。UDP 是一种无连接、不可靠的协议,适用于对实时性要求较高、能够容忍一定数据丢失的应用场景。以下是 UDP 服务器端和客户端的基本设计步骤:
UDP 服务器端设计:
-
创建 DatagramSocket 对象:
DatagramSocket serverSocket = new DatagramSocket(port);
创建一个
DatagramSocket
实例来监听指定端口。 -
创建 DatagramPacket 对象用于接收数据:
byte[] receiveData = new byte[1024]; DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
用于接收从客户端发送过来的数据。
-
接收客户端数据:
serverSocket.receive(receivePacket);
通过
DatagramSocket
接收数据报。 -
处理客户端数据:
String clientMessage = new String(receivePacket.getData(), 0, receivePacket.getLength()); // 处理客户端数据...
处理接收到的客户端数据,执行相应的业务逻辑。
-
发送响应给客户端(可选):
InetAddress clientAddress = receivePacket.getAddress(); int clientPort = receivePacket.getPort(); String responseMessage = "Hello, client!"; byte[] sendData = responseMessage.getBytes(); DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, clientAddress, clientPort); serverSocket.send(sendPacket);
如果需要,可以通过
DatagramSocket
发送响应给客户端。 -
关闭 DatagramSocket:
serverSocket.close();
在不再需要监听时关闭
DatagramSocket
。
UDP 客户端设计:
-
创建 DatagramSocket 对象:
DatagramSocket clientSocket = new DatagramSocket();
创建一个
DatagramSocket
实例,用于发送和接收数据。 -
创建 DatagramPacket 对象用于发送数据:
String message = "Hello, server!"; byte[] sendData = message.getBytes(); InetAddress serverAddress = InetAddress.getByName(serverHost); int serverPort = 9876; // 服务器端口号 DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, serverAddress, serverPort);
用于将数据发送给服务器。
-
发送数据给服务器:
clientSocket.send(sendPacket);
通过
DatagramSocket
发送数据报给服务器。 -
创建 DatagramPacket 对象用于接收服务器响应:
byte[] receiveData = new byte[1024]; DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
用于接收从服务器返回的数据。
-
接收服务器响应:
clientSocket.receive(receivePacket); String serverResponse = new String(receivePacket.getData(), 0, receivePacket.getLength()); System.out.println("Server response: " + serverResponse);
通过
DatagramSocket
接收服务器的响应。 -
关闭 DatagramSocket:
clientSocket.close();
在不再需要发送和接收数据时关闭
DatagramSocket
。
示例:
以下是一个简单的 UDP 服务器端和客户端的示例:
UDP Server:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPServer {
public static void main(String[] args) {
int port = 9876;
try (DatagramSocket serverSocket = new DatagramSocket(port)) {
System.out.println("Server is listening on port " + port);
byte[] receiveData = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
serverSocket.receive(receivePacket);
String clientMessage = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("Client message: " + clientMessage);
// 处理客户端数据...
} catch (Exception e) {
e.printStackTrace();
}
}
}
UDP Client:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPClient {
public static void main(String[] args) {
String serverHost = "localhost";
int serverPort = 9876;
try (DatagramSocket clientSocket = new DatagramSocket()) {
String message = "Hello, server!";
byte[] sendData = message.getBytes();
InetAddress serverAddress = InetAddress.getByName(serverHost);
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, serverAddress, serverPort);
clientSocket.send(sendPacket);
byte[] receiveData = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
clientSocket.receive(receivePacket);
String serverResponse = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("Server response: " + serverResponse);
} catch (Exception e) {
e.printStackTrace();
}
}
}
在实际应用中,需要注意异常处理、超时机制、数据的序列化和反序列化等方面,以确保程序的稳定性和可靠性。