TCP通信程序

TCP发送数据

  • Java中的TCP通信
    • Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
    • Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
  • 构造方法
    | 方法名 | 说明 | | —- | —- | | Socket(InetAddress address,int port) | 创建流套接字并将其连接到指定IP指定端口号 | | Socket(String host, int port) | 创建流套接字并将其连接到指定主机上的指定端口号 |

  • 相关方法
    | 方法名 | 说明 | | —- | —- | | InputStream getInputStream() | 返回此套接字的输入流 | | OutputStream getOutputStream() | 返回此套接字的输出流 |

  • 示例代码

    1. public class ClientDemo {
    2. public static void main(String[] args) throws IOException {
    3. //创建客户端的Socket对象(Socket)
    4. //Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号
    5. Socket s = new Socket("127.0.0.1",10000);
    6. //获取输出流,写数据
    7. //OutputStream getOutputStream() 返回此套接字的输出流
    8. OutputStream os = s.getOutputStream();
    9. os.write("hello,tcp,我来了".getBytes());
    10. //释放资源
    11. s.close();
    12. }
    13. }

TCP接收数据

  • 构造方法
    | 方法名 | 说明 | | —- | —- | | ServletSocket(int port) | 创建绑定到指定端口的服务器套接字 |

  • 相关方法
    | 方法名 | 说明 | | —- | —- | | Socket accept() | 监听要连接到此的套接字并接受它 |

  • 注意事项

    1. accept方法是阻塞的,作用就是等待客户端连接
    2. 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
    3. 针对客户端来讲,是往外写的,所以是输出流
      针对服务器来讲,是往里读的,所以是输入流
    4. read方法也是阻塞的
    5. 客户端在关流的时候,还多了一个往服务器写结束标记的动作
    6. 最后一步断开连接,通过四次挥手协议保证连接终止
  • 三次握手和四次挥手
    • 三次握手
      07_TCP三次握手.png
    • 四次挥手
      08_TCP四次挥手.png
  • 示例代码

    public class ServerDemo {
     public static void main(String[] args) throws IOException {
         //创建服务器端的Socket对象(ServerSocket)
         //ServerSocket(int port) 创建绑定到指定端口的服务器套接字
         ServerSocket ss = new ServerSocket(10000);
    
         //Socket accept() 侦听要连接到此套接字并接受它
         Socket s = ss.accept();
    
         //获取输入流,读数据,并把数据显示在控制台
         InputStream is = s.getInputStream();
         byte[] bys = new byte[1024];
         int len = is.read(bys);
         String data = new String(bys,0,len);
         System.out.println("数据是:" + data);
    
         //释放资源
         s.close();
         ss.close();
     }
    }
    

TCP程序练习

  • 案例需求
    客户端:发送数据,接受服务器反馈
    服务器:收到消息后给出反馈
  • 案例分析
    • 客户端创建对象,使用输出流输出数据
    • 服务端创建对象,使用输入流接受数据
    • 服务端使用输出流给出反馈数据
    • 客户端使用输入流接受反馈数据
  • 代码实现

    // 客户端
    public class ClientDemo {
     public static void main(String[] args) throws IOException {
         Socket socket = new Socket("127.0.0.1",10000);
    
         OutputStream os = socket.getOutputStream();
         os.write("hello".getBytes());
        // os.close();如果在这里关流,会导致整个socket都无法使用
         socket.shutdownOutput();//仅仅关闭输出流.并写一个结束标记,对socket没有任何影响
    
         BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
         String line;
         while((line = br.readLine())!=null){
             System.out.println(line);
         }
         br.close();
         os.close();
         socket.close();
     }
    }
    // 服务器
    public class ServerDemo {
     public static void main(String[] args) throws IOException {
         ServerSocket ss = new ServerSocket(10000);
    
         Socket accept = ss.accept();
    
         InputStream is = accept.getInputStream();
         int b;
         while((b = is.read())!=-1){
             System.out.println((char) b);
         }
    
         System.out.println("看看我执行了吗?");
    
         BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
         bw.write("你谁啊?");
         bw.newLine();
         bw.flush();
    
         bw.close();
         is.close();
         accept.close();
         ss.close();
     }
    }
    

TCP程序文件上传练习

  • 案例需求
    客户端:数据来自于本地文件,接收服务器反馈
    服务器:接收到的数据写入本地文件,给出反馈
  • 案例分析
    • 创建客户端对象,创建输入流对象指向文件,每读一次数据就给服务器输出一次数据,输出结束后使用shutdownOutput()方法告知服务端传输结束
    • 创建服务器对象,创建输出流对象指向文件,每接受一次数据就使用输出流输出到文件中,传输结束后。使用输出流给客户端反馈信息
    • 客户端接受服务端的回馈信息
  • 相关方法
    | 方法名 | 说明 | | —- | —- | | void shutdownInput() | 将此套接字的输入流放置在“流的末尾” | | void shutdownOutput() | 禁止用此套接字的输出流 |

  • 代码实现

    // 客户端
    public class ClientDemo {
     public static void main(String[] args) throws IOException {
         Socket socket = new Socket("127.0.0.1",10000);
    
         //是本地的流,用来读取本地文件的.
         BufferedInputStream bis = new BufferedInputStream(new FileInputStream("socketmodule\\ClientDir\\1.jpg"));
    
         //写到服务器 --- 网络中的流
         OutputStream os = socket.getOutputStream();
         BufferedOutputStream bos = new BufferedOutputStream(os);
    
         int b;
         while((b = bis.read())!=-1){
             bos.write(b);//通过网络写到服务器中
         }
         bos.flush();
         //给服务器一个结束标记,告诉服务器文件已经传输完毕
         socket.shutdownOutput();
    
         BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
         String line;
         while((line = br.readLine()) !=null){
             System.out.println(line);
         }
         bis.close();
         socket.close();
     }
    }
    // 服务器
    public class ServerDemo {
     public static void main(String[] args) throws IOException {
         ServerSocket ss = new ServerSocket(10000);
    
         Socket accept = ss.accept();
    
         //网络中的流,从客户端读取数据的
         BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
         //本地的IO流,把数据写到本地中,实现永久化存储
         BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("socketmodule\\ServerDir\\copy.jpg"));
    
         int b;
         while((b = bis.read()) !=-1){
             bos.write(b);
         }
    
         BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
         bw.write("上传成功");
         bw.newLine();
         bw.flush();
    
         bos.close();
         accept.close();
         ss.close();
     }
    }
    

TCP程序服务器优化

  • 优化方案一

    • 需求
      服务器只能处理一个客户端请求,接收完一个图片之后,服务器就关闭了。
    • 解决方案
      使用循环
    • 代码实现

      // 服务器代码如下,客户端代码同上个案例,此处不再给出
      public class ServerDemo {
      public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
      
        while (true) {
            Socket accept = ss.accept();
      
            //网络中的流,从客户端读取数据的
            BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
            //本地的IO流,把数据写到本地中,实现永久化存储
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("optimizeserver\\ServerDir\\copy.jpg"));
      
            int b;
            while((b = bis.read()) !=-1){
                bos.write(b);
            }
      
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();
      
            bos.close();
            accept.close();
        }
        //ss.close();
      
      }
      }
      
  • 优化方案二

    • 需求
      第二次上传文件的时候,会把第一次的文件给覆盖。
    • 解决方案
      UUID. randomUUID()方法生成随机的文件名
    • 代码实现

      // 服务器代码如下,客户端代码同上个案例,此处不再给出
      public class ServerDemo {
      public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
      
        while (true) {
            Socket accept = ss.accept();
      
            //网络中的流,从客户端读取数据的
            BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
            //本地的IO流,把数据写到本地中,实现永久化存储
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("optimizeserver\\ServerDir\\" + UUID.randomUUID().toString() + ".jpg"));
      
            int b;
            while((b = bis.read()) !=-1){
                bos.write(b);
            }
      
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();
      
            bos.close();
            accept.close();
        }
        //ss.close();
      
      }
      }
      
  • 优化方案三

    • 需求
      使用循环虽然可以让服务器处理多个客户端请求。但是还是无法同时跟多个客户端进行通信。
    • 解决方案
      开启多线程处理
    • 代码实现

      // 线程任务类
      public class ThreadSocket implements Runnable {
      private Socket acceptSocket;
      
      public ThreadSocket(Socket accept) {
        this.acceptSocket = accept;
      }
      
      @Override
      public void run() {
        BufferedOutputStream bos = null;
        try {
            //网络中的流,从客户端读取数据的
            BufferedInputStream bis = new BufferedInputStream(acceptSocket.getInputStream());
            //本地的IO流,把数据写到本地中,实现永久化存储
            bos = new BufferedOutputStream(new FileOutputStream("optimizeserver\\ServerDir\\" + UUID.randomUUID().toString() + ".jpg"));
      
            int b;
            while((b = bis.read()) !=-1){
                bos.write(b);
            }
      
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
      
            if (acceptSocket != null){
                try {
                    acceptSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
      }
      }
      // 服务器代码
      public class ServerDemo {
      public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
      
        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            new Thread(ts).start();
        }
        //ss.close();
      }
      }
      
  • 优化方案四

    • 需求
      使用多线程虽然可以让服务器同时处理多个客户端请求。但是资源消耗太大。
    • 解决方案
      加入线程池
    • 代码实现

      // 服务器代码如下,线程任务类代码同上,此处不再给出
      public class ServerDemo {
      public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10000);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                10,   //线程池的总数量
                60,   //临时线程空闲时间
                TimeUnit.SECONDS, //临时线程空闲时间的单位
                new ArrayBlockingQueue<>(5),//阻塞队列
                Executors.defaultThreadFactory(),//创建线程的方式
                new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
        );
      
        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            //new Thread(ts).start();
            pool.submit(ts);
        }
        //ss.close();
      }
      }