java nio socket长连接_JavaNIO处理长连接

之前在IBM的网站上看到过一篇介绍NIO的文章,收获很大。但文中的代码只适合短连接的情况,长连接时就不适用了。

最近恰好要写一个处理长连接的服务,接收日志包,然后打包成syslog形式再转发,所以在它的基础上改了一下。

主要改了两个类,一个是Server,因为我们只关注read事件,所以write事件我们暂不处理。另外,在处理完ON_READ事件后,不能执行key.cancel()。

package nioserver;

import java.net.InetSocketAddress;

import java.net.ServerSocket;

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.LinkedList;

import java.util.List;

import java.util.Set;

/**

*

Title: 主控服务线程,采用NIO实现的长连接服务器

* @author sxl

* @version 1.0

*/

public class Server implements Runnable {

private static Selector selector;

private ServerSocketChannel sschannel;

private InetSocketAddress address;

protected Notifier notifier;

private int port;

/**

* 创建主控服务线程

* @param port 服务端口

* @throws java.lang.Exception

*/

public static int MAX_THREADS = 4;

public Server(int port) throws Exception {

this.port = port;

// 获取事件触发器

notifier = Notifier.getNotifier();

// 创建读写线程池

for (int i = 0; i < MAX_THREADS; i++) {

Thread r = new Reader();

Thread w = new Writer();

Thread sys = new Syslog();

r.start();

w.start();

sys.start();

}

// 创建无阻塞网络套接

selector = Selector.open();

sschannel = ServerSocketChannel.open();

sschannel.configureBlocking(false);

address = new InetSocketAddress(port);

ServerSocket ss = sschannel.socket();

ss.bind(address);

sschannel.register(selector, SelectionKey.OP_ACCEPT);

}

public void run() {

System.out.println("Server started ...");

System.out.println("Server listening on port: " + port);

// 监听

while (true) {

try {

int num = 0;

num = selector.select();

if (num > 0) {

Set selectedKeys = selector.selectedKeys();

Iterator it = selectedKeys.iterator();

while (it.hasNext()) {

SelectionKey key = (SelectionKey) it.next();

it.remove();

// 处理IO事件

if (key.isAcceptable()) {

// Accept the new connection

ServerSocketChannel ssc = (ServerSocketChannel) key.channel();

notifier.fireOnAccept();

SocketChannel sc = ssc.accept();

sc.configureBlocking(false);

// 触发接受连接事件

Request request = new Request(sc);

notifier.fireOnAccepted(request);

// 注册读操作,以进行下一步的读操作

sc.register(selector, SelectionKey.OP_READ, request);

}

else if (key.isReadable()) {

Reader.processRequest(key); // 提交读服务线程读取客户端数据

}

}

}

}

catch (Exception e) {

continue;

}

}

}

}

另一个改动的类是Reader,改变对-1的处理,这里不是break,而是抛出异常。在读取完buffer的数据后,将数据包传递给另外两个线程进行syslog的发送和入库操作。

package nioserver;

import java.io.IOException;

import java.nio.ByteBuffer;

import java.nio.channels.SelectionKey;

import java.nio.channels.SocketChannel;

import java.util.LinkedList;

import java.util.List;

/**

*

Title: 读线程

*

Description: 该线程用于读取客户端数据

* @author sxl

* @version 1.0

*/

public class Reader extends Thread {

private static List pool = new LinkedList();

private static Notifier notifier = Notifier.getNotifier();

public void run() {

while (true) {

try {

SelectionKey key;

synchronized (pool) {

while (pool.isEmpty()) {

pool.wait();

}

key = (SelectionKey) pool.remove(0);

}

// 读取数据

read(key);

}

catch (Exception e) {

continue;

}

}

}

/**

* 读取客户端发出请求数据

* @param sc 套接通道

*/

private static int BUFFER_SIZE = 1024;

public static byte[] readRequest(SocketChannel sc) throws IOException {

ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);

int off = 0;

int r = 0;

byte[] data = new byte[BUFFER_SIZE * 10];

while ( true ) {

buffer.clear();

r = sc.read(buffer);

if(r == 0) break;

if(r == -1)//如果是流的末尾,则抛出异常

throw new IOException();

if ((off + r) > data.length) {//数组扩容

data = grow(data, BUFFER_SIZE * 10);

}

byte[] buf = buffer.array();

System.arraycopy(buf, 0, data, off, r);

off += r;

}

byte[] req = new byte[off];

System.arraycopy(data, 0, req, 0, off);

return req;

}

/**

* 处理连接数据读取

* @param key SelectionKey

*/

public void read(SelectionKey key) {

SocketChannel sc = null;

try {

// 读取客户端数据

sc = (SocketChannel) key.channel();

byte[] clientData = readRequest(sc);

if(clientData.length > 0){//有数据才处理

Request request = (Request)key.attachment();

request.setDataInput(clientData);

// 提交到数据库写入线程

Writer.processRequest(request);

// 提交到Syslog线程,发送syslog

Syslog.processRequest(request);

}

}

catch (Exception e) {

if(sc != null)

try {

sc.socket().close();

sc.close();

} catch (IOException e1) {

e1.printStackTrace();

}

}

}

/**

* 处理客户请求,管理用户的联结池,并唤醒队列中的线程进行处理

*/

public static void processRequest(SelectionKey key) {

synchronized (pool) {

pool.add(pool.size(), key);

pool.notifyAll();

}

}

/**

* 数组扩容

* @param src byte[] 源数组数据

* @param size int 扩容的增加量

* @return byte[] 扩容后的数组

*/

public static byte[] grow(byte[] src, int size) {

byte[] tmp = new byte[src.length + size];

System.arraycopy(src, 0, tmp, 0, src.length);

return tmp;

}

}

4

2

分享到:

18e900b8666ce6f233d25ec02f95ee59.png

72dd548719f0ace4d5f9bca64e1d7715.png

2012-09-12 21:04

浏览 13521

评论

5 楼

zk1878

2014-01-14

楼主 这代码完整么? Notifier 这个是啥类,还有Reader 是个线程,然后又有静态方法的,感觉写的很乱呀

4 楼

linginfanta

2013-09-24

有问题,一直OP_ACCEPT...和OP_READ...。CPU百分之几百。

3 楼

Rong_it

2013-05-14

这个里面有个问题就是某个连接持续发送过程中, 某个SelectioneKey在阻塞过程中Reader.processRequest(key); 一直会被触发,而且添加到List pool = new LinkedList(); 中,可以在Reader.processRequest(key); 前面加条日志看看~

2 楼

378629846

2012-09-13

48150084 写道

做过压力测试吗?非常怀疑会死锁。

传的数据前几位最好给出长度,不要做啥数组扩容,很不专业。

不要用wait,notify。LinkedList换成LinkedBlockingQueue。

谢谢!是应该换成LinkedBlockingQueue,代码简洁多了。

1 楼

48150084

2012-09-13

做过压力测试吗?非常怀疑会死锁。

传的数据前几位最好给出长度,不要做啥数组扩容,很不专业。

不要用wait,notify。LinkedList换成LinkedBlockingQueue。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值