JAVA NIO TCP SOCKET 聊天群发

来源:互联网 发布:我亲爱的甜橙树 知乎 编辑:程序博客网 时间:2024/05/22 22:14

以前都是用一般的socket编程,用线程来控制。最近突然用nio来做些东西。

 

nio的好处我来说一下:第一,读写都是基于块的,效率高。第二,通过引入selector,简化了网络编程模型,异步非阻塞。

 

既然有这么多好处,那就写个NIO TCP网络聊天室来练练手吧。

 

因为没有写gui,是基于控制台的所以没写私了的部分,只写了公共聊天室。(其实,既然是服务器端可以分发给所有人,分发给特定人也是很容易实现的。

 

注意:这里只是为了练手,联系服务器端分发消息到各个客户端。TCP 来写聊天室,在现实中是不可取的。IM都是基于UDP来写的。

 

先上代码吧。

 

服务器端代码 MySocketServer.java

 

 

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.kevin.nio;

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.Date;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author kevin
 */
public class MySocketServer implements Runnable{

  
    private boolean running;
   
    private Selector selector;
    String writeMsg;
    StringBuffer sb=new StringBuffer();
    SelectionKey ssckey;
    public MySocketServer(){
       
        running=true;
       
    }
    public void init(){
        try {
            selector = Selector.open();
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
            ssc.socket().bind(new InetSocketAddress(2345));
            ssckey=ssc.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("server is starting..."+new Date());
        } catch (IOException ex) {
            Logger.getLogger(MySocketServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    public static void main(String[] args){
        MySocketServer server=new MySocketServer();
        new Thread(server).start();
       
    }
    public void execute(){
        try {
            while(running){
                int num=selector.select();
                if(num>0){
                    Iterator<SelectionKey> it=selector.selectedKeys().iterator();
                    while(it.hasNext()){
                        SelectionKey key=it.next();
                        it.remove();
                        if(!key.isValid()) continue;
                        if(key.isAcceptable()){
                            System.out.println("isAcceptable");
                            getConn(key);
                        }
                        else if(key.isReadable()){
                            System.out.println("isReadable");
                            readMsg(key);
                        }
                       
                        else if(key.isValid()&&key.isWritable()){
                            if(writeMsg!=null){
                                System.out.println("isWritable");
                                writeMsg(key);
                            }

                        }
                        
                        else break;

                    }
                   
                }
                Thread.yield();
            }
           
        } catch (IOException ex) {
            Logger.getLogger(MySocketServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

 

    private void getConn(SelectionKey key) throws IOException {
        ServerSocketChannel ssc=(ServerSocketChannel)key.channel();
            SocketChannel sc=ssc.accept();
            sc.configureBlocking(false);
            sc.register(selector, SelectionKey.OP_READ);
            System.out.println("build connection :"+sc.socket().getRemoteSocketAddress());
    }

    private void readMsg(SelectionKey key) throws IOException {
            sb.delete(0, sb.length());
            SocketChannel sc=(SocketChannel)key.channel();
            System.out.print(sc.socket().getRemoteSocketAddress()+" ");
            ByteBuffer buffer=ByteBuffer.allocate(1024);
            buffer.clear();
            int len=0;
            StringBuffer sb=new StringBuffer();
            while((len=sc.read(buffer))>0){
                 buffer.flip();
                 sb.append(new String(buffer.array(),0,len));
            }
            if(sb.length()>0) System.out.println("get from client:"+sb.toString());
            if(sb.toString().trim().toLowerCase().equals("quit")){
                sc.write(ByteBuffer.wrap("BYE".getBytes()));
                System.out.println("client is closed "+sc.socket().getRemoteSocketAddress());
                key.cancel();
                sc.close();
                sc.socket().close();
               
            }
            else{
                String toMsg=sc.socket().getRemoteSocketAddress()+ "said:"+sb.toString();
                System.out.println(toMsg);

                writeMsg=toMsg;
               
                /*
                Iterator<SelectionKey> it=key.selector().keys().iterator();

                while(it.hasNext()){
                    SelectionKey skey=it.next();
                    if(skey!=key&&skey!=ssckey){
                        SocketChannel client=(SocketChannel) skey.channel();
                        client.write(ByteBuffer.wrap(toMsg.getBytes()));
                    }
                  
                }

                 *
                 */

                /*
               
                key.attach(toMsg);
                key.interestOps(key.interestOps()|SelectionKey.OP_WRITE);
                *
                 */
                Iterator<SelectionKey> it=key.selector().keys().iterator();

                while(it.hasNext()){
                    SelectionKey skey=it.next();
                    if(skey!=key&&skey!=ssckey){
                        if(skey.attachment()!=null){
                            String str=(String) skey.attachment();
                            skey.attach(str+toMsg);
                        }else{
                            skey.attach(toMsg);
                        }
                        skey.interestOps(skey.interestOps()|SelectionKey.OP_WRITE);
                    }

                }
                selector.wakeup();//可有可无
               
            }
           
    }

  

    public void run() {
            init();
            execute();
    }

    private void writeMsg(SelectionKey key) throws IOException {
           
            System.out.println("++++enter write+++");
            SocketChannel sc=(SocketChannel) key.channel();
            String str=(String) key.attachment();
           
            sc.write(ByteBuffer.wrap(str.getBytes()));
            key.interestOps(SelectionKey.OP_READ);
    }
}

 

客户端:MySocketClient.java

 

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.kevin.nio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Currency.*;

/**
 *
 * @author kevin
 */
public class MySocketClient implements Runnable{
    Selector selector;
   
     boolean running;
    
     SocketChannel sc;
     public MySocketClient(){
         running=true;
        
     }
     public void init() {
        try {
            sc = SocketChannel.open();
            sc.configureBlocking(false);
            sc.connect(new InetSocketAddress("localhost", 2345));
           
           
        } catch (IOException ex) {
            Logger.getLogger(MySocketClient.class.getName()).log(Level.SEVERE, null, ex);
        }
     }
   
    public static void main(String[] args){
       

        MySocketClient client=new MySocketClient();
        new Thread(client).start();
    }
     
       
    public void execute(){
       
        int num=0;
        try {
            while (!sc.finishConnect()) {
            }
        } catch (IOException ex) {
            Logger.getLogger(MySocketClient.class.getName()).log(Level.SEVERE, null, ex);
        }

        ReadKeyBoard rkb=new ReadKeyBoard();
        new Thread(rkb).start();
        while(running){
            try {
               
                ByteBuffer buffer=ByteBuffer.allocate(1024);
                buffer.clear();

                StringBuffer sb=new StringBuffer();
                Thread.sleep(500);

               
                while((num=sc.read(buffer))>0){
                    sb.append(new String(buffer.array(),0,num));
                    buffer.clear();
                }
                if(sb.length()>0) System.out.println(sb.toString());
                if(sb.toString().toLowerCase().trim().equals("bye")){
                    System.out.println("closed....");
                   
                    sc.close();
                    sc.socket().close();
                    rkb.close();
                    running=false;
                }
            } catch (InterruptedException ex) {
                Logger.getLogger(MySocketClient.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(MySocketClient.class.getName()).log(Level.SEVERE, null, ex);
            }
                }
                              
          }

    public void run() {
        init();
        execute();
    }


  class ReadKeyBoard implements Runnable{

    boolean running2=true;
    public ReadKeyBoard(){
       
    }
    public void close(){
        running2=false;
    }
    public void run() {
        BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
        while(running2){
            try {
                System.out.println("enter some commands:");
            String str = reader.readLine();
            sc.write(ByteBuffer.wrap(str.getBytes()));

        } catch (IOException ex) {
            Logger.getLogger(ReadKeyBoard.class.getName()).log(Level.SEVERE, null, ex);
        }
        }

    }

}
}

 

总结:

    1. 服务器端一定注意注册需要的操作,不要注册不需要的操作。比如在连接被接受后,其实scoket是可以读和可以写的。这里的注册读的话,那么意味着,只有真的有数据来了,才会接到消息。但是随时可以写,如果这个时候注册读写的话(sc.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);),那么服务器就会进入cpu 100%的状态。所以当连接刚被接受的时候,一定要注册读sc.register(selector, SelectionKey.OP_READ);

 

    2. 服务器端写有两种方法:一种就是直接写。第二种是加入ATTACH,然后,通过skey.interestOps(skey.interestOps()|SelectionKey.OP_WRITE);把写消息写到interestOps集合中。就出发了可写的通知了。注意第二种方式,在接到可写的通知后,处理完了消息后,还是得恢复只对写有兴趣的interestOps 如key.interestOps(SelectionKey.OP_READ);

 

    3. 得到所有服务器端的连接的方式是key.selector().keys(),但是一定记得,里面有一个是SocketServerChannel注册的key,这个只可以接受连接,其他的什么都做不了。更不能写数据给客户端。所以一定记得剔除这个。不然程序会抛异常。

    4. 客户端比较简单,所以可以用select的方式,也可以不用。

原创粉丝点击