如今,计算机网络已经成为人们日常生活的必需品,无论是工作时发送邮件,还是在休闲时和朋友网上聊天都离不开计算机网络。所谓的计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统、网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。位于同一个网络中的计算机若想实现彼此间的通信,可以通过编写网络程序来实现,即在不同的计算机上编写一些实现了网络连接的程序,通过这些程序可以实现不同计算机之间数据的交互。本章将重点介绍网络通信的相关知识以及网络程序的编写。
1. 网络基础
1.1. 网络通信协议
为了提供通信支持,位于同一个网络中的计算机在进行连接和通信时必须要遵守一定的规则,这些规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交互。网络通信协议有很多种,本章所学的网络编程知识,主要就是基于TCP/IP协议中的内容。
(1)TCP/IP协议
TCP/IP(又称TCP/IP协议簇)是一组用于实现网络互连的通信协议,其名称来源于该协议簇中两个重要的协议(TCP协议和IP协议)。基于TCP/IP的网络参考模型将协议分成四个层次,如图所示。
(2)TCP/IP协议中的四个层次
TCP/IP协议中的四个层次从最下层到最上层依次是链路层、网络层、传输层和应用层,每层分别负责不同的通信功能。
链路层:链路层也称为数据链路层或网络接口层,通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡。它们一起处理与电缆或其他传输媒介有关的物理接口细节。
网络层:也称网络互联层,是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。网络层对TCP/IP网络中的硬件资源进行标识。
传输层:在TCP/IP网络中,不同的机器之间进行通信,数据的传输是由传输层控制的,这包括数据要发往的目的主机及应用程序、数据的质量控制等。TCP/IP网络中最常用的传输协议TCP和UDP就应用于这一层。传输层通常以TCP或UDP来控制端点到端点的通信。用于通信的端点由Socket定义,而Socket由IP地址和端口号组成。
应用层:主要负责应用程序的协议。大多数基于Internet的应用程序都被看作TCP/IP的应用层协议,如HTTP协议、FTP协议、SMTP协议、Telnet协议等。
1.2. UDP与TCP协议
(1)UDP协议
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在就会发出数据。同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
UDP连接的交互过程如下图所示:
由于UDP协议消耗资源小,通信效率高,所以UDP协议通常都会用于音频、视频和普通数据的传输,但是在使用UDP协议传送数据时,因为UDP具有面向无连接性,不能保证数据的完整性,所以在传输重要数据时不建议使用UDP协议。
(2)TCP协议
TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务器端发出连接请求,每次连接的创建都需要经过"三次握手"。因为TCP协议拥有面向连接特性,所以它可以保证传输数据的安全性,是一个被广泛采用的协议。例如文件传输。
TCP连接的交互过程如下图所示:
第一次握手,客户端向服务器端发出连接请求,等待服务器确认;
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求;
第三次握手,客户端再次向服务器端发送确认信息,确认连接。
2. TCP通信
2.1. ServerSocket类
在Socket编程中,编写服务器端程序需要使用ServerSocket类。ServerSocket类在java.net包中,java.net. ServerSocket继承自java.lang.Object类。ServerSocket类的主要作用是接收客户端的连接请求。
(1)ServerSocket类的构造方法
通过查阅API文档可知,ServerSocket类提供了多种构造方法,如表所示。
|-----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
| 构造方法 | 功能描述 |
| ServerSocket() | 通过该方法创建的ServerSocket对象不与任何端口绑定,这样的ServerSocket对象创建的服务器端没有监听任何端口,不能直接使用,还需要继续调用bind(SocketAddress endpoint)方法将其绑定到指定的端口号上,才可以正常使用。 |
| ServerSocket(int port) | 该方法的作用是以端口port创建ServerSocket对象,并等待客户端的连接请求。最常用的构造方法。 |
| ServerSocket(int port, int backlog) | 该构造方法在第二个构造方法的基础上,增加了一个backlog参数。该参数用于指定最大连接数,即可以同时连接的客户端数量。 |
| ServerSocket(int port, int backlog, InetAddress bindAddr) | 该构造方法在第三个构造方法的基础上,增加了一个bindAddr参数,该参数用于指定相关的IP地址。 |
(2)ServerSocket类的常用方法
了解了ServerSocket类的构造方法后,下面学习一下ServerSocket类的常用方法,如表所示。
|-----------------------------------|---------------------------------------------------------------|
| 方法名称 | 功能描述 |
| Socket accept() | 该方法用于等待客户端的连接,在客户端连接之前会一直处于阻塞状态,如果有客户端连接,就会返回一个与之对应的Socket对象。 |
| InetAddress getInetAddress() | 该方法用于返回一个InetAddress对象,该对象中封装了ServerSocket绑定的IP地址。 |
| boolean isClosed() | 该方法用于判断ServerSocket对象是否为关闭状态,如果是关闭状态则返回true,反之则返回false。 |
| void bind(SocketAddress endpoint) | 该方法用于将ServerSocket对象绑定到指定的IP地址和端口号,其中参数endpoint 封装了IP 地址和端口号。 |
2.2. Socket类
Socket类在java.net包中定义,java.net.Socket继承自java.lang.Object类。Socket类用于编写客户端程序,用户通过创建一个Socket对象建立与服务器的连接。
(1)Socket类的构造方法
通过查阅API文档可知,Socket类的构造方法,如表所示。
|------------------------------------------------------|------------------------------------------------------------------------|
| 构造方法 | 功能描述 |
| Socket() | 使用该构造方法在创建Socket对象时,并没有指定IP地址和端口号,也就意味着只创建了客户端对象,并没有去连接任何服务器。最常用的构造方法。 |
| Socket(String host, int port) | 该构造方法用于在客户端以指定的服务器地址host和端口号port创建一个Socket对象,并向服务器端发出连接请求。 |
| Socket(InetAddress address, int port) | 创建一个流套接字,并将其连接到指定IP地址的指定端口 |
| Socket(InetAddress address, int port,boolean stream) | 该构造方法在使用上与第二个构造方法类似,但IP地址由host指定。 |
(2)Socket类的常用方法
了解了Socket类的构造方法后,下面学习一下Socket类的常用方法,如表所示。
|--------------------------------|-----------------------------------------------------------------------------------------|
| 方法名称 | 功能描述 |
| int getPort() | 该方法用于获取Socket对象与服务器端连接的端口号。 |
| InetAddress getLocalAddress() | 该方法用于获取Socket对象绑定的本地IP地址,并将IP地址封装成InetAddress类型的对象返回。 |
| InetAddress getInetAddress() | 该方法用于获取创建Socket对象时指定的服务器的IP地址。 |
| void close() | 该方法用于关闭Socket连接,结束本次通信。在关闭Socket之前,应将与Socket相关的所有的输入输出流全部关闭,这是因为一个良好的程序应该在执行完毕时释放所有的资源。 |
| InputStream getInputStream() | 该方法返回一个InputStream类型的输入流对象,如果该输入流对象是由服务器端的Socket返回,就用于读取客户端发送的数据,反之,用于读取服务器端发送的数据。 |
| OutputStream getOutputStream() | 该方法返回一个OutputStream类型的输出流对象,如果该输出流对象是由服务器端的Socket返回,就用于向客户端发送数据,反之,用于向服务器端发送数据。 |
表中列举了Socket类的常用方法,其中getInputStream()方法和getOutputStream()方法分别用于获取输入流和输出流。
2.3. TCP通信过程
当客户端和服务器端建立连接后,数据以IO流的形式进行交互,从而实现通信。服务器端和客户端的数据传输过程,如下图所示。
2.4. TCP通信案例
下面通过一个TCP通信的案例进一步学习ServerSocket类和Socket类的用法。要实现TCP通信需要创建一个服务器端程序和一个客户端程序。具体步骤如下。
(1)服务器端程序,代码如下所示:
java
public class TCPServer {
public static void main(String[] args) throws Exception {
//创建ServerSocket对象并指定端口号(8080)
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("服务器正在运行,等待与客户端连接");
Socket client = serverSocket.accept(); //程序阻塞,等待客户端连接
OutputStream os = client.getOutputStream(); //获取客户端的输出流
System.out.println("开始与客户端交互数据...");
// 当客户端连接到服务器端时,向客户端输出数据
os.write(("你好,潍坊理工学院!").getBytes());
Thread.sleep(5000); //模拟执行其他功能占用的时间
System.out.println("结束与客户端交互数据...");
os.close();
client.close();
}
}
(2)客户端程序,代码如下所示:
java
public class TCPClient {
public static void main(String[] args) throws Exception {
Socket client = new Socket("localhost",8080); //指定连接的主机端口号
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())); //取得客户端的输入流
String str = br.readLine(); //读取信息
System.out.println("服务器端发送内容:"+str);
client.close(); //关闭Socket
br.close(); //关闭输入流
}
}
3. UDP通信
UDP是一种面向无连接的协议,因此在通信时发送端和接收端不用建立连接。UDP通信的过程就像是货运公司在两个码头间发送货物一样,DatagramPacket用于封装要发送的数据的"集装箱",DatagramSocket用于完成数据的发送与接收的"码头"。
3.1. DatagramPacket类
DatagramPacket类用于封装UDP通信中发送或者接收的数据,DatagramPacket类对象也称为数据报对象。利用UDP通信时,发送端使用DatagramPacket类将数据打包,即用DatagramPacket类创建一个数据报对象,这个数据报对象包含有需要传输的数据、数据报的长度、IP地址和端口号等信息。
(1)DatagramPacket类的构造函数
想要创建一个DatagramPacket对象,首先需要了解一下它的构造方法。在创建发送端和接收端的DatagramPacket对象时,使用的构造方法有所不同,接收端的构造方法只需要接收一个字节数组作为参数,用于存放接收到的数据;而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定发送端IP地址和端口号。
|------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
| 方法声明 | 功能描述 |
| DatagramPacket(byte[] buf,int length) | 用于创建一个接收端的数据报对象,buf数组用于接收发送端发送过来的数据报中的数据,接收长度为length。没有指定IP地址和端口号。这样的对象只能用于接收端,不能用于发送端。因为发送端一定要明确指出数据的目的地(IP地址和端口号),而接收端不需要明确知道数据的来源,只需要接收到数据即可。 |
| DatagramPacket(byte[] buf,int length,InetAddress addr,int port) | 创建一个用于发送给远程系统的数据报对象,并将数组buf中长度为length的数据发送到地址为address、端口号为port的主机上。创建的数据报对象通常用于发送端。 |
| DatagramPacket(byte[] buf,int offset,int length) | 该构造方法与第一个构造方法类似,同样用于接收端,只不过在第一个构造方法的基础上,增加了一个offset参数,该参数用于指定接收到的数据在放入buf缓冲数组时是从offset索引处开始的。 |
| DatagramPacket(byte[] buf,int offset,int length,InetAddress addr,int port) | 该构造方法与第二个构造方法类似,同样用于发送端,只不过在第二个构造方法的基础上,增加了一个offset参数,该参数用于指定一个从数组的offset索引处开始发送数据。 |
(2)DatagramPacket类的常用方法
讲解了DatagramPacket类的构造方法,下面对该类中的常用方法进行详细讲解,如表所示。
|--------------------------|--------------------------------------------------------------------------|
| 方法 | 功能描述 |
| InetAddress getAddress() | 该方法用于返回发送端或者接收端的IP地址,如果是发送端的DatagramPacket对象,就返回接收端的IP地址,反之,就返回发送端的IP地址 |
| int getPort() | 该方法用于返回发送端或者接收端的端口号,如果是发送端的DatagramPacket对象,就返回接收端的端口号;反之,就返回发送端的端口号 |
| byte[] getData() | 该方法用于返回将要接收或者将要发送的数据,如果是发送端的DatagramPacket对象,就返回将要发送的数据;反之,就返回接收到的数据 |
| int getLength() | 该方法用于返回接收或者将要发送数据的长度,如果是发送端的DatagramPacket对象,就返回将要发送的数据长度;反之,就返回接收到数据的长度 |
表中列举了DatagramPacket类的四个常用方法及其功能,通过这四个方法,可以获取发送或者接收到的DatagramPacket数据报中的信息。
3.2. DatagramSocket类
DatagramSocket类用于在发送主机中建立数据报通信方式,提出发送请求,实现数据报的发送与接收。在创建发送端和接收端的DatagramSocket对象时,使用的构造方法也有所不同。
(1)DatagramSocket类的构造方法
下面对DatagramSocket类中的构造方法进行讲解,如表所示。
|-------------------------------------------|------------------------------------------------------------------------------------------------------------|
| 方法声明 | 功能描述 |
| DatagramSocket() | 该构造方法用于创建发送端的DatagramSocket对象,在创建DatagramSocket对象时,并没有指定端口号,系统会分配一个没有被其他网络程序所使用的端口号 |
| DatagramSocket(int port) | 该构造方法既可用于创建接收端的DatagramSocket对象,又可以创建发送端的DatagramSocket对象,在创建接收端的DatagramSocket对象时,必须要指定一个端口号,这样就可以监听指定的端口 |
| DatagramSocket(int port,InetAddress addr) | 该构造方法用于在有多个IP地址的当前主机上,创建一个以laddr为指定IP地址、以port为指定端口的数据报连接 |
(2)DatagramSocket类的常用方法
除了构造方法,DatagramSocket类还提供了其他方法,用于实现UDP通信,如表所示。
|--------------------------------|------------------------------------------------------------------------------------------|
| 方法声明 | 功能描述 |
| void receive(DatagramPacket p) | 该方法用于接收数据,并将接收到的数据保存到DatagramPacket数据报中,在接收到数据之前,receive()方法会一直处于阻塞状态,只有当接收到数据报时,该方法才会返回 |
| void send(DatagramPacket p) | 该方法用于发送DatagramPacket数据报,将数据报中包含的报文发送到所指定的IP地址主机 |
| void setSoTimeout(int timeout) | 设置传输数据时超时时间为timeout |
| void close() | 关闭数据报连接 |
3.3. UDP通信过程
(1)UDP通信中数据报的发送过程
- 创建一个用于发送数据报的DatagramPacket对象,使其包含如下信息。
要发送的数据;
数据报分组的长度;
发送目的地的主机IP地址和目的端口号。
-
在指定的或可用的本机端口创建DatagramSocket对象。
-
调用DatagramSocket对象的send()方法,以DatagramPacket对象为参数发送数据报。
(2)UDP通信中数据报的接收过程
-
创建一个用于接收数据报的DatagramSocket对象,其中包含空白数据缓冲区和指定数据报分组的长度。
-
在指定的或可用的本机端口创建DatagramSocket对象。
-
调用DatagramSocket对象的receive()方法,以DatagramPacket对象为参数接收数据报,接收到的信息有。
收到的数据报分组。
发送端主机的IP地址。
发送端主机的发送端口号。
3.4. UDP通信案例
要实现UDP通信需要创建一个发送端程序和一个接收端程序。在通信时只有接收端程序先运行,才能避免发送端发送数据时找不到接收端而造成数据丢失的问题。因此,首先需要完成接收端程序的编写,然后再完成发送端程序的编写。
(1)接收端程序,具体代码如下所示。
java
public class Receiver {
public static void main(String[] args) throws Exception {
byte[] buf = new byte[1024]; // 创建一个字节数组,用于接收数据
// 定义一个DatagramSocket对象,端口号为8080
DatagramSocket ds = new DatagramSocket(8080);
// 定义一个DatagramPacket对象,用于接收数据
DatagramPacket dp = new DatagramPacket(buf, buf.length);
System.out.println("等待接收数据");
ds.receive(dp); // 接收数据
/*调用DatagramPacket的方法获得接收到的信息包括数据的内容、长度、发送的IP地址和端口号*/
String str = new String(dp.getData(),0,dp.getLength())+" from "+ dp.getAddress().getHostAddress() + ":" + dp.getPort();
System.out.println(str); // 打印接收到的信息
ds.close(); // 关闭数据报连接
}
}
(2)发送端程序,具体代码如下所示。
java
public class Sender {
public static void main(String[] args) throws Exception {
// 创建一个DatagramSocket对象
DatagramSocket ds = new DatagramSocket(8090);
String str = "你好,潍坊理工学院"; //要发送的数据
byte[] arr = str.getBytes(); //将定义的字符串转为字节数组
// 创建一个要发送的数据报,数据报包括发送的数据,数据的长度,接收端的IP地址以及端口号
DatagramPacket dp = new DatagramPacket(arr, arr.length,InetAddress.getByName("localhost"), 8080);
System.out.println("发送信息");
ds.send(dp); // 发送数据
ds.close(); // 释放资源
}
}