Java网络编程之BIO
由于工作需要搭建一个TCP服务,之前忽略了这块,最近捡起来,顺便记录一下。
1. 什么是BIO?
在JDK1.4
之前,基于Java的所有Socket通信都使用的是同步阻塞I/O(Blocking I/O
),这也被称为传统阻塞型I/O,还有称之为旧的阻塞I/O(Old I/O
,即OIO
)。
OIO是相对于NIO来说的,因为NIO也被称为新的IO,即New I/O。NIO会在下篇文章中介绍。
BIO
的服务器实现模式为一个连接一个线程
,即客户端有连接请求时,服务器就需要启动一个线程进行处理,如果这个连接不做任何事情,就会造成不必要的线程开销(可以通过线程池改善)。示意图如下:
2. BIO的特点和使用场景
特点:
- 同步并阻塞
- 一个连接对应一个线程
- 线程开销大
- 对服务器资源要求较高
- 程序简单易理解
使用场景:
emmm~,我觉得现在应该没有人会用这个了,除了在学习过程中。如果有的话,那它的使用场景必是:连接数目小且架构稳定。
在JDK1.4
以前,BIO是唯一的选择。
3. 代码实现
服务端代码:
public class SocketServer {
public static void main(String[] args) throws Exception {
// 创建一个ServerSocket对象,指定服务端端口、地址
ServerSocket serverSocket = new ServerSocket(7072, 50, InetAddress.getByName("localhost"));
System.out.println("服务器启动:" + serverSocket);
while (true) {
System.out.println("等待连接...");
// 等待客户端连接
Socket activeSocket = serverSocket.accept();
System.out.println("接收到一个连接,来自:" + activeSocket);
// 接收到一个连接,就开启一个线程处理
Runnable runnable = new Runnable() {
@Override
public void run() {
handleClientRequest(activeSocket);
}
};
new Thread(runnable).start();
}
}
private static void handleClientRequest(Socket socket) {
BufferedReader socketReader = null;
BufferedWriter socketWrite = null;
try {
// 通过socket获取字节流,然后包装成字符缓冲流
socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
socketWrite = new BufferedWriter(new OutputStreamWriter(new BufferedOutputStream(socket.getOutputStream())));
String inMsg = null;
// 获取客户端传输到服务端的消息
while ((inMsg = socketReader.readLine()) != null) {
System.out.println("接收到客户端的消息:" + inMsg);
String outMsg = "喵喵喵";
// 向客户端响应消息
socketWrite.write(outMsg);
socketWrite.write("\n");
socketWrite.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 手动关闭资源
if (socketReader != null) {
try {
socketReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socketWrite != null) {
try {
socketWrite.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端可以不用编写代码,使用Windows
自带的telnet
功能,向服务端发送消息,由于Windows
的命令行是GBK
的编码,与服务器不同,发送内容或响应内容如果有中文字符,就会出现乱码,涉及字符集转换,这里就不演示了。想用telnet
功能调试的话,可以找一下telnet
命令使用。
下面给出客户端代码。
客户端代码:
public class SocketClient {
public static void main(String[] args) throws IOException {
// 创建socket,并指定服务器的ip(host) 和 端口
Socket socket = new Socket("localhost", 7072);
// 获取socket所绑定的本地地址
System.out.println("启动客户端:" + socket.getLocalAddress());
// 通过socket获取字节流,并包装成字符缓冲流
BufferedReader socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter socketWrite = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
// 通过控制台输入发送给服务端的消息,并把字节流包装成字符缓冲流
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
String promptMsg = "请输入消息(输入Bye退出):";
String outMsg = null;
// 提示语
System.out.println(promptMsg);
// 获取控制台输入内容,每次一行
while ((outMsg = consoleReader.readLine()) != null) {
if (outMsg.equalsIgnoreCase("bye")) {
break;
}
// 向服务器发送一行消息,因为服务器每次读取一行
socketWrite.write(outMsg);
socketWrite.write("\n");
socketWrite.flush();
// 读取并显示来自服务器的消息
String inMsg = socketReader.readLine();
System.out.println("来自服务器的消息:" + inMsg);
System.out.println(); // 输出一个空白行
System.out.println(promptMsg);
}
// 关闭资源,socket关闭时,其对应的流也会关闭,为了防止内存泄漏,
// 可以手动关闭其他流对象,这里偷个懒
socket.close();
}
}
操作演示
分别运行服务端和客户端(注意这里需要先运行服务端,后运行客户端),就会看到如下信息:
服务端:
客户端:
客户端启动后,服务端就能接收到客户端的连接,如下所示:
由于我们服务端使用的是死循环并且每次接收到新的连接都会创建一个线程进行处理,所以只要服务端资源允许,就可以一直接收客户端的请求。
现在向服务端发送消息:
服务端接收到的消息: