【愚公系列】2022年01月 Java教学课程 67-网络编程-TCP通信

举报
愚公搬代码 发表于 2022/01/13 20:45:11 2022/01/13
【摘要】 一.TCP通信程序 1.TCP发送数据Java中的TCP通信Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。Java为客户端提供了Socket类,为服务器端提供了ServerSocket类构造方法方法名说明Socket(InetAddress address,int port)创建流套接字并将其连接到指...

一.TCP通信程序

1.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() 返回此套接字的输出流
  • 示例代码

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

2.TCP接收数据

  • 构造方法

    方法名 说明
    ServletSocket(int port) 创建绑定到指定端口的服务器套接字
  • 相关方法

    方法名 说明
    Socket accept() 监听要连接到此的套接字并接受它
  • 注意事项

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

    • 三次握手
      在这里插入图片描述

    • 四次挥手

在这里插入图片描述

  • 示例代码

    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();
        }
    }
    

3.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();
        }
    }
    

4.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();
        }
    }
    

5.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();
          }
      }
      
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。