多線程非阻塞服務器設計

接着上篇日誌寫。。一般在設計服務器時,都是非阻塞的,且爲了簡單,通常都設計爲一個線程來操作。

但是這樣設計的缺點也很明顯,倘若服務器有很多連接,那麼每次循環都會處理很多套接字,除了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。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章