本文仅为开发时的经验记录,以备记录相关开发的备份。
1、首先是将创建一个NIOServer的Spring IOC中的对象,如代码所示:
@Configuration
public class SocketConfig {
@Value("${socket.server.port}")
private int port;
@Autowired
private ExecutorService executorService;
@Bean("nioServer")
NIOServer nioServer() {
NIOServer socketServer = new NIOServer(port, executorService);
Thread thread = new Thread(socketServer);
thread.start();
return socketServer;
}
}
2、NIOServer类的代码,如下:
package com.xxx.crm.esb.esbfunction.socket;
import com.xxx.crm.common.context.SpringContext;
import com.xxx.crm.esb.dispatch.EsbRequestDispatcher;
import com.xxx.crm.esb.esbfunction.model.qo.request.basic.BasicEsbReq;
import com.xxx.crm.esb.init.EsbErrorCode;
import com.xxx.crm.esb.util.ResponseToXml;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
public class NIOServer implements Runnable {
private Selector selector;
// 16kb缓冲区大小
private ByteBuffer readBuffer = ByteBuffer.allocate(1024 * 16);
// 16kb缓冲区大小
private ByteBuffer writeBuffer = ByteBuffer.allocate(1024 * 16);
// 获取针对esb请求的分发器,也可以获取具体的业务对象userServiceImpl等
private EsbRequestDispatcher esbRequestDispatcher = SpringContext
.getContext().getBean("esbRequestDispatcherImpl", EsbRequestDispatcher.class);
public NIOServer(int port, ExecutorService threadPool) {
try {
// 打开多路复用器
this.selector = Selector.open();
// 打开服务器通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置服务器通道为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 绑定端口ip地址
serverSocketChannel.bind(new InetSocketAddress(port));
// 把服务器通道注册到多路复用器上,并且监听阻塞事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 输出启动文字
System.out.println("NIO server start...");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true) {
try {
// 多路复用器开始监听
this.selector.select();
// 返回多路复用器已经选择的结果集
Iterator<SelectionKey> selectionKeyIterator = this.selector
.selectedKeys().iterator();
// 遍历
while(selectionKeyIterator.hasNext()) {
// 获取一个选择的元素
SelectionKey key = selectionKeyIterator.next();
// 选择之后从iterator容器中移除
selectionKeyIterator.remove();
if(key.isValid()) {
// 阻塞状态
if(key.isAcceptable()) {
this.accept(key);
}
// 可读装填
if(key.isReadable()) {
this.read(key);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 功能描述: serverSocketChannel读取数据,可以在此写好反馈
* @param key
*/
private void read(SelectionKey key) {
try {
// 清空缓冲区的旧数据
this.readBuffer.clear();
// 获取之前注册的socket通道对象,这个对象就是与客户端通信的管道
SocketChannel channel = (SocketChannel)key.channel();
// 读取数据,非阻塞好处在于,触发事件的时候,数据就都准备好了
int count = channel.read(readBuffer);
// 若没有数据,关闭通道,移除注册的key
if(count == -1) {
key.channel().close();
key.cancel();
return;
}
// 若有数据则进行读取,需要先切换到读取的模式
this.readBuffer.flip();
// 创建byte数组,接受缓冲区的数据
byte[] bytes = new byte[this.readBuffer.remaining()];
// 接收数据
this.readBuffer.get(bytes);
// 调取具体的业务逻辑代码内容
String resultXml = this.processSocket(new String(bytes));
// 读取完成处理之后返回的数据
afterRead(channel, resultXml);
} catch (Exception e) {
}
}
/**
* 功能描述:读取完之后返回数据
* @param channel
* @param resultXml
* @throws IOException
*/
private void afterRead(SocketChannel channel, String resultXml) throws IOException {
// 数据方法缓冲区
writeBuffer.put(resultXml.getBytes());
// 复位buffer
writeBuffer.flip();
// 写出数据
channel.write(writeBuffer);
// 清空数据
writeBuffer.clear();
}
/**
* 功能描述:实现监听状态的处理方法,一般是针对serverSocketChannel对象
* 拿出监听状态的服务器端channel,该状态的channel已经监听到channel,准备好将
* 这个监听的channel转换为等待读取的状态,注册到多路复用器上。
* @param key
*/
private void accept(SelectionKey key) {
try {
// 获取服务器通道
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// 执行阻塞方法,获得与客户端的通道
SocketChannel socketChannel = serverSocketChannel.accept();
// 设置阻塞模式-非阻塞
socketChannel.configureBlocking(false);
// 注册到多路复用器上,并设置读取标识
socketChannel.register(selector, SelectionKey.OP_READ);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 具体业务代码,
* @param requestStr 客户端请求参数
* @return 业务处理后的返回数据
*/
private String processSocket(String requestStr) {
String outputStr;
BasicEsbReq basicEsbReq = new BasicEsbReq();
try {
outputStr = esbRequestDispatcher.dataBeanFix(requestStr, basicEsbReq);
} catch (Exception e) {
basicEsbReq.setReturnShortCode(EsbErrorCode.SYS_ERROR);
e.printStackTrace();
outputStr = ResponseToXml.errorResponseXml(e.getMessage(), basicEsbReq);
}
return outputStr;
}
}
3、客户端代码简写:
public static void main(String[] args) throws UnknownHostException, IOException {
//设置Url和端口, 127.0.0.1代表本机
String url = "127.0.0.1";
int port = 6666;
//与服务端建立连接
Socket socket = new Socket(url,port);
System.out.println("已经连接到服务端");
//建立连接后输出流到服务端
OutputStream outputStream = socket.getOutputStream();
String message = "我是Shares,666";
outputStream.write(message.getBytes("UTF-8"));
InputStream in = socket.getInputStream();
int len = 0;
byte[] bytes = new byte[1024];
StringBuilder sb = new StringBuilder();
while((len=in.read(bytes)) != -1) {
sb.append(new String(bytes, 1, len, "UTF-8"));
if(len < 1024) {
break;
}
}
System.out.println("Server says "+ sb.toString());
outputStream.close();
in.close();
socket.close();
}