javaTCP双向通信
/**
* 实现TCP双向通信
* 需要能够同时完成接收和发送,将这两个操作分给两个线程来处理
*/
public class TCPTwoWayCommunication {
}
class Status1{
//反映连接状态
private boolean over;
//直接使用默认的无参构造器,新建连接时默认没有结束notOver,当需要结束时setOver(true)
public boolean isOver() {
return over;
}
public void setOver(boolean over) {
this.over = over;
}
}
class Sender1 extends Thread{
//发送数据的线程
PrintWriter pw;
Scanner sc;
final Status1 status;
public Sender1(PrintWriter pw,Scanner sc,Status1 status) {
this.pw = pw;
this.sc = sc;
this.status = status;
}
@Override
public void run() {
while (true){
String message = sc.nextLine();
if (status.isOver())break;
//判断通信是否已经结束
pw.println(message);
if ("exit".equalsIgnoreCase(message)){
//己方想要结束通信时输入exit
status.setOver(true);
break;
}
}
}
}
class Receiver1 extends Thread{
//接收数据的线程
BufferedReader br;
PrintWriter pw;
Scanner sc;
final Status1 status;
public Receiver1(BufferedReader br, PrintWriter pw,Scanner sc,Status1 status) {
this.br = br;
this.pw = pw;
this.sc = sc;
this.status = status;
}
@Override
public void run() {
Sender1 s1 = new Sender1(pw, sc,status);
s1.start();
do {
try {
String message = br.readLine();
//.readLine()会阻塞,直到收到对方的内容,如果对方结束通信会返回null
if (status.isOver())break;
//如果是己方输入exit结束通信,这时status.isOver()=true,
// 但己方的exit只是将Sender线程结束,并且发送"exit"给对方Receiver线程使对方Receiver线程关闭,虽然己方设置了status但己方br.readLine()还在阻塞,所以需要对方Receiver线程接收到exit后随便发些什么内容来解除己方的阻塞才能判定status
if ("exit".equalsIgnoreCase(message)){
//这个if是接收到对方的"exit",这时对方的Sender线程已经结束,但Receiver线程还在.readLine()阻塞,所以需要发送任意消息以解除对方阻塞
pw.println("");
//这里也可以通过关闭输出流来解决,输出流关闭后,对方.readLine()返回null,或者对方.read()返回-1,也会解除阻塞
status.setOver(true);
System.out.print("对方结束了通信,输入任意内容退出: ");
//这里由于己方Sender线程还在等待用户输入而阻塞,所以提示用户输入任何内容来解除阻塞、判定status来结束线程
break;
}
if (message==null)break;
//这个if应该不会生效,用作保险,当一方结束通信/关闭Socket后另一方.readLine()会返回null即输入流读到末尾了,如果是.read()则会返回-1,而如果对方没有输入内容直接回车会接收到空字符串""而不是null
System.out.println("收到信息: "+message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}while (true);
synchronized (status) {
//需要关闭的对象由主线程创建、关闭,主线程在创建Receiver线程后.wait()阻塞
status.notify();
//唤醒主线程
}
}
}
class Server1{
public static void main(String[] args) {
try(ServerSocket ss = new ServerSocket(8888);
Socket socket = ss.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
Scanner sc = new Scanner(System.in);
PrintWriter pw = new PrintWriter(socket.getOutputStream(),true)
) {
System.out.println(socket.getPort());
//客户端的端口为52065
Status1 status = new Status1();
System.out.println("服务端已启动,已连接客户端,请输入需要发送的内容,输入exit结束通信");
Receiver1 r1 = new Receiver1(br,pw,sc,status);
r1.start();
synchronized(status){
if (r1.isAlive()){
status.wait();
//对象锁为status,main先执行同步代码,执行到r1.isAlive()=true后.wait()阻塞释放status对象锁,等待r1结束循环后执行同步代码唤醒main
}
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("通信结束");
}
}
class Client1{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Socket socket = null;
PrintWriter pw=null;
BufferedReader br = null;
try {
socket = new Socket("127.0.0.1",8888);
pw = new PrintWriter(socket.getOutputStream(),true);
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("已连接服务端,请输入需要发送的内容,输入exit结束通信");
Status1 status = new Status1();
Receiver1 r1 = new Receiver1(br,pw,sc,status);
r1.start();
synchronized(status){
if (r1.isAlive()){
status.wait();
}
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
} finally {
System.out.println("执行finally,关闭顺序遵循后进先出");
if (br != null) {
try {
br.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
System.out.println("关闭br");
if (pw != null) {
pw.close();
}
System.out.println("关闭pw");
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
System.out.println("关闭socket");
sc.close();
//当发送线程因为执行sc.nextLine()等待用户输入而阻塞时,无法使用sc.close()关闭,而sc之前的其他流、对象都已经关闭,这就导致主线程等待关闭sc而阻塞,当用户输入任意内容回车后,发送线程向下执行,如果调用pw.println(),因为pw已经关闭了就会抛出异常,所以在sc.nextLine()和pw.println()之间进行status判定
//将发送线程设定为守护线程的思路是错的,守护线程需要等所有用户线程结束后才会停止,而主线程会因为sc.close()阻塞无法向下执行导致用户线程未结束。主线程等守护线程结束后再关sc,守护线程等主线程结束才停止。
//这里如果是己方提出exit,那么Sender线程和Receiver线程都结束了,sc正常close。如果是对方提出exit,己方因为Receiver线程结束唤醒主线程,向下关闭流、对象,执行到sc.close()时等待用户输入任何内容以退出。等待用户输入内容后Sender线程安全结束后,主线程执行close并安全退出
System.out.println("关闭sc");
}
System.out.println("通信结束");
}
}