Java 对基于 TCP 协议的网络通信提供了良好的封装,Java 使用 Socket 对象来代表两端的通信端口,并通过 Socket 产生 IO 流来进行网络通信。
1.1 TCP 基础
IP 协议是 Internet 上使用的一个关键协议,它的全称是 Internet Protocol,即 Internet 协议,通常简称 IP 协议。通过使用 IP 协议,从而使 Internet 成为一个允许连接不同类型的计算机和不同操作系统的网络。要使两台计算机彼此能进行通信,必须使两台计算机使用同一种“语言”,IP 协议只保证计算机能发送和接收分组数据。IP 协议负责将消息从一个主机传送到另一个主机,消息在传送的过程中被分割成一个个的小包。尽管计算机通过安装 IP 软件,保证了计算机之间可以发送和接收数据,但 IP 协议还不能解决数据分组在传输过程中可能出现的问题。
因此,若要解决可能出现的问题,连上 Internet 的计算机还需要安装 TCP 协议来提供可靠并且无差错的通信服务。TCP 协议被称作一种端对端协议。这是因为它对两台计算机之间的连接起了重要作用——当一台计算机需要与另一台远程计算机连接时,TCP 协议会让它们建立一个连接:用于发送和接收数据的虚拟链路。
TCP 协议负责收集这些信息包,并将其按适当的次序放好传送,接收端收到后再将其正确地还原。TCP 协议保证了数据包在传送中准确无误。TCP 协议使用重发机制——当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体的确认信息,如果没有收到另一个通信实体的确认信息,则会再次重发刚才发送的信息。通过这种重发机制,TCP 协议向应用程序提供了可靠的通信连接,使它能够自动适应网上的各种变化。即使在 Internet 暂时出现堵塞的情况下,TCP 也能够保证通信的可靠性。
虽然 IP 和 TCP 这两个协议的功能不尽相同,也可以分开单独使用,但它们是在同一时期作为一个协议来设计的,并且在功能上也是互补的。只有两者结合起来,才能保证 Internet 在复杂的环境下正常运行。凡是要连接到 Internet 的计算机,都必须同时安装和使用这两个协议,因此在实际中常把这两个协议统称为 TCP/IP 协议。
1.2 TCP 通信相关类
在Java中,提供了两个类用于实现TCP通信程序,客户端:java.net.Socket
类,用于创建Socket
对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。服务端:java.net.ServerSocket
类,用于创建ServerSocket
对象,相当于开启一个服务,并等待客户端的连接。
1.2.1 Socket 类
Socket
类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。
构造方法
public Socket(String host, int port)
:创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的 host 是 null ,则相当于指定地址为回送地址。
补充:回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。
常用方法
方法名 | 说明 |
---|---|
InputStream getInputStream() | 返回该 Socket 对象对应的输入流,让程序通过该输入流从 Socket 中取出数据 |
OutputStream getOutputStream() | 返回该 Socket 对象对应的输出流,让程序通过该输出流向 Socket 中输出数据 |
void close() | 关闭 Socket |
void shutdownOutput() | 写结束标记 |
InetAddress getLocalAddress() | 获取 Socket 绑定的本地地址 |
InetAddress getInetAddress() | 返回 Socket 连接的地址 |
int getPort() | 返回此 Socket 连接到的远程端口 |
1.2.2 ServerSocket 类
ServerSocket
类:这个类实现了服务器套接字,该对象等待通过网络的请求。
构造方法
public ServerSocket(int port)
:使用该构造方法在创建 ServerSocket 对象时,就可以将其绑定到一个指定的端口号上,参数 port 就是端口号。
常用方法
方法名 | 说明 |
---|---|
Socket accept() | 接收到客户端 Socket 的连接请求,该方法将返回一个与客户端 Socket 对应的 Socket;否则该方法将一直处于等待状态,线程也被阻塞 |
1.3 实现 TCP 通信
1.3.1 TCP 通信分析
①【服务端】启动,创建 ServerSocket 对象,等待连接。
②【客户端】启动,创建 Socket 对象,请求连接。
③【服务端】接收连接,调用 accept 方法,并返回一个 Socket 对象。
④【客户端】Socket 对象,获取 OutputStream,向服务端写出数据。
⑤【服务端】Scoket 对象,获取 InputStream,读取客户端发送的数据。
⑥【服务端】Socket 对象,获取 OutputStream,向客户端回写数据。
⑦【客户端】Scoket 对象,获取 InputStream,解析回写数据。
⑧【客户端】释放资源,断开连接。
1.3.2 服务端代码
public class DemoServerSocket {
public static void main(String[] args) throws Exception{
// 使用多线程连接多个客户端
new Thread(new Runnable() {
@Override
public void run() {
try {
method();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
public static void method() throws Exception{
// 创建服务器连接
ServerSocket serverSocket = new ServerSocket(8888);
// 获取通信对象
Socket socket = serverSocket.accept();
// 获取网络输入流
InputStream inputStream = socket.getInputStream();
// 创建缓冲流
BufferedInputStream bis = new BufferedInputStream(inputStream);
// 创建日期格式化对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHssmmSS");
// 本地缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("src\\com\\" + sdf.format(new Date()) + ".java"));
// 创建缓冲区
byte[] bytes = new byte[1024 * 1024];
// 读取的长度
int len;
// 循环读取字节,并写出到服务器
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes,0,len);
}
// 获取网络输出流
OutputStream outputStream = accept.getOutputStream();
// 写回信息
outputStream.write("上传成功".getBytes());
// 释放资源
bis.close();
bos.close();
accept.close();
}
}
1.3.3 客户端代码
public class DemoSocket {
public static void main(String[] args) throws Exception{
// 创建连接
Socket socket = new Socket("127.0.0.1", 8888);
// 获取网络输出流
OutputStream socketOutput = socket.getOutputStream();
// 创建本地缓冲输入流
BufferedInputStream nativeInput = new BufferedInputStream(new FileInputStream(
new File("C:\\Users\\Demo_Null\\Desktop\\socket.java")));
// 创建缓冲数组
byte[] bytes = new byte[1024 * 1024];
// 读取的长度
int len;
// 循环读取字节并上传到服务器
while ((len = nativeInput.read(bytes)) != -1) {
socketOutput.write(bytes,0,len);
}
// 写结束标记
socket.shutdownOutput();
// 获取网络输入流
InputStream inputStream = socket.getInputStream();
// 创建缓冲流
BufferedInputStream b = new BufferedInputStream(inputStream);
// 打印服务器写回信息
while ((len = b.read(bytes)) != -1) {
System.out.print(new String(bytes,0,len));
}
// 释放资源
socket.close();
}
}
补充: 服务端程序,需要事先启动,等待客户端的连接;客户端主动连接服务器端,连接成功才能通信,服务端不可以主动连接客户端。