TCP 网络编程
一、 JAVA-IO分类:
BIO,NIO,AIO (NIO 2.0)。
BIO 同步阻塞IO (blocking I/O): 服务器处理客户端的连接请求业务需要开启一个线程来进行业务,这样连接的资源越多服务器承载的消耗会变大。
NIO 同步非阻塞 (non-blocking I/O): 同步非阻塞,客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时会得到需要处理的客户端对其进行处理。这种方式就大大减少了过多线程资源的开销。
AIO 异步非阻塞 (Asynchronous I/O) : 异步非阻塞,客户端连接或请求时,服务器的某些状态改变后通知对应处理方法。例如客户端连接服务器时,服务器知道客户端连接则通知处理accept方法,客户端发送给服务器数据,服务器知道有某客户端的数据则通知处理read方法。
NIO 实际上使用一个线程开始轮训选择有状态的客户端,发现某客户端有状态变化后挨个处理,这种处理方式业务实现上一般不要有过长的业务堵塞(例如 较长IO操作)。
AIO 实际上是有状态的客户端,调用一个线程来处理这部分业务。处理完之后如果还需要继续监听这个状态,则还需要重新注册。
以下代码可以copy到编辑器中调试,使用。
二、 BIO JAVA 实现
这里我实现一个简单的发送字符串的例子,服务器接受客户端发送的字符串,每一段完整的内容直接都使用\n进行分割。
客户端发送两条消息: 1234567890\n34567899\n
因为消息发送出来的时候,会出现两种情况:
1. 两个数据包粘在一起需要拆包。
2. 一个数据包第一次获取并不完整,分为多次获取才取完。
1. 服务器实现
BioServer.class
public class BioServer {
public static void main(String[] args) throws Exception {
// 创建一个服务器, 端口号 11111, 最大客户端连接: 10000
ServerSocket server = new ServerSocket(11111, 10000);
while (true) {
// 等待接受客户端的连接,如果有客户端连接则会返回Socket对象,没有则阻塞。
Socket client = server.accept();
// 这里需要开启一个线程来保存与这个客户端的连接,然后继续接受下一个客户端。
Thread thread = new Thread(new BioServerReaderHandler(client) ) ;
thread.start(); // 启动线程
}
}
}
BioServerReaderHandler.class
public class BioServerReaderHandler implements Runnable {
private Socket socket;
public BioServerReaderHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
byte[] buff = new byte[1024];
ByteArrayOutputStream cache = new ByteArrayOutputStream(1024);
while (!socket.isClosed() && !socket.isInputShutdown()) {
try {
int len = in.read(buff);
// 读到 \n 则输出
for (int i = 0; i < len; i++) {
byte b = buff[i];
// 通过 \n 用来分割每个数据包, 能够完整拆除一个包则直接输出
if (b == '\n') {
byte[] lineBytes = cache.toByteArray();
String text = new String(lineBytes, Charset.forName("UTF-8"));
// 客户端关闭客户端
if (text.trim().equals("quit")) return;
// 输出内容
System.out.println(text);
// 返回应答
out.write("已经收到一条消息\n".getBytes(Charset.forName("UTF-8")));
// 清空缓存
cache = new ByteArrayOutputStream(1024);
} else {
// 不满足条件则放入缓存
cache.write(b);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException e1) {
e1.printStackTrace();
} finally {
try {
socket.close();
} catch (Exception e) {
}
}
}
}
2. 客户端实现
这里写一个类似 telnet 的客户端, 通过控制台输入获取一行内容发送给服务器,服务器回传的信息显示到控制台上。
发送服务器的消息和接受服务器回复的消息都是 \n 分割。
如下就实现了一个简单的客户端,控制台的消息发送给服务器,服务器回复的消息再显示到控制台。
BioClient.class
public class BioClient {
public static void main(String[] args) throws Exception {
// 这里写一个类似 telnet 的客户端
Socket client = new Socket("127.0.0.1", 11111);
// 这里开启一个线程异步读取服务器返回的消息
Thread thread = new Thread(new BioClientReaderHandler(client));
thread.start();
// 获得向服务器输出的流对象
OutputStream out = client.getOutputStream();
Scanner in = new Scanner(System.in);
// 监听控制台写入的内容,发送给服务器
while (!client.isClosed() && !client.isOutputShutdown()) {
// 从控制台中读取一行数据 加上 \n 分割符 发送给服务器
String line = in.nextLine();
byte[] lineBytes = (line + "\n").getBytes(Charset.forName("UTF-8"));
out.write(lineBytes);
// 退出
if (line.trim().equals("quit")) {
break;
}
}
client.close();
}
}