日期:2014-05-20  浏览次数:20771 次

有个问题想请教,关于socket
想用socket去实现一个服务端,可以支持多TCP长连接
对请求进行异步处理,但是长连接加上每进来一个客户
端都开一个线程非常的消耗资源,所以能不能这样,用
线程池来处理请求,处理完后把这个链接存放到一个等待
的线程中,该线程用于维护等待的链接,在接受到消息的
时候把链接给线程池进行处理?

并且我该怎么把连接扔给管理进程
我又怎么知道线程是否有消息进来

------解决方案--------------------
楼主的想法是对的,一下分了好多线程出去,浪费,机器性能也受影响,采用线程池的方式处理比较好。
java提供了线程池,网上例子也很多。随便附一个,抛砖引玉。
http://www.cnblogs.com/jersey/archive/2011/03/30/2000231.html
------解决方案--------------------
用NIO,基于事件来处理,可以用一个线程支持N个连接,是大并发处理的标准手段。

各种中间件都用它。


但是初学的话会比传统Socket复杂,有个相对完整的例子供你参考下:
Java code

public class EchoServer {

    private static int SOCKET_NUM = 55555;

    private static DateFormat dateFormatter = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
    
    /**
     * @param args
     */
    public static void main(String[] args) {
        new EchoServer().start();
    }

    public void start() {
        try {
            Selector selector = bindServer();  // 绑定服务端口,并定义一个事件选择器对象记录套接字通道的事件
            
            /* 通过此循环来遍例事件 */
            while (true) {
                log("Waiting events.");
                int n = selector.select(); // 查询事件如果一个事件都没有,这里就会阻塞
                log("Got events: " + n);
                
                ByteBuffer echoBuffer = ByteBuffer.allocate(50); // 定义一个byte缓冲区来存储收发的数据

                /* 循环遍例所有产生的事件 */
                for (SelectionKey key : selector.selectedKeys()) {
                    SocketChannel sc;
                    selector.selectedKeys().remove(key);  // 将本此事件从迭带器中删除
                    
                    /* 如果产生的事件为接受客户端连接(当有客户端连接服务器的时候产生) */
                    if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
                        
                        ServerSocketChannel subssc = (ServerSocketChannel) key.channel(); // 定义一个服务器socket通道
                        
                        sc = subssc.accept(); // 将临时socket对象实例化为接收到的客户端的socket
                        
                        sc.configureBlocking(false); // 将客户端的socket设置为异步
                        
                        sc.register(selector, SelectionKey.OP_READ); // 将客户端的socket的读取事件注册到事件选择器中
                       
                        System.out.println("Got new client:" + sc);
                    }
                    /* 如果产生的事件为读取数据(当已连接的客户端向服务器发送数据的时候产生) */
                    else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
                        
                        sc = (SocketChannel) key.channel(); // 临时socket对象实例化为产生本事件的socket
                        
                        ByteArrayOutputStream bos = new ByteArrayOutputStream(); // 定义一个用于存储byte数据的流对象,存储全部信息
                        
                        echoBuffer.clear(); // 先将客户端的数据清空
                        
                        try {
                            // 循环读取所有客户端数据到byte缓冲区中,当有数据的时候read函数返回数据长度
                            // NIO会自动的将缓冲区一次容纳不下的自动分段
                            int readInt = 0; // 为读取到数据的长度
                            while ((readInt = sc.read(echoBuffer)) > 0) {
                                // 如果获得数据长度比缓冲区大小小的话
                                if (readInt < echoBuffer.capacity()) {
                                    
                                    byte[] readByte = new byte[readInt]; // 建立一个临时byte数组,将齐长度设为获取的数据的长度
                                    // 循环向此临时数组中添加数据
                                    for (int i = 0; i < readInt; i++) {
                                        readByte[i] = echoBuffer.get(i);
                                    }
                                    
                                    bos.write(readByte); // 将此数据存入byte流中
                                }
                                // 否则就是获得数据长度等于缓冲区大小
                                else {                                    
                                    bos.write(echoBuffer.array()); // 将读取到的数据写入到byte流对象中
                                }
                            }
                            // 当循环结束时byte流中已经存储了客户端发送的所有byte数据
                            log("Recive msg: " + new String(bos.toByteArray()));
                        } catch (Exception e) {
                            
                            e.printStackTrace(); // 当客户端在读取数据操作执行之前断开连接会产生异常信息
                            
                            key.cancel(); // 将本socket的事件在选择器中删除
                            break;
                        }
                        
                        writeBack(sc, bos.toByteArray()); // 向客户端写入收到的数据
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 绑定服务端口,初始化整个服务
     * @throws IOException
     */
    private Selector bindServer() throws IOException {
        log("Start binding server socket:" + SOCKET_NUM);
        
        Selector selector = Selector.open(); // 定义一个事件选择器对象记录套接字通道的事件

        ServerSocketChannel ssc = ServerSocketChannel.open(); // 定义一个异步服务器socket对象

        ssc.configureBlocking(false);// 将此socket对象设置为异步

        ServerSocket ss = ssc.socket(); // 定义服务器socket对象-用来指定异步socket的监听端口等信息

        InetSocketAddress address = new InetSocketAddress(SOCKET_NUM); // 定义存放监听端口的对象

        ss.bind(address); // 将服务器与这个端口绑定

        ssc.register(selector, SelectionKey.OP_ACCEPT); // 将异步的服务器socket对象的接受客户端连接事件注册到selector对象内

        log("Binded socket at:" + SOCKET_NUM);
        
        return selector;
    }
    
    private boolean writeBack(SocketChannel sc, byte[] b) {
        ByteBuffer echoBuffer = ByteBuffer.allocate(b.length); // 建立这个byte对象的ByteBuffer
        echoBuffer.put(b); // 将数据存入 
        
        echoBuffer.flip(); // 将缓冲区复位以便于进行其他读写操作
        try {
            // 向客户端写入数据,数据为接受到数据
            sc.write(echoBuffer);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        System.out.println("Msg echo back: " + new String(echoBuffer.array()));
        return true;
    }

    private static void log(Object msg) {
        System.out.println("SERVER [" + dateFormatter.format(new Date()) + "]: " + msg);
    }
}