基于Java使用NIOSocket(java.nio.*)通信的举例:
首先创建NIOSocket的服务端;其次创建NIOSocket的客户端。
通信过程如下:
客户端(C)向服务端(S)发送任意数据(包括用户直接从控制台输入数据,使用Scanner),服务端接受到来自客户端的数据并展示,同时客户端发过来的数据原封不动的再发给客户端;客户端接受来自服务端的数据并展示。
说明:通信过程如上。下面看代码如何实现:
服务端:
package socket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
public class NIOService {
public String IP = "127.0.0.1";// 10.50.200.120
public static final int PORT = 4444;
private static final int SIZE = 256;
// 对于以字符方式读取和处理的数据必须要进行字符集编码和解码
String encoding = System.getProperty("file.encoding");
// 加载字节编码集
Charset charse = Charset.forName(encoding);
public NIOService() throws IOException {
// NIO的通道channel中内容读取到字节缓冲区ByteBuffer时是字节方式存储的,
// 分配两个字节大小的字节缓冲区
ByteBuffer buffer = ByteBuffer.allocate(SIZE);
SocketChannel ch = null;
Selector selector = null;
ServerSocketChannel serverChannel = null;
try {
// 打开通道选择器
selector = Selector.open();
// 打开服务端的套接字通道
serverChannel = ServerSocketChannel.open();
// 将服务端套接字通道连接方式调整为非阻塞模式
serverChannel.configureBlocking(false);
// serverChannel.socket().setReuseAddress(true);
// 将服务端套接字通道绑定到本机服务端端口
serverChannel.socket().bind(new InetSocketAddress(IP, PORT));
// 将服务端套接字通道OP_ACCEP事件注册到通道选择器上
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server on port:" + PORT);
while (true) {
// 通道选择器开始轮询通道事件
selector.select();
Iterator
it = selector.selectedKeys().iterator();
while (it.hasNext()) {
// 获取通道选择器事件键
SelectionKey skey = (SelectionKey) it.next();
it.remove();
// 服务端套接字通道发送客户端连接事件,客户端套接字通道尚未连接
if (skey.isAcceptable()) {
// 获取服务端套接字通道上连接的客户端套接字通道
ch = serverChannel.accept();
System.out.println("Accepted connection from:" + ch.socket());
// 将客户端套接字通过连接模式调整为非阻塞模式
ch.configureBlocking(false);
// 将客户端套接字通道OP_READ事件注册到通道选择器上
ch.register(selector, SelectionKey.OP_READ);
}
// 如果sk对应的Channel有数据需要读取
if (skey.isReadable()) {
// 获取该SelectionKey对银行的Channel,该Channel中有刻度的数据
SocketChannel sc = (SocketChannel) skey.channel();
String content = "";
// 开始读取数据
try {
content = receiverFromClient(sc,buffer);
// 将sk对应的Channel设置成准备下一次读取
skey.interestOps(SelectionKey.OP_READ);
} catch (IOException e) {// 如果捕获到该sk对银行的Channel出现了异常,表明
// Channel对应的Client出现了问题,所以从Selector中取消
// 从Selector中删除指定的SelectionKey
skey.cancel();
if (skey.channel() != null) {
skey.channel().close();
}
}
// 如果content的长度大于0,则处理信息返回给客户端
if (content.length() > 0) {
System.out.println("接受客户端数据:" + content);
// 处理信息返回给客户端
sendToClient(selector,content);
}
//ch.write((ByteBuffer)buffer.rewind());
//buffer.clear();
}
if(skey.isWritable()){
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ch != null)
ch.close();
serverChannel.close();
selector.close();
}
}
/**
* 向客户端发送数据
* @param selector
* @param content
* @throws IOException
*/
public void sendToClient(Selector selector,String content) throws IOException{
// 遍历selector里注册的所有SelectionKey
for (SelectionKey key1 : selector.keys()) {
// 获取该key对应的Channel
Channel targerChannel = key1.channel();
// 如果该Channel是SocketChannel对象
if (targerChannel instanceof SocketChannel) {
// 将读取到的内容写入该Channel中
SocketChannel dest = (SocketChannel) targerChannel;
sendToClient(dest,content);
}
}
}
/**
* 向指定频道发送数据
* @param channel
* @param data
* @throws IOException
*/
public void sendToClient(SocketChannel channel, String data) throws IOException {
channel.write(charse.encode(data));
//channel.socket().shutdownOutput();
}
/**
* 接受来自客户端数据
* @param channel
* @param buffer
* @return
* @throws Exception
*/
private String receiverFromClient(SocketChannel channel,ByteBuffer buffer) throws Exception {
String content = "";
//* 取客户端发送的数据两个方法任选其一即可
// 开始读取数据
// 法一
channel.read(buffer);
CharBuffer cb = charse.decode((ByteBuffer) buffer.flip());
content = cb.toString();
// 法二
/*
while (sc.read(buffer) > 0) {
buffer.flip();
content += charse.decode(buffer);
}//*/
buffer.clear();
return content;
}
public static void main(String[] args) {
try {
new NIOService();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端:
package socket;
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.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;
/**
*
* NIOClient
*
* @author 王俊伟 wjw.happy.love@163.com
* @date 2016年7月21日 下午5:26:25
*/
public class NIOClient {
private static final int SIZE = 1024;
private static NIOClient instance = new NIOClient();
public String IP = "127.0.0.1";// 10.50.200.120
public int CLIENT_PORT = 4444;// 4444 9666
private SocketChannel channel;
private Selector selector = null;
String encoding = System.getProperty("file.encoding");
Charset charset = Charset.forName(encoding);
private NIOClient() {
}
public static NIOClient getInstance() {
return instance;
}
public void send(String content) throws IOException {
selector = Selector.open();
channel = SocketChannel.open();
// channel = SocketChannel.open(new InetSocketAddress(IP,CLIENT_PORT));
InetSocketAddress remote = new InetSocketAddress(IP, CLIENT_PORT);
channel.connect(remote);
// 设置该sc以非阻塞的方式工作
channel.configureBlocking(false);
// 将SocketChannel对象注册到指定的Selector
// SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT
channel.register(selector, SelectionKey.OP_READ);//这里注册的是read读,即从服务端读数据过来
// 启动读取服务器数据端的线程
new ClientThread().start();
channel.write(charset.encode(content));
// 创建键盘输入流
Scanner scan = new Scanner(System.in);//这里向服务端发送数据,同时启动了一个键盘监听器
while (scan.hasNextLine()) {
System.out.println("输入数据:\n");
// 读取键盘的输入
String line = scan.nextLine();
// 将键盘的内容输出到SocketChanenel中
channel.write(charset.encode(line));
}
scan.close();
}
/**
* 从服务端读入数据的线程
*
* @author 王俊伟 wjw.happy.love@163.com
* @date 2016年10月20日 下午9:59:11
*/
private class ClientThread extends Thread {
@Override
public void run() {
try {
while (selector.select() > 0) {
// 遍历每个有可能的IO操作的Channel对银行的SelectionKey
for (SelectionKey sk : selector.selectedKeys()) {
// 删除正在处理的SelectionKey
selector.selectedKeys().remove(sk);
// 如果该SelectionKey对应的Channel中有可读的数据
if (sk.isReadable()) {
// 使用NIO读取Channel中的数据
SocketChannel sc = (SocketChannel) sk.channel();
String content = "";
ByteBuffer bff = ByteBuffer.allocate(SIZE);
while (sc.read(bff) > 0) {
sc.read(bff);
bff.flip();
content += charset.decode(bff);
}
// 打印读取的内容
System.out.println("服务端返回数据:" + content);
// 处理下一次读
sk.interestOps(SelectionKey.OP_READ);
}
}
}
} catch (IOException io) {
io.printStackTrace();
}
}
}
/**
* TCP 处理 线程
*/
class TCPClientReadThread implements Runnable {
private Selector selector;
public TCPClientReadThread(Selector selector) {
this.selector = selector;
new Thread(this).start();
}
@Override
public void run() {
try {
channel.configureBlocking(false);
// selector.select(3000);
channel.register(selector, SelectionKey.OP_READ);
while (true) {
if (selector.select(1000) > 0) {
// 遍历每个有可用IO操作Channel对应的SelectionKey
for (SelectionKey sk : selector.selectedKeys()) {
// 如果该SelectionKey对应的Channel中有可读的数据
if (sk.isReadable()) {
// 使用NIO读取Channel中的数据
SocketChannel sc = (SocketChannel) sk.channel();
// 将字节转化为为UTF-8的字符串
receiveData(sc);
// 为下一次读取作准备
sk.interestOps(SelectionKey.OP_READ);
} else if (sk.isWritable()) {
// 取消对OP_WRITE事件的注册
ByteBuffer buffer = ByteBuffer.allocate(1024);
sk.interestOps(sk.interestOps() & (~SelectionKey.OP_WRITE));
SocketChannel sc = (SocketChannel) sk.channel();
// 此步为阻塞操作,直到写入操作系统发送缓冲区或者网络IO出现异常
// 返回的为成功写入的字节数,若缓冲区已满,返回0
int writeenedSize = sc.write(buffer);
// 若未写入,继续注册感兴趣的OP_WRITE事件
if (writeenedSize == 0) {
sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE);
}
} else if (sk.isConnectable()) {
SocketChannel sc = (SocketChannel) sk.channel();
sc.configureBlocking(false);
// 注册感兴趣的IO事件,通常不直接注册写事件,在发送缓冲区未满的情况下
// 一直是可写的,所以如果注册了写事件,而又不写数据,则很容易造成CPU消耗100%
// SelectionKey sKey = sc.register(selector,
// SelectionKey.OP_READ);
// 完成连接的建立
sc.finishConnect();
}
// 删除正在处理的SelectionKey
selector.selectedKeys().remove(sk);
}
}
if (selector.select(1000) <= 0) {
Thread.sleep(1000);
continue;
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
/**
* 客户端发送数据
*
* @param channel
* @param bytes
* @throws Exception
*/
protected void sendData(SocketChannel channel, byte[] bytes) throws Exception {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
channel.write(buffer);
//channel.socket().shutdownOutput();
}
protected void sendData(SocketChannel channel, String data) throws Exception {
this.sendData(channel, data.getBytes());
}
/**
* 接受服务端的数据
*
* @param channel
* @return
* @throws Exception
*/
protected void receiveData(SocketChannel channel) throws Exception {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
int count = 0;
while ((count = channel.read(buffer)) != -1) {
if (count == 0) {
Thread.sleep(100); // 等等一下
continue;
}
// 转到最开始
buffer.flip();
while (buffer.remaining() > 0) {
System.out.print((char) buffer.get());
}
buffer.clear();
}
}
public static void main(String[] args) {
try {
NIOClient nio = new NIOClient();
nio.send("test\n");//向服务端发送数据
//nio.send("metrics:memory: swap: cpu: network i/o: disks i/o: tcp:\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
如上两个类代码注释很全了。
运行:
首先运行服务端,然后执行客户端,返回数据如下:
服务端:
Server on port:4444
Accepted connection from:Socket[addr=/127.0.0.1,port=58153,localport=4444]
接受客户端数据:test
接受客户端数据:你好啊!
客户端:
服务端返回数据:test
输入数据:
你好啊!
输入数据:
服务端返回数据:你好啊!