首页 > 编程笔记 > Java笔记 阅读:42

Java TCP通信详解(附带实例)

TCP 协议是英文 Transmission Control Protocol 的缩写,即传输控制文协议。

TCP 协议是一种可靠的网络协议,它在通信实例双方各自创建一个 Socket,在通信双方之间形成网络虚拟链路。

TCP 被称作一种端对端协议,是一种可靠的网络协议,它分别在通信的两端创建一个 Socket,形成可以进行通信的虚拟链路。TCP 通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信。服务器端不可以主动连接客户端,如果客户端没有发送连接请求,它将一直处于等待状态。

在 Java 中有两个用于实现 TCP 程序的类,一个是表示服务器端的 ServerSocket 类,另一个是用于表示客户端的 Socket 类。

在通信时,须先采用“三次握手”方式建立 TCP 连接,形成数据传输通道:
它保证了两台通信设备之间的无差别传输,在连接中可以进行大量数据的传输,传输完毕后要释放已建立的连接。

TCP 是一种可靠的网络通信协议,数据传输安全且完整,但是效率比较低。一些对完整性和安全性要求高的数据采用 TCP 协议传输,比如文件传输和下载,如果文件下载不完全,会导致文件损坏而无法打开。

TCP 的“三次握手”如下图所示:


图 1 TCP的“三次握手”

在图 1 中,客户端先向服务器端发出连接请求,等待服务器进行确认,服务器端收到后向客户端发送一个响应,告诉客户端已经收到了连接请求,最后客户端再次向服务器端发送确认信息,确认连接成功。

Java ServerSocket类

在 java.net 包中有一个 ServerSocket 类,它可以实现服务器端程序,其构造方法如下表所示:

表:ServerSocket 类的构造方法
构造方法 方法描述
public ServerSocket() 构造非绑定服务器套接字
public ServerSocket(int port) 构造绑定到特定端口的服务器套接字
public ServerSocket(int port, int backlog) 利用指定的 backlog 构造服务器套接字并将其绑定到指定的本地端口号
public ServerSocket(int port, int backlog, InetAddress bindAddr) 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址构造套接字

上表中列出了 ServerSocket 类的构造方法,通过这些方法可以获得 ServerSocket 类的实例,它还有一些常用方法,如下表所示:

表:ServerSocket 类的常用方法
方法 方法描述
Socket accept() 监听并接受到此套接字的连接
void close() 关闭此套接字连接
InetAddress getInetAddress() 返回此服务器套接字的本地地址
boolean isClosed() 返回此服务器套接字的关闭状态
void bind(SocketAddress endpoint) 将此服务器套接字绑定到特定地址(IP 地址和端口号)

上表中列出了 ServerSocket 类的常用方法,其中 accept() 方法用来接收客户端的请求,当此方法执行后,服务器端程序开始等待,直到客户端发送过来请求,程序才能继续执行,如下图所示。


图 2 TCP服务器端和客户端

在图 2 中,ServerSocket 代表服务器端,Socket 代表客户端,服务器端在调用 accept() 方法后就开始绑定某个端口等待客户端的请求,在客户端发出连接请求后,accept() 方法会将一个 Socket 对象返回给服务器端,用于和客户端实现通信。

Java Socket类

在 java.net 包中还提供了一个 Socket 类,它是一个数据报套接字,包含了源 IP 地址和目的 IP 地址以及源端口号和目的端口号的组合,用于发送和接收 TCP 数据。

Socket 类的常用构造方法如下表所示:

构造方法 方法描述
public Socket() 使用系统默认类型的 SocketImpl 对象,创建一个未连接的套接字
public Socket(InetAddress address, int port) 构造一个流套接字并将其连接到指定 IP 地址的指定端口号
public Socket(Proxy proxy) 构造一个未连接的套接字并指定代理类型(如果有),该代理不管其他设置如何都被使用
public Socket(String host, int port) 构造一个流套接字并将其连接到指定主机上的指定端口号

上表中列出了 Socket 类的常用构造方法,通过这些方法可以获得 Socket 类的实例,它还有一些常用方法,如下表所示。

表:Socket类的常用方法
方法 方法描述
void close() 关闭此套接字连接
InetAddress getInetAddress() 返回此套接字连接的 InetAdress 对象
InputStream getInputStream() 返回此套接字的输入流
OutputStream getOutputStream() 返回此套接字的输出流
int getPort() 返回此套接字连接到的远程端口
boolean isClosed() 返回套接字的关闭状态
void shutdownOutput() 关闭此套接字的输出流

上表中列出了 Socket 类的常用方法,通过这些方法可以使用 TCP 协议进行网络通信。

Java TCP网络程序

前面讲解了 java.net 包中 ServerSocket 类和 Socket 类的基本用法,接下来创建一个服务器端程序和一个客户端程序,通过这两个程序来深入理解 TCP 通信的基本原理。

在运行程序时,必须先运行服务器端程序,服务器端程序如下:
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Demo {
    public static void main(String[] args) throws Exception {
        // 创建服务器端ServerSocket对象,并指定IP为8081
        ServerSocket serverSocket = new ServerSocket(8081);
        System.out.println("等待接收数据");
        // 等待接收客户端的请求
        Socket socket = serverSocket.accept();
        // 获取输入流,并通过数组接收
        InputStream inputStream = socket.getInputStream();
        byte[] arr = new byte[1024];
        int len;
        while ((len = inputStream.read(arr)) != -1) {
            String str = new String(arr, 0, len);
            System.out.println(str);
        }
        // 获取输出流,并向客户端做出响应
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("师傅:为师知道了,退下吧".getBytes());
        // 释放资源
        outputStream.close();
        socket.close();
        serverSocket.close();
    }
}
程序的运行结果如下:

等待接收数据


在创建了服务器端程序后,接着来创建客户端程序:
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

public class Demo {
    public static void main(String[] args) throws Exception {
        // 准备去给服务器端的师傅问好
        System.out.println("准备去叫师傅起床");
        // 创建一个Socket对象,并指定接收端的IP为本机IP,端口号为8081;
        Socket socket = new Socket(InetAddress.getByName("localhost"), 8081);
        // 获取输出流,并将数据通过输出流传送出去,输出字符串要转为字节
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("悟空说:师傅,老孙向您问好来了".getBytes());
        // 关闭输出流
        socket.shutdownOutput();
        // 获取输入流,并定义一个大小为1024字节的数组,用来接收数据
        InputStream inputStream = socket.getInputStream();
        byte[] arr = new byte[1024];
        int len;
        // 当输入流读取的数据不为空时,往下执行,输出服务器端响应的消息
        while ((len = inputStream.read(arr)) != -1) {
            String str = new String(arr, 0, len);
            System.out.println(str);
        }
        // 释放资源
        inputStream.close();
        outputStream.close();
        socket.close();
    }
}
客户端程序的运行结果如下:

准备去叫师傅起床
唐僧:为师知道了,悟空退下吧


在接收到客户端发送的数据时,服务器端的运行结果如下:

等待接收数据
悟空说:师傅,老孙向您问好来了


注意,在运行的时候一定要先运行服务器端,再运行客户端,不然在师傅没睡醒的时候来打扰,肯定要吃闭门羹啊。如果不小心先启动了客户端,那么吃了闭门羹的孙悟空就会报 ConnectException 异常,提示“Connection refused”,中文含义为“拒绝连接”,出现错误的原因在于服务器端程序未开启或者连接的端口错误。

相关文章