java网络socket编程(七)之java中NIO实现聊天系统的群聊功能

来源:互联网 发布:网络教育 档案 公考 编辑:程序博客网 时间:2024/06/15 08:32

1.服务器端代码

<span style="font-family:Microsoft YaHei;">import java.io.IOException;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.nio.ByteBuffer;import java.nio.channels.Channel;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.nio.charset.Charset;import java.util.HashSet;import java.util.Iterator;import java.util.Set;/** * 聊天室服务器 * 网络多客户端聊天室 * 功能1: 客户端通过Java NIO连接到服务端,支持多客户端的连接 * 功能2:客户端初次连接时,服务端提示输入昵称,如果昵称已经有人使用,提示重新输入,如果昵称唯一,则登录成功,之后发送消息都需要按照规定格式带着昵称发送消息 * 功能3:客户端登录后,发送已经设置好的欢迎信息和在线人数给客户端,并且通知其他客户端该客户端上线 * 功能4:服务器收到已登录客户端输入内容,转发至其他登录客户端。 * @author 1 * @date Aug 22, 2016 4:02:01 PM */public class ChatRoomServer {/** 选择器 */private Selector selector;/*****端口号*****/private final static int PORT=9900;/*******在线统计人名或人数********/private HashSet<String> online = new HashSet<String>();/****编码*****/private Charset charset =  Charset.forName("UTF-8");/****用户存在提示信息*****/private static String USER_EXIST = "system message: user exist, please change a name";    /****相当于自定义协议格式,与客户端协商好*****/    private static String USER_CONTENT_SPILIT = "#@#";        public static void main(String[] args) {try {new ChatRoomServer().init();} catch (IOException e) {e.printStackTrace();}}            /**     * 初始化服务器     * @author 1     * @throws IOException      */    public void init() throws IOException{        //打开选择器    this.selector = Selector.open();    // 开启服务器端通道,并指定端口号ServerSocketChannel server = ServerSocketChannel.open();ServerSocket serverSocket = server.socket();InetSocketAddress address = new InetSocketAddress(PORT);serverSocket.bind(address);server.configureBlocking(false);//将选择器注册到服务器通道上server.register(selector, SelectionKey.OP_ACCEPT);System.out.println("server is linstening...");//等待客户端的连接while(true){int nums = this.selector.select();if (nums<=0) {continue;}//存在连接Set<SelectionKey> selectionKeys = this.selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();while (iterator.hasNext()) {//得到当前的选择键SelectionKey key = iterator.next();iterator.remove();//处理当前的选择键dealWithSelectionKey(server,key);}}    }/** *  * @param server * @param key * @author 1 * @throws IOException  */private void dealWithSelectionKey(ServerSocketChannel server, SelectionKey key) throws IOException {if (key.isAcceptable()) {//接收客户端SocketChannel sChannel = server.accept();//设置非阻塞sChannel.configureBlocking(false);//注册选择器,并设置为读取模式,收到一个连接请求,然后起一个SocketChannel,并注册到selector上,之后这个连接的数据,就由这个socketchannel处理。sChannel.register(selector, SelectionKey.OP_READ);//将此对应的channel设置为准备接收其他客户端的请求key.interestOps(SelectionKey.OP_ACCEPT);System.out.println("Server is listening from client :" + sChannel.socket().getRemoteSocketAddress());sChannel.write(charset.encode("Please input your name:"));}//处理来自客户端的数据读取请求else if (key.isReadable()) {//得到该key对应的channel,其中有数据需要读取SocketChannel sc = (SocketChannel) key.channel();StringBuffer content = new StringBuffer();ByteBuffer buffer = ByteBuffer.allocate(1024);try {//得到客户端传过来的消息while(sc.read(buffer)>0){buffer.flip();content.append(charset.decode(buffer));}//将此对应的channel设置为准备下一次接受数据key.interestOps(SelectionKey.OP_READ);} catch (Exception e) {e.printStackTrace();key.cancel();sc.close();}//如果内容不为空if (content.length()>0) {//拆分规则String[] msgArr = content.toString().split(USER_CONTENT_SPILIT);//注册名字if (msgArr!=null && msgArr.length==1) {//用户已经存在,则直接返回if (online.contains(msgArr[0])) {sc.write(charset.encode(USER_EXIST));}else {String name = msgArr[0];online.add(name);int onlineNum = this.onlineTotal();String msg = "welcome "+name+" to chat room,current online people num is:"+onlineNum;//通知所有的人broadCast(selector, null, msg);}}//聊天内容else if (msgArr!=null && msgArr.length>1) { String name = msgArr[0];                    String message = content.substring(name.length()+USER_CONTENT_SPILIT.length());                    message = name + " say " + message;                    if(online.contains(name)) {                                            //不回发给发送此内容的客户端                    broadCast(selector, sc, message);                    }}}} }/** * 通知所有人 * @param selector 选择器 * @param sc 不通知的客户端 * @param msg 消息 * @author 1 * @throws IOException  */private void broadCast(Selector selector, SocketChannel except, String msg) throws IOException {for(SelectionKey key : selector.keys()){Channel channel = key.channel();if (channel instanceof SocketChannel && channel != except) {SocketChannel socketChannel = (SocketChannel) channel;socketChannel.write(charset.encode(msg));}}}/** * 得到在线总人数 * @return * @author 1 */private int onlineTotal() {int num=0;for(SelectionKey key : this.selector.keys()){Channel targetchannel = key.channel();if (targetchannel instanceof SocketChannel) {num++;}}return num;}        }</span>

2.客户端代码

<span style="font-family:Microsoft YaHei;">package com.hq.io.socket;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.nio.charset.Charset;import java.util.Iterator;import java.util.Scanner;import java.util.Set;/** *  * @author 1 * @date Aug 22, 2016 4:02:01 PM */public class ChatRoomClient {private Selector selector = null;static final int port = 9900;private Charset charset = Charset.forName("UTF-8");private SocketChannel sc = null;private String name = "";private static String USER_EXIST = "system message: user exist, please change a name";private static String USER_CONTENT_SPILIT = "#@#";public void init() throws IOException {selector = Selector.open();// 连接远程主机的IP和端口sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", port));sc.configureBlocking(false);sc.register(selector, SelectionKey.OP_READ);// 开辟一个新线程来读取从服务器端的数据new Thread(new ClientThread()).start();// 在主线程中 从键盘读取数据输入到服务器端Scanner scan = new Scanner(System.in);while (scan.hasNextLine()) {String line = scan.nextLine();if ("".equals(line))continue; // 不允许发空消息if ("".equals(name)) {name = line;line = name + USER_CONTENT_SPILIT;} else {line = name + USER_CONTENT_SPILIT + line;}sc.write(charset.encode(line));// sc既能写也能读,这边是写}}private class ClientThread implements Runnable {public void run() {try {while (true) {int readyChannels = selector.select();if (readyChannels == 0)continue;Set selectedKeys = selector.selectedKeys(); // 可以通过这个方法,知道可用通道的集合Iterator keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey sk = (SelectionKey) keyIterator.next();keyIterator.remove();dealWithSelectionKey(sk);}}} catch (IOException io) {}}private void dealWithSelectionKey(SelectionKey sk) throws IOException {if (sk.isReadable()) {// 使用 NIO 读取 Channel中的数据,这个和全局变量sc是一样的,因为只注册了一个SocketChannel// sc既能写也能读,这边是读SocketChannel sc = (SocketChannel) sk.channel();ByteBuffer buff = ByteBuffer.allocate(1024);String content = "";while (sc.read(buff) > 0) {buff.flip();content += charset.decode(buff);}// 若系统发送通知名字已经存在,则需要换个昵称if (USER_EXIST.equals(content)) {name = "";}System.out.println(content);sk.interestOps(SelectionKey.OP_READ);}}}public static void main(String[] args) throws IOException {new ChatRoomClient().init();}}</span>


1 0
原创粉丝点击