网络编程(二)BIO
同步阻塞 IO(BIO)— 原生态
原理
graph TB
subgraph 客户端
id1[Socket]==连接服务器==>id11[通道];
id3[客户端请求]--流-->id2[OutputStream];
id2-->id1;
id1-.读取.->id12[InputStream];
id12-.流.->id13[BufferedReader];
id13-.信息.->id14[msgRes];
end
subgraph 服务器ServerSocket
id11==>id21[通道];
id4[Socket]==accept==>id21;
id4--读取-->id5[InputStream];
id5--流-->id6[BufferedReader]
id6--信息-->id7[msg];
id8[服务器响应]-.流.->id9[OutputStream];
id9.->id4;
end
实现
服务器端实现
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
public class BioServer {
//编码格式
private static Charset charset = Charset.forName("UTF-8");
public static void main(String[] args) {
int port = 1100;
//ServerSocket提供链接的场地
try ( ServerSocket socketServer = new ServerSocket(port)){
while (true){
//接收连接,如果没有连接建立,这里会阻塞
//Socket建立和客户端连接的通道
Socket socket = socketServer.accept();
//从通道中读取客户端发送给服务器的信息
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), charset)
);
String msg = null;
//连接进来后,会在这里等待客户端发送消息。
while ((msg =reader.readLine())!=null){
System.out.println("服务器收到信息:"+msg);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端实现
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class BioClient implements Runnable{
private String address;
private int port;
public static void main(String[] args) {
//客户端和服务器建立连接
BioClient client = new BioClient("localhost", 1100);
client.run();
}
@Override
public void run() {
try(Socket socket = new Socket(address, port);
OutputStream outputStream = socket.getOutputStream()) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入发送给服务器的信息:");
String msg = scanner.nextLine();
outputStream.write(msg.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public BioClient(String address, int port) {
super();
this.address=address;
this.port = port;
}
}
这就是一个简单的 BIO,我们可以看到:服务器端代码必须要在连接处和接收消息处阻塞,只用得到了相应的信息,才会往下继续执行。如果多个客户端一起请求,大家都会阻塞在此处,这样的程序效率及其低下,甚至长期等待不能用。有什么办法能够解决这样的问题呢?。
同步阻塞 IO(BIO)— 多线程
原理
graph TB
subgraph 客户端
id1[Socket]==连接服务器==>id11[通道];
id3{客户端请求}--流-->id2{OutputStream};
id2-->id1;
id1-.读取.->id12[InputStream];
id12-.流.->id13[BufferedReader];
id13-.信息.->id14[msgRes];
end
subgraph 服务器ServerSocket
id11==>id21[通道];
id4[Socket]==accept==>id21;
id4==投入线程池==>id22[newFixedThreadPool]
id22--读取-->id5[InputStream];
id5--流-->id6[BufferedReader]
id6--信息-->id7[msg];
id8[服务器响应]-.流.->id9{OutputStream};
id9.->id22;
end
服务器端多线程实现
import cn.mldn.info.HostInfo;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class BIOEchoServerThread {
public static void main(String[] args) throws Exception{
// 设置监听端口
ServerSocket serverSocket = new ServerSocket(HostInfo.PORT) ;
System.out.println("服务器端已经启动,监听的端口为:" + HostInfo.PORT);
boolean flag = true ;
while(flag) {
Socket client = serverSocket.accept() ;
new Thread(new EchoClientHandler(client)).start();
}
serverSocket.close() ;
}
private static class EchoClientHandler implements Runnable {
// 每一个客户端都需要启动一个任务(task)来执行。
private Socket client ;
private Scanner scanner ;
private PrintStream out ;
// 循环标记
private boolean flag = true ;
public EchoClientHandler(Socket client) {
// 保存每一个客户端操作
this.client = client ;
try {
this.scanner = new Scanner(this.client.getInputStream()) ;
// 设置换行符
this.scanner.useDelimiter("\n") ;
this.out = new PrintStream(this.client.getOutputStream()) ;
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(this.flag) {
// 现在有数据进行输入
if (this.scanner.hasNext()) {
// 去掉多余的空格内容
String val = this.scanner.next().trim() ;
System.err.println("{服务器端}" + val);
if("byebye".equalsIgnoreCase(val)) {
this.out.println("ByeByeByte...");
this.flag = false ;
} else {
out.println("【ECHO】" + val);
}
}
}
this.scanner.close();
this.out.close();
try {
this.client.close();
} catch (IOException e) {
}
}
}
}
这里是利用多线程的方式来实现,在连接处多开线程去处理,这样多个客户端连接时,就不会大家一致排队阻塞在此处,但是阻塞还是存在的。还有什么更好的办法来解决这样的问题吗?
服务器端线程池实现
import cn.mldn.info.HostInfo;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BIOEchoServer {
public static void main(String[] args) throws Exception{
// 设置监听端口
ServerSocket serverSocket = new ServerSocket(HostInfo.PORT) ;
System.out.println("服务器端已经启动,监听的端口为:" + HostInfo.PORT);
boolean flag = true ;
ExecutorService executorService = Executors.newFixedThreadPool(10) ;
while(flag) {
Socket client = serverSocket.accept() ;
executorService.submit(new EchoClientHandler(client)) ;
}
executorService.shutdown() ;
serverSocket.close() ;
}
private static class EchoClientHandler implements Runnable {
// 每一个客户端都需要启动一个任务(task)来执行。
private Socket client ;
private Scanner scanner ;
private PrintStream out ;
// 循环标记
private boolean flag = true ;
public EchoClientHandler(Socket client) {
// 保存每一个客户端操作
this.client = client ;
try {
this.scanner = new Scanner(this.client.getInputStream()) ;
// 设置换行符
this.scanner.useDelimiter("\n") ;
this.out = new PrintStream(this.client.getOutputStream()) ;
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(this.flag) {
// 现在有数据进行输入
if (this.scanner.hasNext()) {
// 去掉多余的空格内容
String val = this.scanner.next().trim() ;
System.err.println("{服务器端}" + val);
if("byebye".equalsIgnoreCase(val)) {
this.out.println("ByeByeByte...");
this.flag = false ;
} else {
out.println("【ECHO】" + val);
}
}
}
this.scanner.close();
this.out.close();
try {
this.client.close();
} catch (IOException e) {
}
}
}
}
如上所示,我们再次改动,改成以线程池的办法来连接。但是线程池也有问题:
请求大于线程,没有足够的线程来处理,响应时间很长,甚至服务器拒绝。
阻塞等待接收客户端的数据时,这段时间占着线程,而池中线程数是有限的,并发量大时,将导致没有线程处理请求,请求的响应时间长,甚至拒绝服务。
从上面三部分的代码可以看出来,代码性能等问题,全是阻塞惹的祸,如果没有阻塞就好了,如果能不阻塞,在没有数据时,就去干点别的事情,有数据了才处理数据。
客户端实现
import cn.mldn.info.HostInfo;
import cn.mldn.util.InputUtil;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class BIOEchoClient {
public static void main(String[] args) throws Exception {
// 定义连接的主机信息
Socket client = new Socket(HostInfo.HOST_NAME,HostInfo.PORT) ;
// 获取服务器端的响应数据
Scanner scan = new Scanner(client.getInputStream()) ;
scan.useDelimiter("\n") ;
// 向服务器端发送信息内容
PrintStream out = new PrintStream(client.getOutputStream()) ;
// 交互的标记
boolean flag = true ;
while(flag) {
String inputData = InputUtil.getString("请输入要发送的内容:").trim() ;
// 把数据发送到服务器端上
out.println(inputData);
if(scan.hasNext()) {
String str = scan.next().trim() ;
System.out.println(str);
}
if ("byebye".equalsIgnoreCase(inputData)) {
flag = false ;
}
}
client.close();
}
}