ServerSocketChannel

ServerSocketChannel

到目前为止,所举的案例中都没有涉及Selector。不要急,好东西要慢慢来。Selector类可以用于避免使用阻塞式客户端中很浪费资源的“忙等”方法。例如,考虑一个IM服务器。像QQ或者旺旺这样的,可能有几万甚至几千万个客户端同时连接到了服务器,但在任何时刻都只是非常少量的消息。需要读取和分发。这就需要一种方法阻塞等待,直到至少有一个信道可以进行I/O操作,并指出是哪个信道。NIO的选择器就实现了这样的功能。一个Selector实例可以同时检查一组信道的I/O状态。用专业术语来说,选择器就是一个多路开关选择器,因为一个选择器能够管理多个信道上的I/O操作。然而如果用传统的方式来处理这么多客户端,使用的方法是循环地一个一个地去检查所有的客户端是否有I/O操作,如果当前客户端有I/O操作,则可能把当前客户端扔给一个线程池去处理,如果没有I/O操作则进行下一个轮询,当所有的客户端都轮询过了又接着从头开始轮询;这种方法是非常笨而且也非常浪费资源,因为大部分客户端是没有I/O操作,我们也要去检查;而Selector就不一样了,它在内部可以同时管理多个I/O,当一个信道有I/O操作的时候,他会通知Selector,Selector就是记住这个信道有I/O操作,并且知道是何种I/O操作,是读呢?是写呢?还是接受新的连接;所以如果使用Selector,它返回的结果只有两种结果,一种是0,即在你调用的时刻没有任何客户端需要I/O操作,另一种结果是一组需要I/O操作的客户端,这是你就根本不需要再检查了,因为它返回给你的肯定是你想要的。这样一种通知的方式比那种主动轮询的方式要高效得多!要使用选择器(Selector),需要创建一个Selector实例(使用静态工厂方法open())并将其注册(register)到想要监控的信道上(注意,这要通过channel的方法实现,而不是使用selector的方法)。最后,调用选择器的select()方法。该方法会阻塞等待,直到有一个或更多的信道准备好了I/O操作或等待超时。select()方法将返回可进行I/O操作的信道数量。现在,在一个单独的线程中,通过调用select()方法就能检查多个信道是否准备好进行I/O操作。如果经过一段时间后仍然没有信道准备好,select()方法就会返回0,并允许程序继续执行其他任务。package nio.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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;public class ServerSocketChannelTest {public class ServerSocketChannelTest {
public static void main(String[] args) {
selector();
}
private static final int BUF_SIZE=1024;
private static final int PORT=8080;
private static final int TIMEOUT=3000;

public static void handleAccept(SelectionKey key) throws IOException {
ServerSocketChannel ssChannel=(ServerSocketChannel)key.channel();
//通常不会仅仅只监听一个连接,在while循环中调用 accept()方法
//通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 accept()
//方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。因此,
//accept()方法会一直阻塞到有新连接到达。
SocketChannel sc=ssChannel.accept();
sc.configureBlocking(false);
sc.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocate(BUF_SIZE));
}
public static void handleRead(SelectionKey key) throws IOException {
SocketChannel sc=(SocketChannel)key.channel();
ByteBuffer buf=(ByteBuffer)key.attachment();
long bytesRead=sc.read(buf);
while(bytesRead>0) {
//flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,
//并将limit设置成之前position的值。
buf.flip();
while(buf.hasRemaining()) {
//使用get()方法从Buffer中读取数据
System.out.print((char)buf.get());
}
System.out.println();
buf.clear();
bytesRead=sc.read(buf);
}
if(bytesRead==-1) {
sc.close();
}
}
public static void handleWrite(SelectionKey key) throws IOException {
ByteBuffer buf=(ByteBuffer)key.attachment();
buf.flip();
SocketChannel sc=(SocketChannel)key.channel();
while(buf.hasRemaining()) {
sc.write(buf);
}
//compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。
buf.compact();
}
public static void selector() {
Selector selector=null;
ServerSocketChannel ssc=null;
try {
selector=Selector.open();
//通过调用 ServerSocketChannel.open() 方法来打开ServerSocketChannel.
ssc=ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(PORT));
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
while(true) {
if(selector.select(TIMEOUT)0) {
System.out.println("
");
continue;
}
Iterator iter=selector.selectedKeys().iterator();
while(iter.hasNext()) {
SelectionKey key=iter.next();
if(key.isAcceptable()) {
handleAccept(key);
}
if(key.isReadable()) {
handleRead(key);
}
if(key.isWritable()&&key.isValid()) {
handleWrite(key);
}
if(key.isConnectable()) {
System.out.println(“isConnectable = true”);
}
iter.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(selector!=null) {
selector.close();
}
if(ssc!=null) {
//通过调用ServerSocketChannel.close() 方法来关闭ServerSocketChannel
ssc.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端代码
package nio.socket;import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;public class SocketChannelTest {
public static void main(String[] args) {
client();
}
public static void client() {
ByteBuffer buffer=ByteBuffer.allocate(1024);
SocketChannel socketChannel=null;
try {
socketChannel=SocketChannel.open();//打开SocketChannel
socketChannel.configureBlocking(false);//设置为非阻塞方式
socketChannel.connect(new InetSocketAddress(“127.0.0.1”,8080));
//为了确定连接是否建立,可以调用finishConnect()的方法
if(socketChannel.finishConnect()) {
int i=0;
while(true) {
TimeUnit.SECONDS.sleep(1);
String info=“I am “+i+++”-th information from client”;
buffer.clear();
buffer.put(info.getBytes());
buffer.flip();
while(buffer.hasRemaining()) {
System.out.println(buffer);
socketChannel.write(buffer);//写数据到SocketChannel用的是SocketChannel.write()方法
}
}
}
} catch (IOException |InterruptedException e) {
e.printStackTrace();
}finally {
if(socketChannel!=null) {
try {
socketChannel.close();//关闭SocketChannel
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值