Socekt(TCP)服务端传输http协议之NIO非阻塞
作为TCP服务端有三种写法,一是通过阻塞写法直接实例化socket;二是通过NIO非阻塞法实现;三是通过netty来实现(一般用于高并发这快)。我这边使用的是使用NIO非阻塞法实现。
需求:客户端通过socket发送http协议来传输数据,同时也会出现客户端分批发送数据(如以http协议Post形式发送数据,第一次发送请求头,第二次发送实体)如客户端这样发送数据。
解决:可以把发送过来的http协议作为一次平常发送过来的字符串进行处理。
TCP服务端代码:
public class NIOServer {
private InetAddress addr; //地址
private int port; //端口
private Selector selector;
private static int BUFF_SIZE = 1024*1024; //用于缓存客户端发送的数据,同时防止客户端分批次发送数据
// 通道的保存 key:IP value:通道
private static ConcurrentHashMap<String,SelectionKey> keyPool=new ConcurrentHashMap<>();
public NIOServer() {
}
/**
*
* @param addr 可以传null
* @param port 端口
* @throws IOException
*/
public NIOServer(InetAddress addr, int port) {
try{
this.addr = addr;
this.port = port;
startServer();
} catch (Exception e){
log.info("启动失败");
}
}
private void startServer() throws IOException {
// 获得selector及通道(socketChannel)
this.selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
// 绑定地址及端口
InetSocketAddress listenAddr = new InetSocketAddress(this.addr, this.port);
serverChannel.socket().bind(listenAddr);
serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);
log.info("NIOServer运行中...");
while (true) {
log.info("服务器等待新的连接和selector选择…");
this.selector.select();
// 选择key工作
Iterator keys = this.selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = (SelectionKey) keys.next();
// 防止出现重复的key,处理完需及时移除
keys.remove();
//无效直接跳过
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) { //是否可接收
this.accept(key); //刚开始连接
} else if (key.isReadable()) {
this.read(key); //是否可写
} else if (key.isWritable()) {
this.write(key); //是否可读
} else if (key.isConnectable()) { //是否可连接
this.connect(key); //表示那些ip连接
}
}
}
}
private void connect(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
if (channel.finishConnect()) {
// 成功
log.info("成功连接了");
} else {
keyPool.remove(channel.socket().getRemoteSocketAddress()+"");
// 失败
log.info("失败连接");
}
}
private void accept(SelectionKey key) throws IOException {
// 通过选择器键获取服务器套接字通道,通过 accept() 方法获取套接字通道连接
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel channel = serverChannel.accept();
// 设置套接字通道为非阻塞模式
channel.configureBlocking(false);
// 为套接字通道注册选择器,该选择器为服务器套接字通道的选择器,即选择到该 SocketChannel 的选择器
// 设置选择器关心请求为读操作,设置数据读取的缓冲器容量为处理器初始化时候的缓冲器容量
channel.register(this.selector, SelectionKey.OP_READ);
//获取客户端的ip与端口
SocketAddress remoteAddr = channel.socket().getRemoteSocketAddress();
keyPool.put(remoteAddr+"",key);
log.info("连接到: "+remoteAddr);
}
private void read(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(BUFF_SIZE);//开批一个空间存储发送过来的数据,一般用于存储分批发送的数据
int numRead = channel.read(buffer);
if (numRead == -1) {
log.info("关闭客户端连接: "+channel.socket().getRemoteSocketAddress());
keyPool.remove(channel.socket().getRemoteSocketAddress()+"");
channel.close();
return;
}
String msg = new String(buffer.array()).trim();
log.info("客户端发送的数据: "+msg);
//解析数据后返回数据
String reMsg= ""; //针对客户端发送的数据进行解析同时反回要发送的数据
log.info("要返回的数据: "+reMsg);
// 回复客户端
channel.write(ByteBuffer.wrap(reMsg.getBytes()));
}
//发送的数据
private void write(SelectionKey key) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(BUFF_SIZE);
byteBuffer.flip();
SocketChannel clientChannel = (SocketChannel) key.channel();
while (byteBuffer.hasRemaining()) {
clientChannel.write(byteBuffer);
}
byteBuffer.compact();
}
}
至此解决完成。