Java中TCP通信以及多线程通信

回顾Java多线程在Socket通信中的使用

一、Java中socket编程

  • socket = IP + 端口号,构成了在网络上唯一能被识别的标识符套接字。
  • 通信的client和server双方都要获取对方的socket才能进行通信,socket之间的通信就是网络通信。
  • socket之间就是普通的IO流传输。
  • 这篇主要回顾TCP通信,也就是基于字节流的网络传输。

二、一般的单线程socket通信

  • 建立连接的几个步骤:

    1. server 端声明 ServerSocket ,然后调用accept() 方法阻塞获得client的socket。
  1. 通过获得的socket调用 getInputStream()或者getOutputStream()获得输入输出流,进行数据传输。
  2. client 声明 Socket ,获得输入输出流,进行数据传输。
  3. 双方关闭socket
  • 建立示例:

    //服务器端
    public class Server {
        public static void main(String[] args) {
            ServerSocket serverSocket;
            {
                try {
                    serverSocket = new ServerSocket(8888);
                    Socket socket = serverSocket.accept();
                    InputStream inputStream = socket.getInputStream();
                    OutputStream outputStream = socket.getOutputStream();
    
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                    String str;
                    str = bufferedReader.readLine();
                    System.out.println(str);
    				BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
                    bufferedWriter.write("Message Received");
                    bufferedWriter.flush();
                    socket.shutdownOutput();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public class Client {
        public static void main(String[] args) {
            Socket socket;
            {
                try {
                    socket = new Socket("127.0.0.1",8888);
                    InputStream inputStream = socket.getInputStream();
                    OutputStream outputStream = socket.getOutputStream();
                    BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
                    bufferedWriter.write("First message");
                    bufferedWriter.flush();
                    socket.shutdownOutput();//需要告诉server端client这边输出已经结束了,那边readline可以停止阻塞了
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                    System.out.println(bufferedReader.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    运行结果:

    image-20200709131710867

    image-20200709131804284

    注:

    • socket中有两处是阻塞的
    1. server的accept(),调用这个方法之后,server在连接建立之前会一直阻塞等待
    2. IO中的read()方法,没有获得client端的消息发送结束标识,会一直阻塞等待,所以通过socket.shutdownOutput()方法结束client的socket,这样如果server端发送消息,client还是能收到的;如果直接close就不能收到了消息了。
    • 只要有结束标识都会正常接收,另一个例子如下,通过newline增加分割
  • 同步信息发送

    public class Server {
        public static void main(String[] args) {
            ServerSocket serverSocket;
    
            {
                try {
                    serverSocket = new ServerSocket(8888);
                    Socket socket = serverSocket.accept();
                    InputStream inputStream = socket.getInputStream();
                    OutputStream outputStream = socket.getOutputStream();
    
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                    String str;
                    while((str = bufferedReader.readLine()) != null){
                        System.out.println(str);
                    }
    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
    }
    
    public class Client {
        public static void main(String[] args) {
            Socket socket;
            {
                try {
                    socket = new Socket("127.0.0.1",8888);
                    InputStream inputStream = socket.getInputStream();
                    OutputStream outputStream = socket.getOutputStream();
                    BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in,"UTF-8"));
                    while(true){
                        bufferedWriter.write(bufferedReader.readLine());
                        bufferedWriter.newLine();
                        bufferedWriter.flush();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
    
    }
    
  • 主要问题:

    只能是一对一通信,如果有A、B、C三者,A和B进行通信时,C对B发送消息B是收不到的,因为B会阻塞在A上,只有A关闭之后B才能和连接到它的client进行通信。

三、多线程socket通信

  • 每个client通过server的一个线程进行通信。

  • 使用线程池减少资源消耗,提高运行速度。

    public class ThreadServer {
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(8899);
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            while(true){
                Socket socket = serverSocket.accept();
                executorService.submit(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                            String str;
                            while((str = bufferedReader.readLine()) != null){
                                System.out.println(str);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    }
    
    
  • 多个client进程都可以通过一个server线程进行通信

四、总结

  • socket通信关键点为:socket的获取,IO流的应用,交互关系的明确,体会双向通信的时序关系。
  • 明确怎么标识消息的间隔,使用shutdownOutput() 还是 close();bufferedStream的flush()问题,可能出现不显示的情况。
  • 多线程中线程池的使用提高效率,减少资源的消耗。

版权声明:本文为PanicJaw原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。