之前的文章(Java中TCP通信的实现:​​https://blog.51cto.com/u_113754/6066848​​ )在研究 TCP 通信的双向通信时,客户端和服务端必须交替请求,才能保持正常会话。

现在就来对这个问题进行解决吧。

1 思路

解决思路是使用多线程的方式:

一个线程用于发送消息;

一个线程用于接收消息。

其中,发送消息的线程,具有的特点是:接收键盘输入内容,并将内容通过 Socket 对象发送到客户端或服务端;

接收消息的线程,特点是:接收从客户端/服务端发来的请求。

2 服务端

2.1 创建接收线程

接收线程,用于接收处理客户端请求。

当收到客户端发来结束请求的 “再见”时,设置 ReceiveBufferSize 值为1,并跳出循环。

//接收消息
class Receive extends Thread{
private Socket socket;

Receive(Socket socket){
this.socket = socket;
}

@Override
public void run() {
this.receiveMessage();
}

//接收消息的方法
private void receiveMessage(){
try{
BufferedReader bw = new BufferedReader(new InputStreamReader(socket.getInputStream()));

while (true){
String line = bw.readLine();
System.out.println("接收到客户端请求 " + line);

if("再见".equals(line)){
socket.setReceiveBufferSize(1);
break;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}

2.2 创建发送线程

发送线程,用于服务端向客户端发送消息。

在发送线程里,需要接收键盘输入,以便和客户端进行互动。

当判断 ReceiveBufferSize 的值是否为1,如果设置为了1,结束当前会话并退出循环。

//发送消息
class Send extends Thread{
private Socket socket;

public Send(Socket socket){
this.socket = socket;
}

@Override
public void run() {
this.sendMessage();
}

//发送消息的方法
private void sendMessage(){
try{
//创建向客户端发送消息的输出流
PrintWriter pw = new PrintWriter(socket.getOutputStream());
//创建接收键盘输入对象
Scanner scanner = new Scanner(System.in);

while (true) {
if(socket.getReceiveBufferSize() == 1){
socket.close();
break;
}
String output = scanner.nextLine();
pw.println(output);
pw.flush();
}
}catch (Exception e){
e.printStackTrace();
}
}
}

2.3 创建服务端主线程

在服务端主线程中创建 ServerSocket 对象及接收客户端连接的 Socket 对象,并分别启动接收消息、发送消息线程。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server {
public static void main(String[] args) {
try{
ServerSocket serverSocket = new ServerSocket(10001);
System.out.println("服务端启动,等待客户端连接……");

while (true) {
Socket socket = serverSocket.accept();

new Send(socket).start();
new Receive(socket).start();
}
}catch (Exception e){
e.printStackTrace();
}
}
}

3 客户端

3.1 创建接收线程

当收到服务端发来的结束请求“再见”时,关闭 Socket 连接并退出循环。

//接收消息
class ClientReceive extends Thread{
private Socket socket;

ClientReceive(Socket socket){
this.socket = socket;
}

@Override
public void run() {
this.receiveMessage();
}

//接收消息的方法
private void receiveMessage(){
try{
BufferedReader bw = new BufferedReader(new InputStreamReader(socket.getInputStream()));

while (true){
String line = bw.readLine();
System.out.println("接收到服务端信息 " + line);

//当收到客户端发来结束请求时,关闭会话
if("再见".equals(line)){
socket.close();
break;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}

3.2 创建发送线程

当客户端发出“再见”时,即退出循环。

//发送消息
class ClientSend extends Thread{
private Socket socket;

public ClientSend(Socket socket){
this.socket = socket;
}

@Override
public void run() {
this.sendMessage();
}

//发送消息的方法
private void sendMessage(){
try{
//创建向客户端发送消息的输出流
PrintWriter pw = new PrintWriter(socket.getOutputStream());
//创建接收键盘输入对象
Scanner scanner = new Scanner(System.in);

while (true) {
String output = scanner.nextLine();
pw.println(output);
pw.flush();

if("再见".equals(output)){
break;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}

3.3 创建客户端主线程

在客户端主线程中分别启动接收消息、发送消息线程。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class Client {
public static void main(String[] args) {
try{
Socket socket = new Socket("localhost", 10001);

new ClientSend(socket).start();
new ClientReceive(socket).start();
}catch (Exception e){
e.printStackTrace();
}
}
}

以上就实现了客户端与服务端的互动通信,且不受接收顺序的限制。

4 启动服务端和客户端并相互发送消息

客户端和服务端可以发送多轮消息,并在相互发出“再见”时,结束了客户端的会话。

Java中TCP通信的实现之双向通信2——多轮会话_客户端