BIO
BIO即阻塞IO(Blocking IO)。
基本设计思路
在服务端和客户端分别创建两个线程,用于消息收发。
- 服务端通过 ServerSocket 对象来创建服务,监听端口
- 客户端通过 Socket 对象来连接至服务端
代码实现
消息接收线程类,主要任务是循环接收来自 socket 的消息,并输出到终端。
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
/**
* 消息接收线程类
*
* @date 2019-11-20
*/
public class MessageReciever implements Runnable {
/**
* Socket 对象,处理消息
*/
private Socket socket;
/**
* 构造函数:接受一个 Socket 对象
*
* @param s socket对象
*/
public MessageReciever(Socket s) {
socket = s;
}
@Override
public void run() {
// 循环等待消息
while (true) {
try {
// step 1: 从socket中获取到输入流 ( getInputStream 方法 )
InputStream inputStream = socket.getInputStream();
// step 2: 设置一个缓冲区
byte[] buffer = new byte[1024];
// step 3: 从缓冲区中获取消息内容
int len = inputStream.read(buffer);
// step 4: 输出
System.out.println(">> " + new String(buffer, 0, len));
} catch (IOException ioe) {
ioe.printStackTrace();
break;
}
}
// 出错后关闭socket
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
消息发送线程类,主要任务是从终端读取用户输入,并通过 socket 发送出去
import java.io.OutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;
/**
* 消息发送线程
*
* @date 2019-11-20
*/
public class MessageDeliverer implements Runnable {
/**
* Socket 对象,处理消息
*/
private Socket socket;
/**
* 构造函数:接受一个 Socket 对象
*
* @param s socket对象
*/
public MessageDeliverer(Socket s) {
socket = s;
}
@Override
public void run() {
// 用于接收用户终端输入
Scanner scanner = new Scanner(System.in);
// 循环等待输入,并将每一行输入的内容发送至服务端
while (true) {
try {
// step 1: 从socket里获取到输出流( getOutputStream 方法 )
OutputStream outputstream = socket.getOutputStream();
// step 2: 从键盘获取一个字符串
String str = scanner.nextLine();
// step 3: 发送消息
outputstream.write(str.getBytes());
} catch (IOException ioe) {
ioe.printStackTrace();
scanner.close();
break;
}
}
// 出错后关闭socket
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端入口,创建一个 ServerSocket 对象,监听一个端口,并开启两个线程分别用户接收消息和发送消息
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端主线程
*
* @date 2019-11-20
*/
public class TcpServer {
public static void main(String args[]) {
// step 1: 创建服务端的 ServerSocket 对象
ServerSocket serverSocket = null;
// step 2: 创建服务端的 Socket 对象
Socket socket = null;
// step 3: 开启服务
try {
serverSocket = new ServerSocket(8080);
System.out.println("等待客户端连接...");
socket = serverSocket.accept();
System.out.println("客户端连接成功");
} catch (IOException e) {
try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException ioE) {
ioE.printStackTrace();
}
e.printStackTrace();
return;
}
Thread deliver = new Thread(new MessageDeliverer(socket));
deliver.start();
Thread reciever = new Thread(new MessageReciever(socket));
reciever.start();
}
}
客户端入口,创建一个 Socket 对象,连接到指定的 IP 地址,并开启两个线程分别用户接收消息和发送消息
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
/**
* 客户端主线程
*
* @date 2019-11-20
*/
public class TcpClient {
public static void main(String args[]) {
Socket socket = null;
try {
socket = new Socket(InetAddress.getLocalHost(), 8080);
} catch (IOException e) {
e.printStackTrace();
return;
}
Thread deliver = new Thread(new MessageDeliverer(socket));
deliver.start();
Thread reciever = new Thread(new MessageReciever(socket));
reciever.start();
}
}
运行结果
小结
此类连接方式为点对点连接。也就是说一个客户端(线程)对应一个服务端(线程)。
事实上,在此点对点连接的通信中,服务端和客户端是对等的。
基于 BIO 模式下的连接,每新添加一个客户端,服务端就会启动两个线程(此 demo 未实现多客户端连接的情况),当socket中没有新消息时,线程就一直阻塞等待。