多线程非阻塞服务器设计

接着上篇日志写。。一般在设计服务器时,都是非阻塞的,且为了简单,通常都设计为一个线程来操作。

但是这样设计的缺点也很明显,倘若服务器有很多连接,那么每次循环都会处理很多套接字,除了CPU使用率不高外,如果某个套接字的数据传输速度很慢,那么他的调用也会很慢(个人猜测,因为TCP传输速度是双向均衡的),从而势必会影响其他套接字的数据传输。

所以非阻塞+多线程是大型服务器必备的解决方案。

多线程操作同一套接字需要考虑的问题比较复杂,我在http://blog.csdn.net/shallwake/archive/2009/12/15/5014160.aspx有讨论,原则就是:尽量不要多线程同时recv();绝对不能多线程send();可以用两个线程,一个recv(),一个send()。原因简单点就是虽然套接字是线程安全,但是多线程会有发包错乱。

所以,对于服务器,只要保证每个线程操作的套接字间没有交集就行,当然,此交集只是逻辑上没有交集,比如,某个线程操作A套接字send(),另一个线程负责操作A套接字recv(),那么他们还是逻辑上没有交集的,原因上面已讲述。

剩下就是数据结构的设计,个人总结有2种。

一,动态创建线程:

就是规定某个线程处理N个套接字,当总套接字超过此值就另外再创建线程处理。

二,预先分配线程:

就是先分配N个线程,然后随着套接字的增长,将他们均匀地分配给每个线程。

难点:套接字是动态变化的,增加的时候比较好办,但是若是减少,那么方法一与方法二就有难度差距了。

对于方法一:当某个线程负责处理的套接字减少,小于N时,那么下次增加套接字时不能再开线程,要继续利用它,若是套接字减少为0,那么此线程就自己销毁了。所以整个动态过程的维护时比较复杂的,自己只是想会,没试验过。

对于方法二:我下面提供解决方案。

 

方法二的实现:

首先感谢kasicass GG在上篇文章中的指点~~,今天下午按照他的思路用java简单尝试了下

思路还是比较简单,开启5个线程负责异步读取客户端信息,当然,若是没有套接字处理,线程不能退出,所以每个线程都用到了BlockingQueue,而且维护了一个变量sockCount记录自己当前处理的套接字数量。当有连接时,主线程选择sockCount最少的那个线程负责处理该连接,就OK了,没有方法一动态维护那么复杂。

嗯,懒人大招,帖代码了。。。

Server类

import java.nio.channels.*;
import java.util.*;
import java.net.*;
import java.io.*;

public class Server extends Thread{

    private ServerSocketChannel sSockChan;

    private Selector selector;

    private ArrayList
  
  
   
    readers;

    public Server(){
        readers = new ArrayList();
        
   
   for(
   
   int i = 0; i < 5; i ++){
            EventReader er = new EventReader(this);
            er.start();
            readers.add(er);
        }

        initServerSocket();
    }

    public 
   
   int getIndex(){
        
   
   int min = 999999;
        
   
   int pos = 0;
        
   
   for(
   
   int i = 0; i < 5; i ++){
            
   
   if(min >= readers.get(i).getSocketCount()){
                 min = readers.get(i).getSocketCount();
                 pos = i;
            }
        }
        
   
   return pos;
    }

    private 
   
   void initServerSocket() {
	try {
	    
   
   // open a non-blocking server socket channel
	    sSockChan = ServerSocketChannel.
   
   open();
	    sSockChan.configureBlocking(false);

	    
   
   // bind to localhost on designated port
	    InetAddress addr = InetAddress.getLocalHost();
	    System.out.println("
   
   binding to address: " + addr.getHostAddress());
	    sSockChan.socket().bind(new InetSocketAddress(addr, 8550));

	    
   
   // get a selector
	    selector = Selector.
   
   open();

	    
   
   // register the channel with the selector to handle accepts
	    SelectionKey acceptKey = sSockChan.
   
   register(selector, SelectionKey.OP_ACCEPT);
	}
	catch (Exception e) {
	    System.out.println("
   
   error initializing ServerSocket");
	    System.
   
   exit(1);
	}
    }

    @Override
    public 
   
   void run(){

        
   
   while(true){

	    try {
		
   
   // blocking select, will return when we get a new connection
		selector.select();

		
   
   // fetch the keys
		Set readyKeys = selector.selectedKeys();

		
   
   // run through the keys and process
		Iterator i = readyKeys.iterator();
		
   
   while (i.hasNext()) {
		    SelectionKey key = (SelectionKey) i.next();
		    i.
   
   remove();

		    ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();
		    SocketChannel clientChannel = ssChannel.accept();

		    
   
   // 这里是重点。
		    readers.get(getIndex()).addNewClient(clientChannel,getIndex());
		    System.out.println("
   
   got connection from: " + clientChannel.socket().getInetAddress());
		}
	    }
	    catch (IOException ioe) {
		System.out.println("
   
   error during serverSocket select(): " + ioe.getMessage());
	    }
	    catch (Exception e) {
		System.out.println("
   
   exception in run()");
	    }
        }
    }

    public 
   
   static 
   
   void main(String[] args) {
        Server server = new Server();
        server.start();
    }
}
  
  
EventReader类(就是每个线程)
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;

public class EventReader extends Thread{
    private Server sv;
    
    private Selector selector;
    
    private BlockingQueue newClients;

    private int scoketCount = 0;

    public int getSocketCount(){
        return scoketCount;
    }

    public EventReader(Server sv){
        this.sv = sv;
        newClients = new LinkedBlockingQueue();
    }

    public void addNewClient(SocketChannel clientChannel, int id) {
        try {
            scoketCount++;
            newClients.put(clientChannel);
            selector.wakeup();
            System.out.println("I'm in thread" + id);
        } catch (InterruptedException ex) {
            Logger.getLogger(EventReader.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public void run(){
        try {
            selector = Selector.open();

            while(true){
                read();
                checkNewConnections();

                try { Thread.sleep(30); } catch (InterruptedException e) {}
            }
        } catch (IOException ex) {
            Logger.getLogger(EventReader.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    private void read(){
        try {
            selector.select();
            Set readyKeys = selector.selectedKeys();

            Iterator i = readyKeys.iterator();
            while (i.hasNext()) {
                SelectionKey key = (SelectionKey) i.next();
                i.remove();
                SocketChannel channel = (SocketChannel) key.channel();
                ByteBuffer buffer = (ByteBuffer)key.attachment();

                long nbytes = channel.read(buffer);

                if(nbytes == -1){
                    System.out.println("channel has closed");
                    scoketCount--;
                    channel.close();
                }
                System.out.println(buffer.array());

                //注意,只是作为简单演示,并未考虑如何读取完整数据包,读多少输出多少然后复原。
                buffer.clear();
            }
        } catch (IOException ex) {
            Logger.getLogger(EventReader.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void checkNewConnections(){
        try {
            SocketChannel clientChannel = (SocketChannel) newClients.take();
            clientChannel.configureBlocking( false);
            clientChannel.register( selector, SelectionKey.OP_READ,ByteBuffer.allocate(1024));
        } catch (InterruptedException ex) {
            Logger.getLogger(EventReader.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch(IOException ex){

        }
    }
}


Over。。。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值