最近一直在忙着JAVA NIO的知识,花了一下午的时间,总算写出了一个可以运行的程序,废话少说,上代码!
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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.Set;
public class NIOServer {
/*标识数字*/
private int flag = 0;
/*缓冲区大小*/
private int BLOCK = 4096;
/*接受数据缓冲区*/
private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
/*发送数据缓冲区*/
private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
private Selector selector;
public NIOServer(int port) throws IOException {
// 打开服务器套接字通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 服务器配置为非阻塞
serverSocketChannel.configureBlocking(false);
// 检索与此通道关联的服务器套接字
ServerSocket serverSocket = serverSocketChannel.socket();
// 进行服务的绑定
serverSocket.bind(new InetSocketAddress(port));
// 通过open()方法找到Selector
selector = Selector.open();
// 注册到selector,等待连接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server Start----8888:");
}
// 监听
private void listen() throws IOException {
while (true) {
// 选择一组键,并且相应的通道已经打开
selector.select();
// 返回此选择器的已选择键集。
Set selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleKey(selectionKey);
}
}
}
// 处理请求
private void handleKey(SelectionKey selectionKey) throws IOException {
// 接受请求
ServerSocketChannel server = null;
SocketChannel client = null;
String receiveText;
String sendText;
int count=0;
// 测试此键的通道是否已准备好接受新的套接字连接。
if (selectionKey.isAcceptable()) {
// 返回为之创建此键的通道。
server = (ServerSocketChannel) selectionKey.channel();
// 接受到此通道套接字的连接。
// 此方法返回的套接字通道(如果有)将处于阻塞模式。
client = server.accept();
// 配置为非阻塞
client.configureBlocking(false);
// 注册到selector,等待连接
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 返回为之创建此键的通道。
client = (SocketChannel) selectionKey.channel();
//将缓冲区清空以备下次读取
receivebuffer.clear();
//读取服务器发送来的数据到缓冲区中
count = client.read(receivebuffer);
if (count > 0) {
receiveText = new String( receivebuffer.array(),0,count);
System.out.println("服务器端接受客户端数据--:"+receiveText);
client.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
//将缓冲区清空以备下次写入
sendbuffer.clear();
// 返回为之创建此键的通道。
client = (SocketChannel) selectionKey.channel();
sendText="message from server--" + flag++;
//向缓冲区中输入数据
sendbuffer.put(sendText.getBytes());
//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip();
//输出到通道
client.write(sendbuffer);
System.out.println("服务器端向客户端发送数据--:"+sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
int port = 8888;
NIOServer server = new NIOServer(port);
server.listen();
}
}
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.util.Iterator;
import java.util.Set;
public class NIOClient {
/*标识数字*/
private static int flag = 0;
/*缓冲区大小*/
private static int BLOCK = 4096;
/*接受数据缓冲区*/
private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
/*发送数据缓冲区*/
private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
/*服务器端地址*/
private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress(
"localhost", 1111);
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
// 打开socket通道
SocketChannel socketChannel = SocketChannel.open();
// 设置为非阻塞方式
socketChannel.configureBlocking(false);
// 打开选择器
Selector selector = Selector.open();
// 注册连接服务端socket动作
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 连接
socketChannel.connect(SERVER_ADDRESS);
// 分配缓冲区大小内存
Set selectionKeys;
Iterator iterator;
SelectionKey selectionKey;
SocketChannel client;
String receiveText;
String sendText;
int count=0;
while (true) {
//选择一组键,其相应的通道已为 I/O 操作准备就绪。
//此方法执行处于阻塞模式的选择操作。
selector.select();
//返回此选择器的已选择键集。
selectionKeys = selector.selectedKeys();
//System.out.println(selectionKeys.size());
iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
selectionKey = iterator.next();
if (selectionKey.isConnectable()) {
System.out.println("client connect");
client = (SocketChannel) selectionKey.channel();
// 判断此通道上是否正在进行连接操作。
// 完成套接字通道的连接过程。
if (client.isConnectionPending()) {
client.finishConnect();
System.out.println("完成连接!");
sendbuffer.clear();
sendbuffer.put("Hello,Server".getBytes());
sendbuffer.flip();
client.write(sendbuffer);
}
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
client = (SocketChannel) selectionKey.channel();
//将缓冲区清空以备下次读取
receivebuffer.clear();
//读取服务器发送来的数据到缓冲区中
count=client.read(receivebuffer);
if(count>0){
receiveText = new String( receivebuffer.array(),0,count);
System.out.println("客户端接受服务器端数据--:"+receiveText);
client.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
sendbuffer.clear();
client = (SocketChannel) selectionKey.channel();
sendText = "message from client--" + (flag++);
sendbuffer.put(sendText.getBytes());
//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip();
client.write(sendbuffer);
System.out.println("客户端向服务器端发送数据--:"+sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
selectionKeys.clear();
}
}
}
个人感觉,JAVA NIO的操作时麻烦了不少,但是无疑这样做效率会得到很大的提升。
9
顶
3
踩
分享到:
2010-09-18 03:32
浏览 45247
评论
22 楼
albert0707
2018-06-29
非常感谢!
21 楼
清风送月
2015-10-19
楼主:为什么我写的时候使用iterator.remove(); 会报java.lang.UnsupportedOperationExceptionselectionKeys = selector.selectedKeys();
//System.out.println(selectionKeys.size());
iterator = selectionKeys.iterator();
20 楼
zaiyueming
2015-04-02
sj4268778 写道
都跑的通么??端口号都不一致。。
hekuilove 写道
端口不一样你是怎么跑起来的
这种小事何必纠结,自己不会改?
19 楼
hekuilove
2015-03-16
端口不一样你是怎么跑起来的
18 楼
sj4268778
2015-03-02
都跑的通么??端口号都不一致。。
17 楼
kennykinte
2014-06-18
xunke515 写道
-------------
NIOServer 类第101行有些问题
如果继续注册读事件,服务端会持续得进行循环.
这样客户端接受不到服务端返回得数据,会一直保持连接.
---------
应该将NIOServer 类第101行替换为client.close() ;
---------
欢迎拍砖.
兄台说的不太正确哦...
主贴代码就是让C/S两端不断循环读取,发送数据的, 只要客户端向服务端发送数据了,服务端就会回一条消息给客户端,如此反复.
如果将NIOServer 类第101行替换为client.close() ;
那么客户端就被关闭了.
16 楼
kennykinte
2014-06-18
softkf 写道
我也有点纳闷,如果把如下代码
else if (selectionKey.isWritable()) {
//将缓冲区清空以备下次写入
sendbuffer.clear();
// 返回为之创建此键的通道。
client = (SocketChannel) selectionKey.channel();
sendText="message from server--" + flag++;
//向缓冲区中输入数据
sendbuffer.put(sendText.getBytes());
//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip();
//输出到通道
client.write(sendbuffer);
System.out.println("服务器端向客户端发送数据--:"+sendText);
client.register(selector, SelectionKey.OP_READ);
}
中的红色部分去掉,就会出现服务器一直往客户端打印东西? 这是为何? 兄台弄清楚了么?
看我上一条回复, register方法会覆盖前一次register方法的效果. 所以如果你这边不加这条红色语句, 那么在这个channel里的事件就一直是OP_WRITE了,所以每次select()都会执行selectionKey.isWritable()这段的逻辑, 就一直给客户端发送数据了.
15 楼
kennykinte
2014-06-18
zk1878 写道
对nio不是很了解,这里有个疑问,在client那的代码
为什么当selectionKey.isConnectable()时,只注册了
SelectionKey.OP_READ,而不注册SelectionKey.OP_WRITE;
还有当selectionKey.isWritable()时,为什么每次都要注册
SelectionKey.OP_READ,我觉得应该是在selectionKey.isConnectable()
时同时注册OP_READ,OP_WRITE吧
对于某个指定的socketchannel,调用register方法会覆盖前一次调用register的效果,而你想在selectionKey.isConnectable()这里同时注册OP_READ和OP_WRITE事件的话, 可以这么写client.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);
但是主贴里selectKey循环遍历的时候, 又用的是if-else结构,所以你注册2个事件,也不会都执行到.
但是按照主贴里的写法,在isConnectable()里注册OP_READ,程序下次就会进入isReadable(),在isReadable()里又注册OP_WRITE, 这样就把OP_READ给覆盖掉了,程序下次就进入isWritable(),在isWritable()又注册了OP_READ,下一次就进入isReadable()....如此反复循环, 所以主贴代码是对的.
当然这种结构好不好, 我就不评论了.
14 楼
kennykinte
2014-06-17
josico 写道
问一下 为什么在遍历迭代器的时候 要做一次remove操作?
我觉得一点意义都没有啊
http://www.molotang.com/articles/906.html
这个帖子里有解释为什么要remove
13 楼
josico
2014-06-17
问一下 为什么在遍历迭代器的时候 要做一次remove操作?
我觉得一点意义都没有啊
12 楼
xunke515
2014-03-23
softkf 写道
zk1878 写道
对nio不是很了解,这里有个疑问,在client那的代码
为什么当selectionKey.isConnectable()时,只注册了
SelectionKey.OP_READ,而不注册SelectionKey.OP_WRITE;
还有当selectionKey.isWritable()时,为什么每次都要注册
SelectionKey.OP_READ,我觉得应该是在selectionKey.isConnectable()
时同时注册OP_READ,OP_WRITE吧
我也有点纳闷,如果把如下代码
else if (selectionKey.isWritable()) {
//将缓冲区清空以备下次写入
sendbuffer.clear();
// 返回为之创建此键的通道。
client = (SocketChannel) selectionKey.channel();
sendText="message from server--" + flag++;
//向缓冲区中输入数据
sendbuffer.put(sendText.getBytes());
//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip();
//输出到通道
client.write(sendbuffer);
System.out.println("服务器端向客户端发送数据--:"+sendText);
client.register(selector, SelectionKey.OP_READ);
}
中的红色部分去掉,就会出现服务器一直往客户端打印东西? 这是为何? 兄台弄清楚了么?
-------------
NIOServer 类第101行有些问题
如果继续注册读事件,服务端会持续得进行循环.
这样客户端接受不到服务端返回得数据,会一直保持连接.
---------
应该将NIOServer 类第101行替换为client.close() ;
---------
欢迎拍砖.
11 楼
softkf
2014-02-25
zk1878 写道
对nio不是很了解,这里有个疑问,在client那的代码
为什么当selectionKey.isConnectable()时,只注册了
SelectionKey.OP_READ,而不注册SelectionKey.OP_WRITE;
还有当selectionKey.isWritable()时,为什么每次都要注册
SelectionKey.OP_READ,我觉得应该是在selectionKey.isConnectable()
时同时注册OP_READ,OP_WRITE吧
我也有点纳闷,如果把如下代码
else if (selectionKey.isWritable()) {
//将缓冲区清空以备下次写入
sendbuffer.clear();
// 返回为之创建此键的通道。
client = (SocketChannel) selectionKey.channel();
sendText="message from server--" + flag++;
//向缓冲区中输入数据
sendbuffer.put(sendText.getBytes());
//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip();
//输出到通道
client.write(sendbuffer);
System.out.println("服务器端向客户端发送数据--:"+sendText);
client.register(selector, SelectionKey.OP_READ);
}
中的红色部分去掉,就会出现服务器一直往客户端打印东西? 这是为何? 兄台弄清楚了么?
10 楼
zk1878
2014-01-14
对nio不是很了解,这里有个疑问,在client那的代码
为什么当selectionKey.isConnectable()时,只注册了
SelectionKey.OP_READ,而不注册SelectionKey.OP_WRITE;
还有当selectionKey.isWritable()时,为什么每次都要注册
SelectionKey.OP_READ,我觉得应该是在selectionKey.isConnectable()
时同时注册OP_READ,OP_WRITE吧
9 楼
Jnerd
2014-01-13
运行不了啊,服务端没有接收到字符串,只是连接上了
8 楼
632578551a
2013-03-06
关于 一条读写操作完成之后修改通道关心事件为什么不用
key的interestOps方法???
7 楼
刚开始吧
2013-02-26
这个程序的目的是什么?弄的我很模糊
6 楼
luisfabiano
2013-01-14
listen 方法是私有方法啊。怎么能随便调用呢?
5 楼
wangxinshui87
2012-11-30
为什么不把服务端和客户端的port写一致呢?
4 楼
lgfy1984
2012-09-26
好文,收藏
3 楼
xm_king
2012-07-06
sahala3293 写道
构造方法里面是不是尽量不要抛出异常,会不会导致一些奇怪的问题
这个也是要根据具体情况和场景分析,比如说,在构造方法中需要建立数据库连接,如果数据库连接异常的话,那最好就抛个异常出来,让上层知道。
« 上一页 1 2 下一页 »