栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

基于BIO的Java Socket通信详解

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

基于BIO的Java Socket通信详解

BIO,即阻塞IO,在基于Socket的消息通信过程中,Socket服务端向外部提供服务,而Socket客户端可以建立到Socket服务端的连接,进而发送请求数据,然后等待Socket服务端处理,并返回处理结果(响应)。
基于BIO的通信,Socket服务端会发生阻塞,即在监听过程中每次accept到一个客户端的Socket连接,就要处理这个请求,而此时其他连接过来的客户端只能阻塞等待。可见,这种模式下Socket服务端的处理能力是非常有限的,客户端也只能等待,直到服务端空闲时进行请求的处理。

BIO通信实现

下面基于BIO模式,来实现一个简单的Socket服务端与Socket客户端进行通信的逻辑,对这种通信方式有一个感性的认识。具体逻辑描述如下:
1、Socket客户端连接到Socket服务端,并发送数据“I am the client N.”;
2、Socket服务端,监听服务端口,并接收客户端请求数据,如果请求数据以“I am the client”开头,则响应客户端“I am the server, and you are the Nth client.”;

Socket服务端实现,代码如下所示:

package org.shirdrn.java.communications.bio; 
 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.net.ServerSocket; 
import java.net.Socket; 
 
 
public class SimpleBioTcpServer extends Thread { 
   
   
  private int port = 8888; 
   
  private static int sequence = 0; 
   
  public SimpleBioTcpServer(int port) { 
    this.port = port; 
  } 
   
  @Override 
  public void run() { 
    Socket socket = null; 
    try { 
      ServerSocket serverSocket = new ServerSocket(this.port); 
      while(true) { 
 socket = serverSocket.accept(); // 监听 
 this.handleMessage(socket); // 处理一个连接过来的客户端请求 
      } 
    } catch (IOException e) { 
      e.printStackTrace(); 
    } 
  } 
   
   
  private void handleMessage(Socket socket) throws IOException { 
    InputStream in = socket.getInputStream(); // 流:客户端->服务端(读) 
    OutputStream out = socket.getOutputStream(); // 流:服务端->客户端(写) 
    int receiveBytes; 
    byte[] receiveBuffer = new byte[128]; 
    String clientMessage = ""; 
    if((receiveBytes=in.read(receiveBuffer))!=-1) { 
      clientMessage = new String(receiveBuffer, 0, receiveBytes); 
      if(clientMessage.startsWith("I am the client")) { 
 String serverResponseWords =  
   "I am the server, and you are the " + (++sequence) + "th client."; 
 out.write(serverResponseWords.getBytes()); 
      } 
    } 
    out.flush(); 
    System.out.println("Server: receives clientMessage->" + clientMessage); 
  } 
 
  public static void main(String[] args) { 
    SimpleBioTcpServer server = new SimpleBioTcpServer(1983); 
    server.start(); 
  } 
} 

上述实现,没有进行复杂的异常处理。

Socket客户端实现,代码如下所示:

package org.shirdrn.java.communications.bio; 
 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.net.Socket; 
import java.net.UnknownHostException; 
import java.util.Date; 
 
 
public class SimpleBioTcpClient { 
   
  private String ipAddress; 
  private int port; 
  private static int pos = 0; 
   
  public SimpleBioTcpClient() {} 
   
  public SimpleBioTcpClient(String ipAddress, int port) { 
    this.ipAddress = ipAddress; 
    this.port = port; 
  } 
 
   
  public void send(byte[] data) { 
    Socket socket = null; 
    OutputStream out = null; 
    InputStream in = null; 
    try { 
      socket = new Socket(this.ipAddress, this.port); // 连接 
      // 发送请求 
      out = socket.getOutputStream(); 
      out.write(data);  
      out.flush(); 
      // 接收响应 
      in = socket.getInputStream(); 
      int totalBytes = 0; 
      int receiveBytes = 0; 
      byte[] receiveBuffer = new byte[128]; 
      if((receiveBytes=in.read(receiveBuffer))!=-1) { 
 totalBytes += receiveBytes; 
      } 
      String serverMessage = new String(receiveBuffer, 0, receiveBytes); 
      System.out.println("Client: receives serverMessage->" + serverMessage); 
    } catch (UnknownHostException e) { 
      e.printStackTrace(); 
    } catch (IOException e) { 
      e.printStackTrace(); 
    } catch (Exception e) { 
      e.printStackTrace(); 
    } finally { 
      try { 
 // 发送请求并接收到响应,通信完成,关闭连接 
 out.close(); 
 in.close(); 
 socket.close(); 
      } catch (IOException e) { 
 e.printStackTrace(); 
      } 
    } 
  } 
 
  public static void main(String[] args) { 
    int n = 1; 
    StringBuffer data = new StringBuffer(); 
    Date start = new Date(); 
    for(int i=0; i

首先启动Socket服务端进程SimpleBioTcpServer,然后再运行Socket客户端SimpleBioTcpClient。可以看到,服务端接收到请求数据,然后响应客户端,客户端接收到了服务端的响应数据。

上述实现中,对于Socket客户端和服务端都是一次写入,并一次读出,而在实际中如果每次通信过程中数据量特别大的话,服务器端是不可能接受的,可以在确定客户端请求数据字节数的情况,循环来读取并进行处理。

另外,对于上述实现中流没有进行装饰(Wrapped)处理,在实际中会有性能的损失,如不能缓冲等。

对于Socket服务端接收数据,如果可以使多次循环读取到的字节数据通过一个可变长的字节缓冲区来存储,就能方便多了,可是使用ByteArrayOutputStream,例如:

ByteArrayOutputStream data = new ByteArrayOutputStream(); 
data.write(receiveBuffer, totalBytes , totalBytes + receiveBytes); 

BIO通信测试

下面测试一下大量请求的场景下,Socket服务端处理的效率。

第一种方式:通过for循环来启动5000个Socket客户端,发送请求,代码如下所示:

public static void main(String[] args) { 
  int n = 5000; 
  StringBuffer data = new StringBuffer(); 
  Date start = new Date(); 
  for(int i=0; i

经过测试,大约需要9864ms,大概接近10s。

第二种方式:通过启动5000个独立的客户端线程,同时请求,服务端进行计数:

package org.shirdrn.java.communications.bio; 
 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.net.ServerSocket; 
import java.net.Socket; 
import java.net.UnknownHostException; 
import java.util.Date; 
 
 
public class SimpleBioTcpTest { 
   
  static int threadCount = 5000; 
   
   
  static class SocketServer extends Thread { 
     
     
    private int port = 8888; 
     
    private static int sequence = 0; 
     
    public SocketServer(int port) { 
      this.port = port; 
    } 
     
    @Override 
    public void run() { 
      Socket socket = null; 
      int counter = 0; 
      try { 
 ServerSocket serverSocket = new ServerSocket(this.port); 
 boolean flag = false; 
 Date start = null; 
 while(true) { 
   socket = serverSocket.accept(); // 监听 
   // 有请求到来才开始计时 
   if(!flag) { 
     start = new Date(); 
     flag = true; 
   } 
   this.handleMessage(socket); // 处理一个连接过来的客户端请求 
   if(++counter==threadCount) { 
     Date end = new Date(); 
     long last = end.getTime() - start.getTime(); 
     System.out.println(threadCount + " requests cost " + last + " ms."); 
   } 
 } 
      } catch (IOException e) { 
 e.printStackTrace(); 
      } 
    } 
     
     
    private void handleMessage(Socket socket) throws IOException { 
      InputStream in = socket.getInputStream(); // 流:客户端->服务端(读) 
      OutputStream out = socket.getOutputStream(); // 流:服务端->客户端(写) 
      int receiveBytes; 
      byte[] receiveBuffer = new byte[128]; 
      String clientMessage = ""; 
      if((receiveBytes=in.read(receiveBuffer))!=-1) { 
 clientMessage = new String(receiveBuffer, 0, receiveBytes); 
 if(clientMessage.startsWith("I am the client")) { 
   String serverResponseWords =  
     "I am the server, and you are the " + (++sequence) + "th client."; 
   out.write(serverResponseWords.getBytes()); 
 } 
      } 
      out.flush(); 
      System.out.println("Server: receives clientMessage->" + clientMessage); 
    } 
  } 
   
   
  static class SocketClient implements Runnable { 
     
    private String ipAddress; 
    private int port; 
     
    private String data; 
     
    public SocketClient(String ipAddress, int port) { 
      this.ipAddress = ipAddress; 
      this.port = port; 
    } 
 
    @Override 
    public void run() { 
      this.send();
    } 
     
     
    public void send() { 
      Socket socket = null; 
      OutputStream out = null; 
      InputStream in = null; 
      try { 
 socket = new Socket(this.ipAddress, this.port); // 连接 
 // 发送请求 
 out = socket.getOutputStream(); 
 out.write(data.getBytes());  
 out.flush(); 
 // 接收响应 
 in = socket.getInputStream(); 
 int totalBytes = 0; 
 int receiveBytes = 0; 
 byte[] receiveBuffer = new byte[128]; 
 if((receiveBytes=in.read(receiveBuffer))!=-1) { 
   totalBytes += receiveBytes; 
 } 
 String serverMessage = new String(receiveBuffer, 0, receiveBytes); 
 System.out.println("Client: receives serverMessage->" + serverMessage); 
      } catch (UnknownHostException e) { 
 e.printStackTrace(); 
      } catch (IOException e) { 
 e.printStackTrace(); 
      } catch (Exception e) { 
 e.printStackTrace(); 
      } finally { 
 try { 
   // 发送请求并接收到响应,通信完成,关闭连接 
   out.close(); 
   in.close(); 
   socket.close(); 
 } catch (IOException e) { 
   e.printStackTrace(); 
 } 
      } 
    } 
 
    public void setData(String data) { 
      this.data = data; 
    } 
  } 
 
  public static void main(String[] args) throws Exception { 
    SocketServer server = new SocketServer(1983); 
    server.start(); 
     
    Thread.sleep(3000); 
     
    for(int i=0; i

 经过测试,大约需要7110ms,大概接近7s,没有太大提高。

BIO通信改进

通过上面的测试我们可以发现,在Socket服务端对来自客户端的请求进行处理时,会发生阻塞,严重地影响了能够并发处理请求的效率。实际上,在Socket服务端接收来自客户端连接能力的范围内,可以将接收请求独立出来,从而在将处理请求独立粗话来,通过一个请求一个线程处理的方式来解决上述问题。这样,服务端是多处理线程对应客户端多请求,处理效率有一定程度的提高。

下面,通过单线程接收请求,然后委派线程池进行多线程并发处理请求:

 
  static class SocketServer extends Thread { 
     
     
    private int port = 8888; 
     
    private static int sequence = 0; 
     
    private ExecutorService pool; 
     
    public SocketServer(int port, int poolSize) { 
      this.port = port; 
      this.pool = Executors.newFixedThreadPool(poolSize); 
    } 
     
    @Override 
    public void run() { 
      Socket socket = null; 
      int counter = 0; 
      try { 
 ServerSocket serverSocket = new ServerSocket(this.port); 
 boolean flag = false; 
 Date start = null; 
 while(true) { 
   socket = serverSocket.accept(); // 监听 
   // 有请求到来才开始计时 
   if(!flag) { 
     start = new Date(); 
     flag = true; 
   } 
   // 将客户端请求放入线程池处理 
   pool.execute(new RequestHandler(socket)); 
   if(++counter==threadCount) { 
     Date end = new Date(); 
     long last = end.getTime() - start.getTime(); 
     System.out.println(threadCount + " requests cost " + last + " ms."); 
   } 
 } 
      } catch (IOException e) { 
 e.printStackTrace(); 
      } 
    } 
     
     
    class RequestHandler implements Runnable { 
 
      private Socket socket; 

      public RequestHandler(Socket socket) { 
 this.socket = socket; 
      } 

      @Override 
      public void run() { 
 try { 
   InputStream in = socket.getInputStream(); // 流:客户端->服务端(读) 
   OutputStream out = socket.getOutputStream(); // 流:服务端->客户端(写) 
   int receiveBytes; 
   byte[] receiveBuffer = new byte[128]; 
   String clientMessage = ""; 
   if((receiveBytes=in.read(receiveBuffer))!=-1) { 
     clientMessage = new String(receiveBuffer, 0, receiveBytes); 
     if(clientMessage.startsWith("I am the client")) { 
String serverResponseWords =  
  "I am the server, and you are the " + (++sequence) + "th client."; 
out.write(serverResponseWords.getBytes()); 
     } 
   } 
   out.flush(); 
   System.out.println("Server: receives clientMessage->" + clientMessage); 
 } catch (IOException e) { 
   e.printStackTrace(); 
 }  
      } 
    } 
  } 

可见,这种改进方式增强服务端处理请求的并发度,但是每一个请求都要由一个线程去处理,大量请求造成服务端启动大量进程进行处理,也是比较占用服务端资源的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/142577.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号