一、服務端
1、服務端處理器
首先我們下一個服務端部分的處理器
/**
* 類說明:
* 編寫NIO的服務端部分的處理器,主要是為了啟動服務,收到請求的報文,然后返回返回的報文
*/
public class NioServerHandle implements Runnable{
private Selector selector;//選擇器
private ServerSocketChannel serverChannel;//通道,用來溝通系統(tǒng)和程序之間的交互事件
private volatile boolean started; //端口監(jiān)聽是否啟動狀態(tài)
/**
* 構造方法
* 初始化選擇器 selector
* 初始化渠道 serverChannel
* 設置渠道的阻塞方式 ,true為阻塞式編程,false為非阻塞式
* 這里主要是基本的API使用方式
* @param port 服務開發(fā)的端口號
*/
public NioServerHandle(int port) {
try {
selector = Selector.open();
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
started = true;
serverChannel.socket().bind( new InetSocketAddress( port ) );
serverChannel.register( selector,SelectionKey.OP_CONNECT );
started = true;
System.out.println("服務端口已經(jīng)啟動");
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
/**
* 停止服務
*/
public void stop(){
started = false;
}
@Override
public void run() {
while (started){//這里是判斷服務是否啟動著,如果為false就關閉接口
try {
selector.select();//選擇器選擇中,這是哥阻塞方法,除非有事件觸發(fā),否則一般都是阻塞了
Set<SelectionKey> keys = selector.selectedKeys();//獲取到所有觸發(fā)的事件
Iterator<SelectionKey> it = keys.iterator();
SelectionKey key = null;
while (it.hasNext()){//遍歷所有的事件
key = it.next();
it.remove();
handleInput(key);//依次處理事件
}
} catch (IOException e) {
e.printStackTrace();
}
}
if(selector!=null){
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 處理事件的方法
* 如果事件為可讀事件,就讀取緩存中的所有東西,經(jīng)過處理,然后返回。
* 這個方法有個欠缺,如果請求的報文長度大于1024字節(jié)的時候,就會出現(xiàn)報文不完全的情況。
* @param key 操作類型
* @throws IOException
*/
private void handleInput(SelectionKey key) throws IOException {
if(key.isValid()){//先判斷事件是不是有效事件
if(key.isAcceptable()){
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
System.out.println("=======建立連接===");
sc.configureBlocking(false);
sc.register(selector,SelectionKey.OP_READ);
}
//處理新接入的請求信息
if(key.isReadable()){
/**isReadbale()官網(wǎng)的解釋,判斷這個key的同時是否完成
* Tests whether this key's channel is ready for reading.
*/
System.out.println("=====socket channel 數(shù)據(jù)準備完成,可以去讀 讀取===");
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(buffer);//從sc中讀取1024的數(shù)據(jù)放到buffer里邊
if(readBytes>0){
buffer.flip();//讀寫轉換
byte[] bytes = new byte[buffer.remaining()];////buffer.remaining :buffer可以讀取的數(shù)據(jù)的長度
/**
* buffer.get(bytes)方法的官方解釋,將buffer里邊的byte傳入到參數(shù)的數(shù)組中
* This method transfers bytes from this buffer into the given
* destination array.
*/
buffer.get(bytes);
String message = new String(bytes,"utf-8");
System.out.println("服務器收到消息,"+message);
String result = response(message);//這個是調(diào)用業(yè)務邏輯的方法
doWrite(sc,result);//將返回的報文寫入到channer中
}
else if(readBytes<0){
key.cancel();
sc.close();
}
}
if (key.isWritable()){
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer attr = (ByteBuffer) key.attachment();
if(attr.hasRemaining()){
int count = sc.write( attr );
System.out.println("write "+ count +
"byte and has R"+attr.hasRemaining());
}else {
sc.register( selector,SelectionKey.OP_READ );
}
}
}
}
/**
* 開始處理返回報文,傳入的參數(shù)是通道和返回報文,將返回報文寫入到渠道中去
* @param sc 傳入的渠道信息
* @param response 傳入的返回報文
* @throws IOException
*/
private void doWrite(SocketChannel sc, String response) throws IOException {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
// sc.write(writeBuffer);
serverChannel.register( selector,SelectionKey.OP_READ|SelectionKey.OP_WRITE,writeBuffer );
}
/**
* 業(yè)務邏輯處理
* @param msg 傳入的參數(shù)是請求報文。
* @return
*/
public static String response(String msg){
return "Hello,"+msg+",Now is "+new java.util.Date(
System.currentTimeMillis()).toString() ;
}
}
2、服務端啟用函數(shù)
private static NioServerHandle nioServerHandle;
public static void main(String[] args) {
start();
}
private static void start(){
if(nioServerHandle!=null){
nioServerHandle.stop();//如果服務已經(jīng)起起來了就關了重新啟動一下
}
nioServerHandle = new NioServerHandle(12000);//表示監(jiān)聽的端口號
new Thread(nioServerHandle,"server").start();//開始服務,run()方法開始調(diào)用
}
二、客戶端代碼的編寫
1、客戶端的框架部分
/**
* 類說明:客戶端調(diào)用的處理器
*/
public class NioClientHandle implements Runnable {
/**
* 需要定義要鏈接哪個服務端,要連接哪個端口,是否要啟動這個連接,渠道是多少
*/
private String host;
private int port;
private volatile boolean started;
private Selector selector;
private SocketChannel socketChannel;
/**
* 構造函數(shù),一個服務端用一個實例
* @param host ip
* @param port 端口
*/
public NioClientHandle(String host,int port){
this.host = host;
this.port = port;
try {
//創(chuàng)建選擇器
this.selector = Selector.open();
//打開監(jiān)聽通道
socketChannel = SocketChannel.open();
//設置是否為阻塞模式
socketChannel.configureBlocking(false);
started = true;
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
}
public void stop(){
started = false;
}
@Override
public void run() {
try {
// 先連接
doConnect();
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
while (started){
try {
selector.select();//阻塞方法,至少一個注冊事件發(fā)生的時候,就會繼續(xù)
Set<SelectionKey> keys = selector.selectedKeys();//獲取到事件
Iterator<SelectionKey> it = keys.iterator();
SelectionKey key =null;
//遍歷所有的
while(it.hasNext()){
key = it.next();
it.remove();//現(xiàn)將處理過的selectionKey從選定的集合中刪除,如果沒有刪除,仍然在事件集合中以一個激活的建出現(xiàn),這會是我們再次處理
try {
handleInput(key);//具體處理事件的方法
} catch (Exception e) {
if(key!=null){
key.cancel();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 處理的邏輯
* @param key 傳入的操作類型
* @throws Exception
*/
private void handleInput(SelectionKey key) throws Exception {
if(key.isValid()){
SocketChannel sc = (SocketChannel) key.channel();//
if (key.isConnectable()){//如果是連接事件的話
if(sc.finishConnect()){//確認鏈接已經(jīng)建立
socketChannel.register(selector, SelectionKey.OP_READ);//連接建立就去訂閱閱讀事件,然后有返回的時候,就會進入下邊的readable事件處理里邊
}else {
System.exit(-1);
}
}
if(key.isReadable()){
ByteBuffer buffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(buffer);
if(readBytes>0){
//讀到的數(shù)據(jù),進行具體的業(yè)務處理//這里應該是返回的報文
}else if(readBytes<0){
key.cancel();
sc.close();
}
}
}
}
/**
* 進行連接的具體操作
* @throws IOException
*/
private void doConnect() throws IOException {
if(socketChannel.connect(new InetSocketAddress(host,port))){}//如果連接的時候已經(jīng)連接上了就直接處理
else{
socketChannel.register(selector,SelectionKey.OP_CONNECT);//如果沒有連接上就訂閱事件,進入到Connectable里邊
}
}
/**
* 進行連接的具體操作
* @throws IOException
*/
private void doConnect() throws IOException {
if(socketChannel.connect(new InetSocketAddress(host,port))){}//如果連接的時候已經(jīng)連接上了就直接處理
else{
socketChannel.register(selector,SelectionKey.OP_CONNECT);//如果沒有連接上就訂閱事件,進入到Connectable里邊
}
}
//發(fā)送消息
public void sendMsg(String msg){
//socketChannel,一開始初始化的
doWrite(socketChannel,msg);
}
/**
* 用來發(fā)送信息
* @param sc 渠道
* @param msg 寫入的信息
* @return
*/
private void doWrite(SocketChannel sc,String msg){
byte[] bytes = msg.getBytes();
//定義buffer
ByteBuffer byteBuffer = ByteBuffer.allocate( bytes.length );
//將bytes放入buffer
byteBuffer.put( bytes );
//改變buffer的讀寫狀態(tài)
byteBuffer.flip();
//緩存進入渠道
try {
sc.write( byteBuffer );
} catch (IOException e) {
e.printStackTrace();
}
}
}
2、啟用框架
public class NioClient {
private static NioClientHandle nioClientHandle;
public static void main(String[] args) {
start();
NioClient.sendMsg("1123123");
}
private static void start() {
if(nioClientHandle!=null){
nioClientHandle.stop();
nioClientHandle = new NioClientHandle("",1);
new Thread(nioClientHandle,"server").start();
}
}
private static boolean sendMsg(String msg) {
nioClientHandle.sendMsg(msg);
return false;
}
}