JAVA网络编程NIO实现简易多人聊天室

本文介绍了BIO(Blocking IO)和NIO(Non-blocking IO)两种网络IO模型的工作原理及其区别。在BIO模型中,服务端每个连接都需要一个独立的线程处理,而NIO则通过选择器实现了一对多的连接处理,提高了效率。文中还给出了BIO和NIO实现的简易多人聊天室代码示例,展示了NIO如何利用Selector和Buffer进行数据传输。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

BIO模型

BIO即blocking IO,顾名思义是一种阻塞模型。当没有客户端连接时,服务端会一直阻塞,当有客户端新建连接时,服务端会新开一个线程去响应(不用多线程的话服务端同一时刻最多只能接收一个连接)。但不断的新开线程对服务器的压力是巨大的,为了缓解压力可以采用线程池技术实现线程复用,但这种做法治标不治本,本质还是一个连接一个线程。代码如下:

//服务端
public class BIOServer {

    private final static int PORT = 8888;

    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(PORT);
        ExecutorService pool = Executors.newFixedThreadPool(20);
        while(true) {
            Socket socket = server.accept();
            pool.execute(()->{
                handler(socket);
            });
        }
    }

    public static void handler(Socket socket) {
        try {
            InputStream inputStream = socket.getInputStream();
            byte[] bytes = new byte[1024];
            while(true) {
                int len = inputStream.read(bytes);
                if(len != -1) {
                    System.out.println("收到来自客户端的消息:" + new String(bytes,0, len));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
//客户端
public class BIOClient {

    private final static String IP = "localhost";
    private final static int PORT = 8888;

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket(IP,PORT);
        Scanner sc = new Scanner(System.in);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        while(sc.hasNext()) {
            String str = sc.nextLine();
            bw.write(str);
            bw.flush();
        }
    }

}

NIO模型

NIO即non-blocking IO,顾名思义是一种非阻塞模型。NIO的目的就是实现一个线程处理多个连接,故引入了几个重要的核心概念:

  • Channel,管道。Channel可以理解为连接,与BIO中Sokcet类似,一个连接对应一个Channel,但Channel中仍内置了一个Socket,可以调用socket()获取。
  • Selector,选择器。Selector类似一个调度中心,所有Channel都需要注册到选择器中,并绑定一个SelectionKey,绑定时还会指定要监听的事件,如:连接就绪、读就绪、写就绪等。可以调用Selector提供的API实现对发生监听事件的连接进行处理。
  • Buffer,缓冲区。Buffer底层是一个数组,供Channel实现对数据的读写。Buffer的position、limit、capacity分别指当前索引、读/写上限索引、数组容量。

三者之间的关系如下图所示(图比较乱…序号表示NIO流程执行的大概步骤,部分连线只为了便于理解,不等于实际调用):
在这里插入图片描述
下面是用NIO实现的简易多人聊天室代码:

//服务端
public class NIOServer {

    private final String IP = "localhost";
    private final int PORT = 8888;
    private ServerSocketChannel serverChannel;
    private Selector selector;
    private int count;

    public NIOServer init() {
        try {
            serverChannel = ServerSocketChannel.open();
            selector = Selector.open();
            serverChannel.bind(new InetSocketAddress(IP,PORT));
            serverChannel.configureBlocking(false);
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            count = 0;
        } catch (IOException e) {
            System.out.println("初始化失败...");
        } finally {
            return this;
        }
    }

    public void start() {
        while(true) {
            try {
                //阻塞直到有任意通道发生任一事件
                selector.select();
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    //监听到新用户连接
                    if (key.isAcceptable()) {
                        userConnect(key);
                    }
                    //监听到有用户发消息
                    if (key.isReadable()) {
                        userSendMsg(key);
                    }
                    iterator.remove();
                }
            } catch (IOException e) {
                System.out.println("未知的bug...");
            }
        }
    }

    public void userConnect(SelectionKey key) throws IOException {
        SocketChannel channel = serverChannel.accept();
        channel.configureBlocking(false);
        channel.register(selector, SelectionKey.OP_READ);
        String msg = "有新的用户接入:" + channel.socket().getRemoteSocketAddress() +
                ",在线用户总数:" + ++count + "个";
        System.out.println(msg);
        transfer(msg, channel);
    }

    public void userSendMsg(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        String msg = null;
        try {
            //用户非正常关闭会直接在read这里抛IO异常
            int len = channel.read(buffer);
            //用户正常关闭连接需要手动抛IO异常,不然会一直空轮询分发读就绪事件
            if (len == -1)
                throw new IOException();
            msg = channel.socket().getPort() + ":" + new String(buffer.array(), 0, len);
        } catch (IOException e) {
            //有用户断开连接
            msg = channel.socket().getPort() + "断开连接,在线用户总数:" + --count + "个";
            key.cancel();
            channel.socket().close();
            channel.close();
        } finally {
            System.out.println(msg);
            transfer(msg, channel);
        }
    }

    public void transfer(String msg, SocketChannel self) throws IOException{
        Iterator<SelectionKey> iterator = selector.keys().iterator();
        ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
        while(iterator.hasNext()) {
            SelectionKey key = iterator.next();
            if(key.channel() instanceof ServerSocketChannel)
                continue;
            SocketChannel channel = (SocketChannel) key.channel();
            //转发用户消息
            if(self != key.channel()) {
                channel.write(buffer);
                buffer.clear();
            }
        }
    }

    public static void main(String[] args) {
        new NIOServer().init().start();
    }

}
public class NIOClient {

    private final static String IP = "localhost";
    private final static int PORT = 8888;
    private Selector selector;
    private SocketChannel channel;

    public void init() {
        try {
            selector = Selector.open();
            InetSocketAddress address = new InetSocketAddress(IP, PORT);
            channel = SocketChannel.open();
            channel.connect(address);
            channel.configureBlocking(false);
            channel.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            System.out.println("初始化失败...");
        }
    }

    public void sendMsg(String msg) {
        try {
            ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
            channel.write(buffer);
        } catch (IOException e) {
            System.out.println("服务器异常...");
        }
    }

    public void readMsg(ByteBuffer buffer) {
        try {
            //只绑定了一个channel且监听的读事件...
            if(selector.select() > 0) {
                int len = channel.read(buffer);
                if (len != -1) {
                    buffer.flip();
                    System.out.println(new String(buffer.array(), 0, len));
                }
                //这一步一定不能少,不然只能读一次消息
                selector.selectedKeys().clear();
            }
        } catch (IOException e) {
            System.out.println("服务器异常...");
        }
    }

    public static void main(String[] args) {
        NIOClient client = new NIOClient();
        client.init();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        new Thread(() -> {
            while (true) {
                buffer.clear();
                client.readMsg(buffer);
            }
        }).start();
        Scanner sc = new Scanner(System.in);
        while(sc.hasNext()) {
            client.sendMsg(sc.nextLine());
        }
        try {
            client.channel.socket().close();
            client.channel.close();
        } catch (IOException e) {
            System.out.println("退出异常...");
        }
    }

}

写代码时遇到的问题及个人理解

  • 当调用Channel的register方法注册到Selector前,一定要先把管道设置为非阻塞模式,即调用configureBlocking(false),否则会报模式错误。
  • SelectionKey与Channel是绑定关系,可以用SelectionKey获取绑定的Channel。在Selector中存储SelectionKey时其实有两个表:注册表与事件表。其中,注册表包含了所有注册到Selector的Channel对应的SelectionKey,而事件表只包含那些发生了监听事件的SelectionKey。
  • Selector提供的几个重要的API:select()、selectedKeys()、keys()。selectedKeys()返回的是事件表集合,keys()返回的是注册表集合。select()比较特殊,它返回的是发生监听事件的SelectionKey数目,并不是简单的返回事件表的大小,而是在注册表中存在但不在事件表中存在且此时发生了监听事件的SelectionKey数目(即从注册表新增到事件表的数目),且它会阻塞直到该数目>0。这几个方法弄清楚就可以解释为何每次遍历时需要移除当前元素。
  • 用户正常断开与非正常断开时在服务端的情况是不一样的。正常断开指客户端正常调用channel和socket的close方法关闭,此时服务端调用channel.read()时会返回-1,需要判断该情况并断开连接。非正常断开即客户端没有调用close方法关闭直接结束程序,此时服务端调用channel.read()会直接报IO异常,所以需要自己抓。
  • 多人聊天时客户端需要自己不断发消息同时不断接收服务端发送的消息,需使用两个线程分别处理。
  • 上述BIO实现的聊天室是主线程负责监听端口并建立连接,后丢给线程池中的线程负责每个连接的网络IO(一个连接一个线程);上述NIO实现的聊天室仅包含一个线程即主线程,负责监听端口建立连接以及每个连接的网络IO。实际上NIO也可扩展为多个selector的模式,效率更高,可参考Netty模型。
package com.ui.server; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ServerBootFrame extends JFrame { /** * */ private static final long serialVersionUID = 1L; JPanel jp = new JPanel(new BorderLayout()); JPanel jp1 = new JPanel(new FlowLayout()); JScrollPane jsp1 = new JScrollPane(); JButton jbStart = new JButton("启动"); JButton jbEnd = new JButton("关闭"); JTextArea jtaState = new JTextArea(10, 25); Font font = new Font("Serif", Font.BOLD, 18); Color fore = Color.YELLOW; Color back = new Color(81, 217, 251); public ServerBootFrame(String title) { super(title); this.getContentPane().add(jp); jp.add(jsp1, "Center"); jsp1.getViewport().add(jtaState); jp.add(jp1, "South"); jp1.add(jbStart); jp1.add(jbEnd); jtaState.setFont(font); jtaState.setForeground(fore); jtaState.setBackground(back); jp1.setBackground(back); this.setResizable(false); this.setLocation(250, 250); } public void showFrame() { this.pack(); this.setVisible(true); } public void bootingServer(final BootEndInterface bt) { this.jbStart.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { bt.boot(); } }); } public void endingServer(final BootEndInterface ed) { this.jbEnd.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ed.end(); } }); } public void closeWindow(final BootEndInterface ed) { this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e1) { ed.end(); } }); } public void appendStringTojtaState(String str) { jtaState.append(str); } } package com.ui.client; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import java.sql.*; public class LoginFrame extends JFrame { JLabel jUserName=new JLabel("用户姓名:"); JLabel jUserPsd=new JLabel("用户密码:"); JTextField txtUserName=new JTextField("",10); JPasswordField txtUserPsd=new JPasswordField(10); JButton okButton=new JButton("确定"); JButton regButton=new JButton("注册"); JPanel jp=new JPanel(new GridLayout(2,1)); JPanel jp1=new JPanel(new FlowLayout(FlowLayout.CENTER)); JPanel jp2=new JPanel(new FlowLayout(FlowLayout.LEFT)); JPanel jp3=new JPanel(new FlowLayout()); Font f=new Font("Serif",Font.BOLD,15); public LoginFrame() { super("用户登陆界面"); this.setLocation(250,250); this.getContentPane().add(jp,"Center"); this.getContentPane().add(jp3,"South"); jp.add(jp1);jp.add(jp2); jp1.add(jUserName);jp1.add(txtUserName); jp2.add(jUserPsd);jp2.add(txtUserPsd); jp3.add(okButton);jp3.add(regButton); txtUserName.setFont(f); txtUserPsd.setFont(f); txtUserPsd.setEchoChar('x'); txtUserName.setToolTipText("请输入用户名"); txtUserPsd.setToolTipText("请输入用户密码"); okButton.addActionListener(new SolveButtonEvent(1)); regButton.addActionListener(new SolveButtonEvent(2)); this.setResizable(false); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void showWindow() { this.pack(); this.setVisible(true); } public void closeWindow() { this.dispose(); } public String getUserName() { return this.txtUserName.getText().trim(); } public String getUserPassword() { return new String(this.txtUserPsd.getPassword()); } class SolveButtonEvent implements ActionListener { int select=0; public SolveButtonEvent(int select) { this.select=select; } public void actionPerformed(ActionEvent e) { //int x=(int) LoginFrame.this.txtUserName.getLocationOnScreen().getX(); //int y=(int) LoginFrame.this.txtUserName.getLocationOnScreen().getY(); String userName=LoginFrame.this.getUserName(); String userPsd=LoginFrame.this.getUserPassword(); int nameLength=userName.length(); int psdLength=userPsd.length(); if(select==1) { if(nameLength>0 && psdLength>0 ) { if(LoginFrame.this.query(userName,userPsd)==true) { LoginFrame.this.closeWindow(); //new Client(); } else { MyDialog md=new MyDialog(LoginFrame.this,"提示窗口","错误!","用户名或密码错误.\n登陆失败"); md.showDialog(); } } else { if(nameLength==0) { MyDialog md=new MyDialog(LoginFrame.this,"提示窗口","提示","用户名不能为空"); md.showDialog(); } else if(psdLength==0) { MyDialog md=new MyDialog(LoginFrame.this,"提示窗口","提示","用户密码不能为空"); md.showDialog(); } } } else if(select==2) { RegisterFrame rf=new RegisterFrame(LoginFrame.this); rf.showWindow(); } } } public boolean query(String userName,String userPsd) { Connection conn=null; PreparedStatement psm=null; ResultSet rs=null; String sql="select * from user_manager where name=? and psd=?"; boolean result=false; try { Class.forName("oracle.jdbc.driver.OracleDriver"); conn=DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xdf","scott","tiger"); psm=conn.prepareStatement(sql); psm.setString(1,userName); psm.setString(2,userPsd); rs=psm.executeQuery(); //rs结果集指向第一条记录的前一个位置 //如果第一条记录为空表示用户名或密码错误 if(rs.next()==true) { result=true; this.closeWindow(); } psm.close(); conn.close(); } catch(ClassNotFoundException e1){ e1.printStackTrace(); } catch(SQLException e2){ e2.printStackTrace(); } catch(Exception e3){ e3.printStackTrace(); } return result; } } package com.nio.client; 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.SocketChannel; import java.util.Iterator; import com.nio.user.ClientUser; import com.nio.user.ClientUserManager; import com.nio.user.UserData; public class NIOClient { private ClientUserManager cltManager = null; //通道管理器 private Selector selector; /** * 获得一个Socket通道,并对该通道做一些初始化的工作 * @param ip 连接的服务器的ip * @param port 连接的服务器的端口号 * @throws IOException */ public void initClient(String ip,int port) throws IOException { cltManager = ClientUserManager.instance(); // 获得一个Socket通道 SocketChannel channel = SocketChannel.open(); // 设置通道为非阻塞 channel.configureBlocking(false); // 获得一个通道管理器 this.selector = Selector.open(); // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调 //用channel.finishConnect();才能完成连接 channel.connect(new InetSocketAddress(ip,port)); //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。 channel.register(selector, SelectionKey.OP_CONNECT); } /** * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 * @throws IOException * @throws InterruptedException */ @SuppressWarnings("unchecked") public void listen() throws IOException, InterruptedException { // 轮询访问selector while (true) { // 选择一组可以进行I/O操作的事件,放在selector中,客户端的该方法不会阻塞, //这里和服务端的方法不一样,查看api注释可以知道,当至少一个通道被选中时, //selector的wakeup方法被调用,方法返回,而对于客户端来说,通道一直是被选中的 selector.select(); // 获得selector中选中的项的迭代器 Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 连接事件发生 if (key.isConnectable()) { SocketChannel channel = (SocketChannel) key .channel(); System.out.println("channel client ?" + channel); // 如果正在连接,则完成连接 if(channel.isConnectionPending()){ channel.finishConnect(); } //设置成非阻塞 channel.configureBlocking(false); //在这里可以给服务端发送信息哦 //channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes())); //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。 channel.register(this.selector, SelectionKey.OP_READ); //添加用户 UserData userData = new UserData(); userData.lineState = 1; userData.channel = channel; cltManager.addUser(userData); //连接成功发送一个通知消息 UIClient.sendUserInfoMsg(); } else if (key.isReadable()) { ClientUser cltUser = cltManager.getUser((SocketChannel)key.channel()); if (!cltUser.read()) { key.channel().close(); } } //删除已选的key,以防重复处理 ite.remove(); } } } } package com.nio.server; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.HashMap; import java.util.Iterator; import java.util.Vector; import com.nio.user.ServerUser; import com.nio.user.ServerUserManager; import com.nio.user.UserData; import com.ui.server.BootEndInterface; import com.ui.server.ServerBootFrame; public class NIOServer implements BootEndInterface { private ServerBootFrame serverFrame = new ServerBootFrame("服务器端"); private ServerUserManager userManager = null; HashMap<String, String> hmClient = new HashMap<String, String>(); Vector<String> client = new Vector<String>(); int count = 0; private static NIOServer nioServer = null; public NIOServer() { serverFrame.showFrame(); serverFrame.bootingServer(this); serverFrame.endingServer(this); serverFrame.closeWindow(this); nioServer = this; } // 通道管理器 private Selector selector; /** * 获得一个ServerSocket通道,并对该通道做一些初始化的工作 * * @param port * 绑定的端口号 * @throws IOException */ public void initServer(int port) throws IOException { serverFrame.appendStringTojtaState("服务器(NIO机制)启动中......\n"); // 获得一个ServerSocket通道 ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 设置通道为非阻塞 serverChannel.configureBlocking(false); // 将该通道对应的ServerSocket绑定到port端口 serverChannel.socket().bind(new InetSocketAddress(port)); // 获得一个通道管理器 this.selector = Selector.open(); // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后, // 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。 serverChannel.register(selector, SelectionKey.OP_ACCEPT); //System.out.println("serverChannel 0?" + serverChannel); } /** * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 * * @throws IOException */ @SuppressWarnings("unchecked") public void listen() throws IOException { // System.out.println("服务端启动成功!"); serverFrame.appendStringTojtaState("服务器(NIO机制)启动成功......\n"); // 轮询访问selector while (true) { // 当注册的事件到达时,方法返回;否则,该方法会一直阻塞 selector.select(); // 获得selector中选中的项的迭代器,选中的项为注册的事件 Iterator<?> ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); // 客户端请求连接事件 if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key .channel(); // 获得和客户端连接的通道 SocketChannel channel = server.accept(); System.out.println("channel A?" + channel); // 设置成非阻塞 channel.configureBlocking(false); // 在这里可以给客户端发送信息哦 // channel.write(ByteBuffer.wrap(new // String("向客户端发送了一条信息").getBytes())); // 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。 channel.register(this.selector, SelectionKey.OP_READ); // 添加一个玩家对象 UserData userData = new UserData(); userData.lineState = 1; userData.channel = channel; userManager.addUser(userData); } else if (key.isReadable()) { ServerUser serverUser = userManager .getUser((SocketChannel) key.channel()); // 读取数据失败 if (!serverUser.read()) { serverUser.clean(); key.channel().close(); } } // 删除已选的key,以防重复处理 ite.remove(); } } } /** * 启动服务端测试 * * @throws IOException */ public static void main(String[] args) throws IOException { new NIOServer(); } @Override public void boot() { userManager = ServerUserManager.instance(); userManager.initUsers(); serverFrame.appendStringTojtaState("创建玩家内存数据对象成功...\n"); new Thread(new Runnable() { @Override public void run() { try { NIOServer.this.initServer(5555); NIOServer.this.listen(); } catch (Exception e) { serverFrame.appendStringTojtaState("服务器启动失败......\n"); } } }).start(); //服务端主逻辑处理 new Thread(new Runnable() { @Override public void run() { try { while (true) { Thread.sleep(1); userManager.run(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }).start(); } @Override public void end() { if (selector != null) { try { selector.close(); selector = null; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.exit(0); } public void solveMsg(String message) { try { System.out.println(message); // 对消息进行分析 String msg[] = message.split("#"); if (msg[0].equals("AddUser") == true) { hmClient.put(msg[1], msg[2]); sendUsersToOneUser(msg[1]); if (likeThisName(msg[1]))// 如果出现同名用户,则在用户名后面添加数字来区分 { msg[1] = msg[1] + count; } client.add(msg[1]); serverFrame.appendStringTojtaState(msg[1] + "上线...\n"); sendMessageToAllUsers( "AddUser" + "#" + msg[1] + "#" + hmClient.get(msg[1]), msg[1]); } else if (msg[0].equals("UserQuit") == true) { sendMessageToAllUsers("UserQuit" + "#" + msg[1] + "#" + hmClient.get(msg[1]), msg[1]); serverFrame.appendStringTojtaState(msg[1] + "离线...\n"); deleteKeyValue(msg[1]); client.remove(msg[1]); // 应该删除vecUser容器中的对应的Socket对象 } else if (msg[0].equals("Message") == true) { // 如果将消息发送给特定用户 if (msg[1].equals("One") == true) { sendMessageToOneUser("Message" + "#" + msg[2] + "#" + msg[6], msg[4]); } else// 将消息发送给全部用户 { sendMessageToAllUsers("Message" + "#" + msg[2] + "#" + msg[4], msg[2]); } } } catch (Exception e) { e.printStackTrace(); } } public void sendMessageToAllUsers(String msg, String notSendToThisUserName) throws UnsupportedEncodingException { ServerUser lstUsers[] = userManager.getUsers(); for (int i = 0; i < lstUsers.length; i++) { if (lstUsers[i].channel != null) { String address = lockOut("" + lstUsers[i].channel.socket().getRemoteSocketAddress()); if ( !address.equals(hmClient.get(notSendToThisUserName)) ) { lstUsers[i].write(msg.getBytes("utf-8")); } } } } public void sendMessageToOneUser(String msg, String sendToThisUserName) throws UnsupportedEncodingException { ServerUser lstUsers[] = userManager.getUsers(); for (int i = 0; i < lstUsers.length; i++) { if (lstUsers[i].channel != null) { String address = lockOut("" + lstUsers[i].channel.socket().getRemoteSocketAddress()); if ( address.equals(hmClient.get(sendToThisUserName)) ) { lstUsers[i].write(msg.getBytes("utf-8")); break; } } } } // 方法完成将在线用户添给用户的下拉表中 public void sendUsersToOneUser(String newUserName) throws UnsupportedEncodingException { Iterator<String> it = client.listIterator(); for (; it.hasNext();) { String name = it.next(); String ipAndPort = hmClient.get(name); sendMessageToOneUser("OnlyAddUser" + "#" + name + "#" + ipAndPort, newUserName); } } // 将键值对添加到hmClient哈希表中 public void createKeyValue(String userName, String socketAddress) { hmClient.put(userName, socketAddress); } // 从哈希表中删除指定键值对(键为:userName); public void deleteKeyValue(String userName) { hmClient.remove(userName); } // 将字符串前面的斜杠去掉 public String lockOut(String socketAddress) { return socketAddress.substring(1, socketAddress.length()); } // 如果client容器中存放的用户名出现相似用户,则用户名后面添加一个数字 public boolean likeThisName(String thisName) { count = 0; for (Iterator it = client.listIterator(); it.hasNext();) { String temp = (String) it.next(); if (temp.startsWith(thisName) == true) { count++; } } if (count > 0) return true; else return false; } public static void handleMessage(String msg) { // System.out.println("服务端收到信息:" + msg); nioServer.solveMsg(msg); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值