目录
一、常见的客户端服务端模型
客户端:用户使用的程序。
服务端:给用户提供服务的程序。
整体流程:
- 客户端发送请求到服务端。
- 服务端根据客户端的的请求数据,执行相应的业务处理。
- 服务端返回响应:发送处理业务的处理结果。
- 客户端得到处理结果,展示处理结果。
二、Socket套接字
1、概念
Socket套接字是系统提供用于网络通信的技术,也是TCP/IP协议的网络通信的基本操作单元,并且基于Socket套接字的网络程序开发就是网络编程。
2、分类
a、流套接字
流套接字使用的是TCP协议(传输控制协议),TCP协议有如下特点:
- 有连接。
- 可靠通信。
- 面向字节流传输。
- 全双工。
- 传输数据的大小不受限制。
全双工指的是通信双方可以同时发送数据进行通信,还有半双工通信:通信双方不能同时发送数据,单工通信就是只能由一方一直发送数据。
b、数据报套接字
数据报套接字使用的是UDP协议(用户数据报协议),UDP协议有如下的特点:
- 无连接。
- 不可靠。
- 面向用户数据报传输。
- 全双工。
- 传输数据的大小有限制,一次最多传输64K。
c、原始套接字
原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。
三、UDP数据报套接字编程
UDP编程具有两个核心类:
DatagramSocket:是UDP版本的Socket对象,用于接收和发送数据报。有如下三个方法:
receive:发送数据。
send:发送数据。
close:释放资源。
DatagramPacket:表示一个UDP数据报,每次发送和接收的都是DatagramPacket对象。
服务器端代码实现:
整体流程:
- 首先定义一个DatagramSocket对象,用于收发数据。
- 定义一个指定端口号的构造方法,在构造方法中完成实例化对象。
- 定义start方法中,循环处理请求:定义DatagramPacket对象接收请求,处理请求后,再定义一个DatagramPacket对象发送数据。
  
   - 
    
     
    
    
     
      public 
      class 
      UDPEchoServer {
     
    
- 
    
     
    
    
         
      //首先定义一个Socket对象
     
    
- 
    
     
    
    
         
      private 
      DatagramSocket 
      datagramSocket 
      = 
      null;
     
    
- 
    
     
    
    
         
      //构造方法:指定一个端口号
     
    
- 
    
     
    
    
         
      public 
      UDPEchoServer
      (int port) 
      throws SocketException {
     
    
- 
    
     
    
    
     
              datagramSocket = 
      new 
      DatagramSocket(port);
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
         
      //启动服务器
     
    
- 
    
     
    
    
         
      public 
      void 
      start
      () 
      throws IOException {
     
    
- 
    
     
    
    
     
              System.out.println(
      "服务器已经启动!");
     
    
- 
    
     
    
    
             
      while(
      true){
     
    
- 
    
     
    
    
                 
      //数据报
     
    
- 
    
     
    
    
                 
      DatagramPacket 
      requestPacket 
      = 
      new 
      DatagramPacket(
      new 
      byte[
      1024],
      1024);
     
    
- 
    
     
    
    
                 
      //接收数据报
     
    
- 
    
     
    
    
     
                  datagramSocket.receive(requestPacket);
     
    
- 
    
     
    
    
                 
      //将接收到的数据报转成字符串
     
    
- 
    
     
    
    
                 
      String 
      request 
      = 
      new 
      String(requestPacket.getData(),
      0,requestPacket.getLength(),
      "UTF-8");
     
    
- 
    
     
    
    
                 
      //处理数据,返回响应
     
    
- 
    
     
    
    
                 
      String 
      response 
      = process(request);
     
    
- 
    
     
    
    
                 
      //将响应返回给客户端
     
    
- 
    
     
    
    
                 
      DatagramPacket 
      responsePacket 
      = 
      new 
      DatagramPacket(response.getBytes(),
      0,response.getBytes().length,
     
    
- 
    
     
    
    
     
                          requestPacket.getSocketAddress());
     
    
- 
    
     
    
    
     
                  datagramSocket.send(responsePacket);
     
    
- 
    
     
    
    
                 
      //展示
     
    
- 
    
     
    
    
     
                  System.out.println(
      "[客户IP"+responsePacket.getAddress()+
      "客户端口"+responsePacket.getPort()+
     
    
- 
    
     
    
    
                         
      "请求"+request+
      "响应"+response);
     
    
- 
    
     
    
    
     
              }
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
         
      //简单的回显服务来处理数据
     
    
- 
    
     
    
    
         
      public String 
      process
      (String request){
     
    
- 
    
     
    
    
             
      return request;
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      public 
      static 
      void 
      main
      (String[] args) 
      throws IOException {
     
    
- 
    
     
    
    
             
      UDPEchoServer 
      udpEchoServer 
      = 
      new 
      UDPEchoServer(
      9090);
     
    
- 
    
     
    
    
     
              udpEchoServer.start();
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
     
      }
     
    
- 
    
     
    
    
      
     
    
注意:
服务器的端口号一般是指定的,因为在客户端程序中需要指定服务器端的的端口号。
响应服务可以先别的,这里只是简单的回显服务。
客户端代码实现:
整体流程:
- 首先定义一个DatagramSocket对象,用于收发数据。
- 定义一个构造方法指定服务器端的IP地址和端口号,并完成DatagramSocket对象实例。
- 定义start方法,循环发送请求,先定义DatagramPacket对象包装请求,发送到指定的服务器端,再定义一个DatagramPacket对象接收响应处理后的数据。
  
   - 
    
     
    
    
     
      public 
      class 
      UDPEchoClient {
     
    
- 
    
     
    
    
         
      //定义Socket对象
     
    
- 
    
     
    
    
         
      DatagramSocket 
      datagramSocket 
      =
      null;
     
    
- 
    
     
    
    
         
      private 
      int serverPort;
     
    
- 
    
     
    
    
         
      private String serverIP;
     
    
- 
    
     
    
    
         
      //构造方法时传入的是服务器端的IP地址和端口号
     
    
- 
    
     
    
    
         
      public 
      UDPEchoClient
      (int port,String IP) 
      throws SocketException {
     
    
- 
    
     
    
    
     
              datagramSocket = 
      new 
      DatagramSocket();
     
    
- 
    
     
    
    
     
              serverPort = port;
     
    
- 
    
     
    
    
     
              serverIP = IP;
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
         
      //启动客户端
     
    
- 
    
     
    
    
         
      public 
      void 
      start
      () 
      throws IOException {
     
    
- 
    
     
    
    
             
      Scanner 
      sc 
      = 
      new 
      Scanner(System.in);
     
    
- 
    
     
    
    
             
      while (
      true){
     
    
- 
    
     
    
    
     
                  System.out.println(
      "发送请求>");
     
    
- 
    
     
    
    
                 
      String 
      request 
      = sc.next();
     
    
- 
    
     
    
    
                 
      //发送UDP请求
     
    
- 
    
     
    
    
                 
      DatagramPacket 
      requestPacket 
      = 
      new 
      DatagramPacket(request.getBytes(),request.getBytes().length,
     
    
- 
    
     
    
    
     
                          InetAddress.getByName(serverIP),serverPort);
     
    
- 
    
     
    
    
                 
      //发送数据
     
    
- 
    
     
    
    
     
                  datagramSocket.send(requestPacket);
     
    
- 
    
     
    
    
                 
      //读取数据并进行解析
     
    
- 
    
     
    
    
                 
      DatagramPacket 
      responsePacket 
      = 
      new 
      DatagramPacket(
      new 
      byte[
      1024],
      1024);
     
    
- 
    
     
    
    
     
                  datagramSocket.receive(responsePacket);
     
    
- 
    
     
    
    
                 
      String 
      response 
      = 
      new 
      String(responsePacket.getData(),
      0,responsePacket.getLength(),
      "UTF-8");
     
    
- 
    
     
    
    
                 
      //打印响应结果
     
    
- 
    
     
    
    
     
                  System.out.println(response);
     
    
- 
    
     
    
    
     
              }
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      public 
      static 
      void 
      main
      (String[] args) 
      throws IOException {
     
    
- 
    
     
    
    
             
      UDPEchoClient 
      udpEchoClient 
      = 
      new 
      UDPEchoClient(
      2580,
      "127.0.0.1");
     
    
- 
    
     
    
    
     
              udpEchoClient.start();
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
     
      }
     
    
注意:
客户端程序一般需要系统自动分配,因为你不知道此时那个端口号没有被占用,而且客户端的个数一般多于服务器端的数目。在客户端的构造方法中需要指定服务器端的IP地址和端口号。
在启动程序时,先启动服务器端,再启动应用器端。
例如:将回显服务改变成数据+收到。
  
   - 
    
     
    
    
     
      public 
      class 
      UDPEChoServer2 
      extends 
      UDPEchoServer {
     
    
- 
    
     
    
    
         
      public 
      UDPEChoServer2
      (int port) 
      throws SocketException {
     
    
- 
    
     
    
    
             
      super(port);
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
         
      public String  
      process 
      (String request){
     
    
- 
    
     
    
    
             
      return request+
      "收到";
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      public 
      static 
      void 
      main
      (String[] args) 
      throws IOException {
     
    
- 
    
     
    
    
             
      UDPEChoServer2 
      udpeChoServer2 
      = 
      new 
      UDPEChoServer2(
      2580);
     
    
- 
    
     
    
    
     
              udpeChoServer2.start();
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
     
      }
     
    
运行结果:


四、TCP数据报套接字编程
TCP编程的核心类:
ServerSocket:是服务器端的流套接字,创建实例对象时,可以绑定指定的端口,
accept方法:用于监听指定端口,有客户端连接的话就会返回一个Socket对象,基于该Socket对象与客户端建立连接,否则就会发生阻塞等待。
Socket:客户端与服务器端建立连接后,该类对象用于通信双方进行收发数据。创建实例对象时要绑定服务器端的IP地址和端口号。
有以下常用方法:
getInetAddress:返回套接字所连接的地址。
getInputStream:返回此套接字的输入流。
getOutputStream:返回此套接字的输出流。
服务器端代码实现:
整体流程:
- 定义一个ServerSocket对象。
- 定义指定端口号的构造方法。
- 定义start方法来启动,创建Socket对象建立连接。
- 定义processConnection方法循环处理请求,先得到请求进行处理之后将结果返回给客户端。
  
   - 
    
     
    
    
     
      public 
      class 
      TCPEchoServer {
     
    
- 
    
     
    
    
         
      //定义一个服务器对象
     
    
- 
    
     
    
    
         
      ServerSocket 
      serverSocket 
      =
      null;
     
    
- 
    
     
    
    
         
      public 
      TCPEchoServer
      (int port) 
      throws IOException {
     
    
- 
    
     
    
    
     
              serverSocket = 
      new 
      ServerSocket(port);
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
         
      //开始
     
    
- 
    
     
    
    
         
      public 
      void 
      start
      () 
      throws IOException {
     
    
- 
    
     
    
    
     
              System.out.println(
      "服务器已经启动");
     
    
- 
    
     
    
    
             
      while(
      true){
     
    
- 
    
     
    
    
                 
      //利用Socket对象来建立连接
     
    
- 
    
     
    
    
                 
      Socket 
      clientSocket 
      = serverSocket.accept();
     
    
- 
    
     
    
    
     
                  processConnection(clientSocket);
     
    
- 
    
     
    
    
     
              }
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      private 
      void 
      processConnection
      (Socket clientSocket) {
     
    
- 
    
     
    
    
     
              System.out.println(
      "[" + clientSocket.getInetAddress().toString() + 
      " "+clientSocket.getPort()+
      "]已经建立连接");
     
    
- 
    
     
    
    
             
      try(
      InputStream 
      inputStream 
      = clientSocket.getInputStream()){
     
    
- 
    
     
    
    
                 
      try(
      OutputStream 
      outputStream 
      = clientSocket.getOutputStream()){
     
    
- 
    
     
    
    
                     
      //读取请求
     
    
- 
    
     
    
    
                     
      Scanner 
      sc 
      = 
      new 
      Scanner(inputStream);
     
    
- 
    
     
    
    
                     
      //循环处理请求
     
    
- 
    
     
    
    
                     
      while(
      true){
     
    
- 
    
     
    
    
                         
      if(!sc.hasNext()){
     
    
- 
    
     
    
    
     
                              System.out.println(
      "[" + clientSocket.getInetAddress().toString() + 
      "]已经断开连接");
     
    
- 
    
     
    
    
                             
      break;
     
    
- 
    
     
    
    
     
                          }
     
    
- 
    
     
    
    
                         
      String 
      request 
      = sc.next();
     
    
- 
    
     
    
    
                         
      //对请求做出响应
     
    
- 
    
     
    
    
                         
      String 
      response 
      = process(request);
     
    
- 
    
     
    
    
                         
      //将响应返回给客户端
     
    
- 
    
     
    
    
                         
      PrintWriter 
      printWriter 
      = 
      new 
      PrintWriter(outputStream);
     
    
- 
    
     
    
    
     
                          printWriter.println(response);
     
    
- 
    
     
    
    
                         
      //刷新缓存区,使客户端尽快得到响应结果
     
    
- 
    
     
    
    
     
                          printWriter.flush();
     
    
- 
    
     
    
    
     
                      }
     
    
- 
    
     
    
    
     
                  }
     
    
- 
    
     
    
    
     
              } 
      catch (IOException e) {
     
    
- 
    
     
    
    
     
                  e.printStackTrace();
     
    
- 
    
     
    
    
     
              }
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      private String 
      process
      (String request) {
     
    
- 
    
     
    
    
             
      return request;
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      public 
      static 
      void 
      main
      (String[] args) 
      throws IOException {
     
    
- 
    
     
    
    
             
      TCPEchoServer 
      tcpEchoServer 
      = 
      new 
      TCPEchoServer(
      9087);
     
    
- 
    
     
    
    
     
              tcpEchoServer.start();
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
     
      }
     
    
客户端代码实现:
整体流程:
- 定义一个Socket对象。
- 定义一个指定服务器的IP地址和端口号的构造方法,并完成对Socket对象的实例。
- 定义start方法,循环发送请求,然后接收响应。
  
   - 
    
     
    
    
     
      public 
      class 
      TCPEchoClint {
     
    
- 
    
     
    
    
         
      Socket 
      socket 
      =
      null;
     
    
- 
    
     
    
    
         
      public 
      TCPEchoClint
      (int serverPort,String serverIP) 
      throws IOException {
     
    
- 
    
     
    
    
             
      //与指定的服务器端建立连接
     
    
- 
    
     
    
    
     
              socket = 
      new 
      Socket(serverIP,serverPort);
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
         
      //启动
     
    
- 
    
     
    
    
         
      public 
      void 
      start
      (){
     
    
- 
    
     
    
    
     
              System.out.println(
      "客户端已经连接到服务器端");
     
    
- 
    
     
    
    
             
      Scanner 
      sc 
      =
      new 
      Scanner(System.in);
     
    
- 
    
     
    
    
             
      try(
      InputStream 
      inputStream 
      = socket.getInputStream()) {
     
    
- 
    
     
    
    
                 
      try(
      OutputStream 
      outputStream 
      = socket.getOutputStream()){
     
    
- 
    
     
    
    
                     
      //循环发送请求
     
    
- 
    
     
    
    
                     
      while(
      true){
     
    
- 
    
     
    
    
                         
      String 
      request 
      = sc.next();
     
    
- 
    
     
    
    
                         
      //发送请求
     
    
- 
    
     
    
    
                         
      PrintWriter 
      printWriter 
      =
      new 
      PrintWriter(outputStream);
     
    
- 
    
     
    
    
     
                          printWriter.println(request);
     
    
- 
    
     
    
    
     
                          printWriter.flush();
     
    
- 
    
     
    
    
                         
      Scanner 
      scanner 
      =
      new 
      Scanner(inputStream);
     
    
- 
    
     
    
    
                         
      String 
      response 
      = scanner.next();
     
    
- 
    
     
    
    
     
                          System.out.println(
      "response:"+response);
     
    
- 
    
     
    
    
     
                      }
     
    
- 
    
     
    
    
     
                  }
     
    
- 
    
     
    
    
     
              } 
      catch (IOException e) {
     
    
- 
    
     
    
    
     
                  e.printStackTrace();
     
    
- 
    
     
    
    
     
              }
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
         
      public 
      static 
      void 
      main
      (String[] args) 
      throws IOException {
     
    
- 
    
     
    
    
             
      TCPEchoClint 
      tcpEchoClint 
      = 
      new 
      TCPEchoClint(
      9087,
      "127.0.0.1");
     
    
- 
    
     
    
    
     
              tcpEchoClint.start();
     
    
- 
    
     
    
    
     
          }
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     
      }
     
    
但是这样实现服务器端代码存在BUG, 当再启动一个客户端就连接不到服务器,为什么呢?
 
 
 
 
原因分析:在服务器端的start方法中,利用while循环来建立连接,并调用processConnection方法,但是在processConnection方法中又有一个while循环处理请求,这样只要一个客户端请求未结束,别的客户端就连接不到服务器端,这显然不符合实际。
解决方案:在start方法中创建线程,使多个客户端能并发连接到服务器端。
  
   - 
    
     
    
    
     
      public 
      void 
      start
      () 
      throws IOException {
     
    
- 
    
     
    
    
     
              System.out.println(
      "服务器已经启动");
     
    
- 
    
     
    
    
             
      while(
      true){
     
    
- 
    
     
    
    
                 
      //利用Socket对象来建立连接
     
    
- 
    
     
    
    
                 
      Socket 
      clientSocket 
      = serverSocket.accept();
     
    
- 
    
     
    
    
                 
      Thread 
      t 
      = 
      new 
      Thread(
     
    
- 
    
     
    
    
     
                          ()-> processConnection(clientSocket)
     
    
- 
    
     
    
    
     
                  ) ;
     
    
- 
    
     
    
    
     
                  t.start();
     
    
- 
    
     
    
    
     
              }
     
    
- 
    
     
    
    
     
          }
     
    
转载:https://blog.csdn.net/m0_62404808/article/details/128772267
