一、概念
网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。
java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。
java.net 包中提供了两种常见的网络协议的支持:
-
TCP:TCP(英语:Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP 层是位于 IP 层之上,应用层之下的中间层。TCP 保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。
-
UDP:UDP (英语:User Datagram Protocol,用户数据报协议),位于 OSI 模型的传输层。一个无连接的协议。提供了应用程序之间要发送数据的数据报。由于UDP缺乏可靠性且属于无连接协议,所以应用程序通常必须容许一些丢失、错误或重复的数据包。
二、 TCP网络编程
套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。
当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。
java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。
以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:
-
服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
-
服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
-
服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
-
Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
-
在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现 socket。
(1)InetAddress 类
这个类表示互联网协议(IP)地址。下面列出了 Socket 编程时比较有用的方法:
序号 | 方法描述 |
1 | static InetAddress getByAddress(byte[] addr) 在给定原始 IP 地址的情况下,返回 InetAddress 对象。 |
2 | static InetAddress getByAddress(String host, byte[] addr) 根据提供的主机名和 IP 地址创建 InetAddress。 |
3 | static InetAddress getByName(String host) 在给定主机名的情况下确定主机的 IP 地址。 |
4 | String getHostAddress() 返回 IP 地址字符串(以文本表现形式)。 |
5 | String getHostName() 获取此 IP 地址的主机名。 |
6 | static InetAddress getLocalHost() 返回本地主机。 |
7 | String toString() 将此 IP 地址转换为 String。 |
(2)ServerSocket 类
服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。
ServerSocket 类有四个构造方法:
序号 | 方法描述 |
1 | public ServerSocket(int port) throws IOException 创建绑定到特定端口的服务器套接字。 |
2 | public ServerSocket(int port, int backlog) throws IOException 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。 |
3 | public ServerSocket(int port, int backlog, InetAddress address) throws IOException 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。 |
4 | public ServerSocket() throws IOException 创建非绑定服务器套接字。 |
创建非绑定服务器套接字。 如果 ServerSocket 构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。
这里有一些 ServerSocket 类的常用方法:
序号 | 方法描述 |
1 | public int getLocalPort() 返回此套接字在其上侦听的端口。 |
2 | public Socket accept() throws IOException 侦听并接受到此套接字的连接。 |
3 | public void setSoTimeout(int timeout) 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。 |
4 | public void bind(SocketAddress host, int backlog) 将 ServerSocket 绑定到特定地址(IP 地址和端口号)。 |
(3)Socket 类
java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象通过实例化 ,而服务器获得一个 Socket 对象则通过 accept() 方法的返回值。
Socket 类有五个构造方法.
序号 | 方法描述 |
1 | public Socket(String host, int port) throws UnknownHostException, IOException. 创建一个流套接字并将其连接到指定主机上的指定端口号。 |
2 | public Socket(InetAddress host, int port) throws IOException 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 |
3 | public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException. 创建一个套接字并将其连接到指定远程主机上的指定远程端口。 |
4 | public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException. 创建一个套接字并将其连接到指定远程地址上的指定远程端口。 |
5 | public Socket() 通过系统默认类型的 SocketImpl 创建未连接套接字 |
当 Socket 构造方法返回,并没有简单的实例化了一个 Socket 对象,它实际上会尝试连接到指定的服务器和端口。
下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个 Socket 对象,所以无论客户端还是服务端都能够调用这些方法。
序号 | 方法描述 |
1 | public void connect(SocketAddress host, int timeout) throws IOException 将此套接字连接到服务器,并指定一个超时值。 |
2 | public InetAddress getInetAddress() 返回套接字连接的地址。 |
3 | public int getPort() 返回此套接字连接到的远程端口。 |
4 | public int getLocalPort() 返回此套接字绑定到的本地端口。 |
5 | public SocketAddress getRemoteSocketAddress() 返回此套接字连接的端点的地址,如果未连接则返回 null。 |
6 | public InputStream getInputStream() throws IOException 返回此套接字的输入流。 |
7 | public OutputStream getOutputStream() throws IOException 返回此套接字的输出流。 |
8 | public void close() throws IOException 关闭此套接字。 |
(4)示例
下面是一个简单的网络编程示例。
服务器端:
package Study.Network_Study;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务器端
* 1、在本机的9999端口监听,等待连接
* 2、当没有客户端连接9999端口时,程序会阻塞,等待连接
* 3、通过socket.getInputStream()读取客户端写入到数据通道的数据,显示
*/
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
//1、在本机的9999端口监听,等待连接
//细节:要求本机没有其他服务在监听9999
//这个ServerSocket可以通过accept()返回多个Socket[多个客户端连接服务的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器端,在9999端口监听,等待连接...");
//2、当没有客户端连接9999端口时,程序会阻塞,等待连接
//如果有客户端连接,则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
System.out.println("socket = "+socket.getClass());
//3、通过socket.getInputStream()读取客户端写入到数据通道的数据,显示
InputStream inputStream = socket.getInputStream();
//4、IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while((readLen=inputStream.read(buf))!=-1)
System.out.println(new String(buf,0,readLen));
//5、关闭输入流和socket
inputStream.close();
socket.close();
serverSocket.close();//关闭服务器端
System.out.println("服务器端关闭...");
}
}
客户端:
package Study.Network_Study;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* 客户端
* 1、连接服务器(ip,端口)
* 2、连接后,生成Socket,通过socket.getOutputStream()
* 3、通过输出流,写入数据到数据通道
*/
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
//1、连接服务器(ip,端口)
//连接本地9999端口,连接成功返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
System.out.println("客户端socket返回="+socket.getClass());
//2、连接后,生成Socket,通过socket.getOutputStream()
//获得一个和socket对象关联的输出流对象
OutputStream outputStream = socket.getOutputStream();
//3、通过输出流,写入数据到数据通道
outputStream.write("hello,server".getBytes());
//4、关闭流对象和socket,必须关闭
outputStream.close();
socket.close();
System.out.println("客户端退出...");
}
}
运行结果:
服务器端:
服务器端,在9999端口监听,等待连接...
socket = class java.net.Socket
hello,server
服务器端关闭...
客户端:
客户端socket返回=class java.net.Socket
客户端退出...
三、 UDP网络编程
UDP 就像日常生活中的邮件投递,不能保证可靠地寄到目的地。UDP 是无连接的,对系统资源的要求较少,UDP 可能丢包且不保证数据顺序。但是对于网络游戏和在线视频等要求传输快、实时性高、质量可稍差一点的数据传输,UDP 还是非常不错的。
UDP Socket 网络编程比 TCP Socket 编程简单得多,UDP 是无连接协议,不需要像 TCP 一样监听端口且建立连接,才能进行通信。
(1)DatagramSocket 类
java.net 包中提供了两个类 DatagramSocket 和 DatagramPacker,用来支持 UDP 通信。DatagramSocket 用于在程序之间建立传送数据报的通信连。
下面是 DatagramSocket 常用的构造函数:
- DatagramSocket()。创建数据报 DatagramSocket 对象,并将其绑定到本地主机上任何可以的端口。
- DatagramSocket(port: Int)。创建数据报 DatagramSocket 对象,并将其绑定到本地主机上的指定端口。
- DatagramSocket(port: Int, laddr: InetAddress)。创建数据报 DatagramSocket 对象,并将其绑定到指定的本地地址。
DatagramSocket 其他的常用函数和属性有:
- send(p: DatagramPacket)。发送数据报包。
- receive(p: DatagramPacket)。接收数据报包。
- port 属性。返回 DatagramSocket 连接到的远程端口。
- localPort。返回 DatagramSocket 绑定到的本地端口。
- inetAddress 属性:返回 DatagramSocket 连接地址。
- localAddress 属性:返回 DatagramSocket 绑定的本地地址。
- isClosed 属性:判断返回 DatagramSocket 是否处于关闭状态。
- isConnected 属性:判断返回 DatagramSocket 是否处于连接状态。
- close() 函数:关闭 DatagramSocket。
DatagramSocket 也实现了 AutoCloseable 接口,可以通过自动资源管理技术关闭 DatagramSocket。
(2)DatagramPacket 类
DatagramPacket 用来表示数据报包,是数据传输的载体。
下面是 DatagramPacket 的构造函数:
- DatagramPacket(buf: ByteArray, length: Int)。构造数据报包,其中 buf 是包数据,length 是接收包数据的长度。
- DatagramPacket(buf: ByteArray, length: Int, address: InetAddress, port: Int)。构造数据报包,包发送到指定主机上的指定端口号。
- DatagramPacket(buf: ByteArray, offset: Int, length: Int)。构造数据报包,其中 offset 是 buf 字节数组的偏移量。
- DatagramPacket(buf: ByteArray, offset: Int, length: Int, address: InetAddress, port: Int)。构造数据报包,包发送到指定主机上的指定端口号。
DatagramPacket 常用属性如下:
- address。返回发往或接收该数据报包相关的主机 IP 地址,属性类型是 InetAddress。
- data。返回数据报包中的数据,属性类型是 ByteArray。
- length。返回发送或接收到数据的长度,属性类型是 Int。
- offset。返回发送或接收到的数据的偏移量,属性类型是 Int。
- port。返回发送或接收该数据报包相关的主机的端口号,属性类型是 Int。
(3)示例
A端:
package Study.Network_Study;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 编写一个接收端A和一个发送端B
* 接收端A在9999端口等待接收数据
* 发送端B向接收端A发送数据“hello,明天吃火锅”
* 接收端A接收到发送端B发送的数据,回复“好的,明天见”,再退出
* 发送端B接收到回复的数据,再退出
*/
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//1.创建DatagramSocket对象,准备在9999端口接收数据
DatagramSocket datagramSocket = new DatagramSocket(9999);
//2.构建DatagramPacket对象准备接收数据
//DatagramPacket(byte[],int)
byte[] buf = new byte[64*1024];//UDP一个数据包最大64kb
DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
//3.调用接收方法,接收DatagramPacket对象
// 没有数据包过来时会阻塞,直至有数据包到达9999端口
System.out.println("接收端A正在等待数据");
datagramSocket.receive(datagramPacket);
//4.对DatagramPacket进行拆包,取出数据
int length = datagramPacket.getLength();//实际接收到数据的长度
byte[] data = datagramPacket.getData();
System.out.println(new String(data,0,length));
byte[] data2 = "好的,明天见".getBytes();
DatagramPacket datagramPacket2 = new DatagramPacket(data2, data2.length, InetAddress.getLocalHost(), 9998);
datagramSocket.send(datagramPacket2);
//5.关闭资源
datagramSocket.close();
System.out.println("A端退出");
}
}
B端:
package Study.Network_Study;
import java.io.IOException;
import java.net.*;
/**
* 编写一个接收端A(9999端口)和一个发送端B(9998端口)
* 接收端A在9999端口等待接收数据
* 发送端B向接收端A发送数据“hello,明天吃火锅”
* 接收端A接收到发送端B发送的数据,回复“好的,明天见”,再退出
* 发送端B接收到回复的数据,再退出
*/
public class UDPSenderB {
public static void main(String[] args) throws IOException {
//创建DatagramSocket对象,准备发送和接收数据
DatagramSocket datagramSocket = new DatagramSocket(9998);
//将需要发送的数据封装到DatagramPacket对象
byte[] data = "hello,明天吃火锅".getBytes();
DatagramPacket datagramPacket = new DatagramPacket(data, data.length, InetAddress.getLocalHost(), 9999);
datagramSocket.send(datagramPacket);
byte[] buf = new byte[64*1024];
datagramPacket = new DatagramPacket(buf, buf.length);
System.out.println("接收端B正在等待数据");
datagramSocket.receive(datagramPacket);
int length = datagramPacket.getLength();
byte[] data2 = datagramPacket.getData();
System.out.println(new String(data2,0,length));
//关闭资源
datagramSocket.close();
System.out.println("B端退出");
}
}
运行结果:
A端:
接收端A正在等待数据
hello,明天吃火锅
A端退出
B端:
接收端B正在等待数据
好的,明天见
B端退出