一、Tcp套接字
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在TCP/IP协议族中,TCP协议是互联网中最核心、最广泛使用的网络协议之一。TCP套接字(Socket)是实现TCP协议进行网络通信的关键机制。
1、TCP套接字的基本概念
TCP套接字提供了一种端到端的通信方式,通过它,两台计算机上的应用程序可以相互交换数据。套接字可以被看作是在网络上不同计算机之间建立的一个通信通道。每个套接字都由一个IP地址和一个端口号唯一标识。
- IP地址:用于在网络中唯一标识一个设备。
- 端口号:在同一台设备上,用于区分不同的服务或应用程序。端口号的范围是0到65535。
2、TCP套接字的通信过程
-
服务器监听:
- 服务器首先在其指定的端口上创建一个TCP套接字,并设置为监听状态。
- 服务器等待客户端的连接请求。
-
客户端请求连接:
- 客户端创建一个TCP套接字,并指定要连接的服务器IP地址和端口号。
- 客户端向服务器发送连接请求(SYN包)。
-
服务器接受连接:
- 服务器收到客户端的连接请求后,确认连接请求(SYN-ACK包)。
- 客户端收到服务器的确认后,再次发送一个确认包(ACK包),此时TCP连接建立成功。
-
数据交换:
- 连接建立后,客户端和服务器可以开始交换数据。
- TCP协议确保数据的可靠传输,包括数据的完整性、顺序性和无丢失。
-
关闭连接:
- 当数据交换完成后,客户端或服务器可以发起关闭连接的请求。
- 双方通过发送FIN包和确认ACK包来关闭TCP连接。
3、TCP套接字的应用
TCP套接字广泛应用于各种需要可靠传输数据的网络应用程序中,如Web服务器(HTTP)、文件传输(FTP)、远程登录(SSH)等。这些应用程序通过TCP套接字在客户端和服务器之间建立连接,实现数据的可靠传输。
4、编程实现
在编程中,TCP套接字的实现依赖于操作系统提供的套接字API。
ServerSocket
- 定义:
ServerSocket
是java.net包中的一个类,它实现了服务器套接字。这个类的对象可以绑定到一个特定的端口上,并侦听该端口上的客户端连接请求。 - 作用:
ServerSocket
接受到一个客户端的连接请求时,会调用accept()
方法创建一个新的Socket
对象,通过这个Socket对象,服务器可以与客户端进行通信。 - 构造方法:
ServerSocket(int port)
,创建一个绑定到特定端口的服务器套接字。如果端口为0,则使用任何空闲端口(一般不将端口置为0,否则端口会随机分配给服务器,这会使客户端无法准确与服务器连接)。
Socket
- 服务器端:通过
ServerSocket
类来创建一个套接字,并绑定到一个特定的端口上,然后监听该端口上的连接请求。当有客户端发起连接请求时,服务器端的ServerSocket会接受该请求,并创建一个新的Socket
对象与客户端进行通信。 - 客户端:通过
Socket
类来创建一个套接字,并指定要连接的服务器IP地址和端口号。然后,客户端向服务器发送连接请求,一旦连接建立成功,就可以通过该Socket对象与服务器进行数据的交换。
Socket
构造方法:Socket(String host, int port)
,创建⼀个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接。
Socket
常用方法:
方法签名 | 说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输⼊流 |
OutputStreamgetOutputStream() | 返回此套接字的输出流 |
二、用TCP套接字构建客户端-服务器
服务器
- 建立连接
创建ServerSocket
对象,使用accept
方法生成与客户端连接的Socket
,每个Socket只能服务一个客户端。为了处理不同客户端的请求,使用多线程的形式,给每个客户端分配一个Socket对象。 - 读取请求并解析
通过字节流的形式读取请求并解析数据,首先建立一个流对象,通过Scanner
类的方法代替read
方法读取数据并解析(直接使用read方法读取的数据是byte类型,还需要转成String类才能完成解析)。 - 根据请求计算响应
将解析完的数据进行计算,生成响应。 - 将响应返回客户端
将响应通过write
方法写回到客户端中
注意:当客户端断开连接后,与客户端相连接的Socket对象也应当被关闭,否则其将会占用文件描述符表,导致资源浪费。
public class TcpEchoServer {
ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
// 创建ServerSocket对象
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("tcp服务器启动!");
ExecutorService pool = Executors.newCachedThreadPool();
while (true) {
// 1. 建立连接
// 不能使用Try()的形式关闭socket对象,如果调用start方法后关闭会影响run方法的使用
Socket clientSocket = serverSocket.accept();
/* // 每个客户端进来就创建一个线程
Thread t = new Thread(() -> {
try {
processConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
t.start();*/
// 使用线程池创建线程,更加快速高效
pool.submit(new Runnable() {
@Override
public void run() {
try {
processConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
Scanner scanner = new Scanner(inputStream);
while (true) {
if (!scanner.hasNext()) {
System.out.printf("[%s:%d]客户端下线!", clientSocket.getInetAddress(), clientSocket.getPort());
break;
}
// 2. 读取请求并解析,使用scanner类方法代替read方法,一步完成读取和解析
String request = scanner.next();
// 3. 将解析完的数据生成响应
String response = process(request);
// 4. 将响应返回到客户端
outputStream.write(response.getBytes());
System.out.printf("[%s:%d] req:%s resp:%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 使用完关闭Socket对象
clientSocket.close();
}
}
private String process(String request) {
// 客户端读取数据也是使用scanner类的next方法,该方法遇到空白符才会停止读取,否则会一直阻塞
// 这里加上\n表示为结束符
return request + "\n";
}
}
客户端
- 建立连接
创建Socket
对象,与服务器建立连接。 - 生成并发送请求
输入请求并通过write
方法将请求发送给服务器。 - 接收响应并解析
使用Scanner
类的next
方法接收响应。 - 根据响应作出进行下一步操作
对响应作出操作,如打印。
public class TcpEchoClient {
Socket socket = null;
public TcpEchoClient(String serverIP, int sercerPort) throws IOException {
// 1. 创建Socket对象
socket = new Socket(serverIP, sercerPort);
}
public void start() {
System.out.println("客户端启动!");
Scanner scannerConsole = new Scanner(System.in);
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
Scanner serverNetwork = new Scanner(inputStream);
while (true) {
// 2. 生成并发送请求
String request = scannerConsole.next();
request += "\n";
outputStream.write(request.getBytes());
// 3. 接收响应并解析
String response = serverNetwork.next();
// 4. 打印响应
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}