最简单的BIO代码
通过Socket通信,服务端使用ServerSocket最后也是获得socket。
这里有一个坑,在使用Scanner.nextLine()方法时,会吃掉最后的回车符,如果服务端把没有回车符的消息回写到客户端,客户端再使用Scanner.nextLine()时就读不到该行了,会一直阻塞,BufferReader.readLine()也是同样的问题。解决方案,手动加上回车符System.lineSeparator。
还有另外一个坑,客户端代码中需要Scanner键盘输入,而idea编辑器不能在控制台输入,eclipse则可以,因为是idea中使用junit测试时无法使用键盘输入,解决方案是,在idea中把@Test下的方法改为main方法即可。
package com.example.io.bio;
import org.junit.Test;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.Scanner;
public class BioEchoExample {
// 服务器
@Test
public void bioEchoServer() throws IOException {
// 1. bio即普通socket编程,服务端使用ServerSocket,客户端使用Socket
ServerSocket serverSocket = new ServerSocket();
// 2. 使用InetSocketAddress绑定地址,客户端是连接地址
serverSocket.bind(new InetSocketAddress(8899));
// 3. 死循环持续等待连接
while (true) {
// 4. 此处阻塞等待连接,连接成功后获取到与客户端交互的Socket实例
Socket socket = serverSocket.accept();
System.out.println("服务端连接建立");
// 5. 获取成功后交由线程去处理,不同步处理了,这里使用匿名类不重新写Class文件了避免麻烦
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true){
// 6. 因为作用域问题,这里的socket是上面4定义的socket,如果是定义了新的Class线程类,需要在构造器中传入socket
// 7. 获取输入流重新写回socket传到客户端
Scanner scanner = new Scanner(socket.getInputStream(), "utf8");
String msg = scanner.nextLine() + System.lineSeparator();
System.out.print("来自客户端的数据:" + msg);
OutputStream outputStream = socket.getOutputStream();
outputStream.write(msg.getBytes("utf8"));
}
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
// 8. 流关闭,请自行添加上
}
// 客户端
@Test
//public static void main(String[] args) throws Exception {
public void bioEchoClient() throws IOException{
// 1. 新建socket
Socket socket = new Socket();
// 2. 连接服务器, 这里会阻塞
socket.connect(new InetSocketAddress("localhost", 8899));
System.out.println("客户端连接建立");
while (true) {
Scanner keyBoardScanner = new Scanner(System.in, "utf8");
String clientMsg = keyBoardScanner.nextLine() + System.lineSeparator();
// 3. 获取输出流,输出到服务端
OutputStream outputStream = socket.getOutputStream();
outputStream.write(clientMsg.getBytes(Charset.forName("utf8")));
Scanner serverScanner = new Scanner(socket.getInputStream(), "utf8");
String serverMsg = serverScanner.nextLine() + System.lineSeparator();
System.out.print("来自服务端的消息:" + serverMsg);
}
}
}
最简单的NIO代码
通过SocketChannel通信,服务端使用ServerSocketChannel最后也是获得SocketChannel,跟BIO的原理类似。
这里也有一个坑,使用ByteBuffer.wrap()后,如果要读取数据,这时不能使用flip()重置,否者读不到数据。
package com.example.io.bio;
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Scanner;
import java.util.Set;
public class NioEchoExample {
// 服务器
@Test
public void NioEchoServer() throws IOException {
// 1.新建服务端通道,配置为非阻塞模式
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
// 2.绑定地址
serverSocketChannel.bind(new InetSocketAddress(8899));
// 3.新建通道选择器并注册监听连接事件
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 4. 死循环服务
try{
while (true) {
// 5.select()方法会阻塞直到selector上注册的事件发生了
selector.select();
// 6.select()方法有事件发生的话,会返回一个set集合
Set selectionKeySet = selector.selectedKeys();
for (SelectionKey s : selectionKeySet) {
if (s.isAcceptable()){
System.out.println("服务端连接建立");
// 7.连接已建立,可以获取SocketChannel进行通信了,此处accept()因为已经有连接进来,不会再堵塞了
ServerSocketChannel ssc = (ServerSocketChannel) s.channel();
SocketChannel socketChannel = ssc.accept();
socketChannel.configureBlocking(false);
// 8.注册通道事件,监听客户端发来的消息并分配buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
socketChannel.register(selector, SelectionKey.OP_READ, byteBuffer);
}else if (s.isReadable()) {
// 9.客户端发来了消息
System.out.print("服务端收到消息, ");
SocketChannel sc = (SocketChannel) s.channel();
ByteBuffer byteBuffer = (ByteBuffer) s.attachment();
// 清空通道,写入数据到通道
byteBuffer.clear();
sc.read(byteBuffer);
// 要打印日志显示出来,切为读模式
byteBuffer.flip();
System.out.println("消息内容:" + Charset.forName("utf8").decode(byteBuffer).toString());
// 10.客户端发来消息后,监听写模式,写回客户端,把byteBuffer切换为读模式
sc.register(selector, SelectionKey.OP_WRITE, byteBuffer);
}else if (s.isWritable()) {
System.out.print("服务器写回客户端,");
SocketChannel sc = (SocketChannel) s.channel();
// 将客户端发的消息获取出来
ByteBuffer byteBuffer = (ByteBuffer) s.attachment();
// 要显示出来,切为读模式
byteBuffer.flip();
System.out.println("消息内容:" + Charset.forName("utf8").decode(byteBuffer).toString());
// 上面显示消息读取完后,position指针在末尾,需要在flip一下
byteBuffer.flip();
sc.write(byteBuffer);
// 重新注册为读
sc.register(selector, SelectionKey.OP_READ, byteBuffer);
}
}
// 事件处理完,清空
selectionKeySet.clear();
}
}catch (Exception e) {
// nop
}
}
// 客户端
@Test
public static void main(String[] args) throws IOException {
//public void NioEchoClient() throws IOException {
// 新建SocketChannel,设置为非阻塞并连接服务器
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("localhost", 8899));
// 新建选择器
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 死循环
while (true) {
selector.select();
Set selectionKeySet = selector.selectedKeys();
for (SelectionKey s : selectionKeySet) {
if (s.isConnectable()) {
System.out.println("客户端已连接");
SocketChannel sc = (SocketChannel) s.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 可连接切换为已连接上,侦听写事件
sc.finishConnect();
sc.register(selector, SelectionKey.OP_WRITE, byteBuffer);
}else if (s.isWritable()) {
System.out.print("客户端发消息给服务端,请输入:");
SocketChannel sc = (SocketChannel) s.channel();
Scanner scanner = new Scanner(System.in, "utf8");
String msg = scanner.nextLine()+System.lineSeparator();
ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes("utf8"));
// 显示出来
//byteBuffer.flip();wrap构造的字符这里不能flip了,否则输出为空
System.out.println("内容为:" + Charset.forName("utf8").decode(byteBuffer).toString());
byteBuffer.flip();
sc.write(byteBuffer);
sc.register(selector, SelectionKey.OP_READ, byteBuffer);
}else if (s.isReadable()) {
System.out.print("客户端收到服务端发来的消息,");
SocketChannel sc = (SocketChannel) s.channel();
ByteBuffer byteBuffer = (ByteBuffer) s.attachment();
byteBuffer.clear();
sc.read(byteBuffer);
byteBuffer.flip();
System.out.println("内容为:" + Charset.forName("utf8").decode(byteBuffer).toString());
sc.register(selector, SelectionKey.OP_WRITE, byteBuffer);
}
}
selectionKeySet.clear();
}
}
}
Netty代码
使用netty的组件EventLoopGroup, Bootstrap, ChannelInitializer, ChannelHandler。ChannelInitializer在初始化时添加一些handler处理器,如编码解码器,其中最后一个为自己的业务处理器。
这里也有个坑,不能使用junit运行启动,需要使用main方法启动。
package com.example.io.bio;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.sctp.SctpNotificationHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.junit.Test;
import java.util.Scanner;
public class NettyEchoExample {
//@Test
public static void main(String[] args) throws InterruptedException {
//public void nettyEchoServer() throws InterruptedException {
// 新建事件循环组,相当于while死循环使程序不退出,boss负责接收连接,workder复制处理连接
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
// 服务器启动类,设置父子事件循环组,并添加父子处理器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer() {
// 子处理器中Channel初始化时加入业务处理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 基于行的编码解码器
pipeline.addLast(new LineBasedFrameDecoder(1024));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
// 自己的业务处理代码,数据写回客户端
pipeline.addLast(new SimpleChannelInboundHandler() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
System.out.println("服务端收到消息,内容为:" + s);
System.out.println("服务端写回客户端,内容为:" + s);
channelHandlerContext.channel().writeAndFlush(s);
}
});
}
});
// 绑定端口
serverBootstrap.bind(8899).sync().channel().closeFuture().sync();
}
//@Test
//public static void main(String[] args) throws InterruptedException {
public void nettyEchoClient() throws InterruptedException{
// 新建事件循环组
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
// 只有一个事件循环组,所以只要一个hander即可
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
// 通道连接激活时即可发送数据到服务器
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("通道建立,客户端可写,请输入:");
Scanner scanner = new Scanner(System.in, "utf8");
String msg = scanner.nextLine();
ctx.channel().writeAndFlush(msg);
}
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 基于行的编码解码器
pipeline.addLast(new LineBasedFrameDecoder(1024));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
// 自己的业务处理代码,发送数据到服务端
pipeline.addLast(new SimpleChannelInboundHandler() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
System.out.println("客户端收到服务端的消息,内容为:" + s);
}
});
}
});
bootstrap.connect("localhost", 8899).sync().channel().closeFuture().sync();
}
}