欢迎光临散文网 会员登陆 & 注册

javaTCP双向通信

2022-09-07 09:50 作者:虚云幻仙  | 我要投稿

/**
* 实现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("通信结束");

   }
}

javaTCP双向通信的评论 (共 条)

分享到微博请遵守国家法律