Java NIO实现的C/S模式多人聊天工具

小弟初学NIO,做了个控制台聊天工具,不知道代码写的如何,望大神们批评指点。

服务器端,两个线程,一个处理客户端请求和转发消息,另一个处理服务器管理员指令,上代码:

package kindz.onlinechat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class Server {

    private static List<SocketChannel> clientList = new LinkedList<SocketChannel>();// 客户端列表
    private static Selector clientManager = null;// 通道管理器
    private static ServerSocketChannel server = null;// 服务器通道
    private static ByteBuffer buff = ByteBuffer.allocate(1500);// 缓冲器
    private static int port = 3333;

    public static void main(String[] args) {
	if (args.length > 0) {
	    try {
		port = Integer.parseInt(args[0]);
	    } catch (NumberFormatException e) {
		System.out.println("端口号只能为数字");
		return;
	    }
	}

	try {
	    // 初始化失败直接退出
	    if (!init())
		return;

	    while (clientManager.isOpen()) {
		select();

		// 获取就绪的key列表
		Set<SelectionKey> keys = clientManager.selectedKeys();

		// 遍历事件并处理
		Iterator<SelectionKey> it = keys.iterator();
		while (it.hasNext()) {
		    SelectionKey key = it.next();
		    // 判断key是否有效
		    if (!key.isValid()) {
			it.remove();// 要移除
			continue;
		    }

		    if (key.isAcceptable()) {// 有请求
			accept(key);
		    } else if (key.isReadable()) {// 有数据
			broadcast(key);
		    }

		    it.remove();
		}
	    }
	} catch (ClosedSelectorException | CancelledKeyException e) {// 一定是其他线程关闭了管理器
	} finally {
	    try {
		if (clientManager != null)
		    clientManager.close();
	    } catch (IOException e) {
	    }

	    try {
		if (server != null)
		    server.close();
	    } catch (IOException e) {
	    }

	    closeAll();
	    System.out.println("服务器已停止");
	}
    }

    // 初始化

    private static boolean init() {
	System.out.println("服务器启动中...");

	try {
	    // 获取管理器
	    clientManager = Selector.open();
	} catch (IOException e) {
	    System.out.println("服务器启动失败,原因:通道管理器无法获取");
	    return false;
	}

	try {
	    // 打开通道
	    server = ServerSocketChannel.open();
	} catch (IOException e) {
	    System.out.println("服务器启动失败,原因:socket通道无法打开");
	    return false;
	}

	try {
	    // 绑定端口
	    server.socket().bind(new InetSocketAddress(port));
	} catch (IOException e) {
	    System.out.println("服务器启动失败,原因:端口号不可用");
	    return false;
	}

	try {
	    // 设置成非阻塞模式
	    server.configureBlocking(false);
	} catch (IOException e) {
	    System.out.println("服务器启动失败,原因:非阻塞模式切换失败");
	    return false;
	}

	try {
	    // 注册到管理器中,只监听接受连接事件
	    server.register(clientManager, SelectionKey.OP_ACCEPT);
	} catch (ClosedChannelException e) {
	    System.out.println("服务器启动失败,原因:服务器通道已关闭");
	    return false;
	}

	Thread service = new Thread(new ServerService(clientManager));// 提供管理员指令服务线程
	service.setDaemon(true);// 设置为后台线程
	service.start();

	System.out.println("服务器启动成功");
	return true;
    }

    // 等待事件
    private static void select() {
	try {
	    // 等待事件
	    clientManager.select();
	} catch (IOException e) {
	    // 忽略未知的异常
	}
    }

    // 此方法获取请求的socket通道并添加到客户端列表中,当然还要注册到管理器中
    private static void accept(SelectionKey key) {
	SocketChannel socket = null;

	try {
	    // 接受请求的连接
	    socket = ((ServerSocketChannel) key.channel()).accept();
	} catch (IOException e) {// 连接失败
	}

	if (socket == null)
	    return;

	SocketAddress address = null;

	try {
	    address = socket.getRemoteAddress();
	    // 注册
	    socket.configureBlocking(false);
	    socket.register(clientManager, SelectionKey.OP_READ);
	} catch (ClosedChannelException e) {// 注册失败
	    try {
		if (socket != null)
		    socket.close();
	    } catch (IOException e1) {
	    }
	    return;
	} catch (IOException e) {
	    try {
		if (socket != null)
		    socket.close();
	    } catch (IOException e1) {
	    }
	    return;
	}
	// 添加到客户端列表中
	clientList.add(socket);
	System.out.println("主机" + address + "连接到服务器");
    }

    // 此方法接收数据并发送个客户端列表的每一个人
    private static void broadcast(SelectionKey key) {
	SocketChannel sender = (SocketChannel) key.channel();
	// 方法结束不清理
	buff.clear();

	int status = -1;
	try {
	    // 读取数据
	    status = sender.read(buff);
	} catch (IOException e) {// 未知的io异常
	    status = -1;
	}

	if (status <= 0) {// 异常断开连接,并移除此客户端
	    remove(sender);
	    return;
	}

	// 发送给每一个人
	for (SocketChannel client : clientList) {
	    // 除了他或她自己
	    if (client == sender)
		continue;
	    buff.flip();
	    try {
		client.write(buff);
	    } catch (IOException e) {// 发送失败,移除此客户端
		remove(client);
	    }
	}
    }

    private static void remove(SocketChannel client) {
	SocketAddress address = null;// 存储主机地址信息

	clientList.remove(client);// 从列表中移除
	
	try {
	    address = client.getRemoteAddress();//获取客户端地址信息
	} catch (IOException e1) {
	}
	
	try {
	    client.close();// 关闭连接
	} catch (IOException e1) {
	}
	client.keyFor(clientManager).cancel();// 反注册
	System.out.println("与主机" + address + "断开连接");
    }

    // 关闭列表中全部通道
    private static void closeAll() {
	for (SocketChannel client : clientList) {
	    try {
		if (client != null)
		    client.close();
	    } catch (IOException e) {
	    }
	}
    }
}
客户端也是两个线程,一个循环等待接收消息,另一个处理用户输入以及发送:

package kindz.onlinechat;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class Client implements Runnable {

    private static String ip = null;
    private static String name = null;
    private static String serverHost="127.0.0.1";//服务器地址
    private static int port=3333;//服务器端口号
    private static SocketChannel socket = null;// 与服务器连接通道

    public static void main(String[] args) {
	if(args.length>0){
	    
	    if (args[0].indexOf(':') == -1) {
		System.err.println("目标地址格式不正确");
		return;
	    }

	    serverHost = args[0].split(":")[0];
	    try {
		port = Integer.parseInt(args[0].split(":")[1]);
	    } catch (NumberFormatException e) {
		System.err.println("端口号只能为数字");
		return;
	    }
	}
	
	try {
	    // 初始化失败退出程序
	    if (!init())
		return;

	    ByteBuffer buff = ByteBuffer.allocate(1500);// 字节缓冲器

	    while (socket.read(buff) != -1) {// 读取信息
		String msg = new String(buff.array(), 0, buff.position());// 转成字符串
		buff.clear();// 清理
		System.out.println(msg);
	    }

	} catch (IOException e) {
	    System.out.println("与服务器断开连接");
	} finally {
	    close();
	    System.out.println("程序已退出");
	}
    }

    // 输入线程
    @SuppressWarnings("resource")
    public void run() {
	try {
	    Scanner sc = new Scanner(System.in);
	    // 循环等待输入
	    while (sc.hasNextLine()) {
		String msg = sc.nextLine();

		if (".exit".equals(msg)){
		    socket.write(ByteBuffer.wrap((name+"-"+ip+"下线了").getBytes()));// 发送下线信息
		    break;
		}
		   

		msg = name + "-" + ip + ":" + msg;
		socket.write(ByteBuffer.wrap(msg.getBytes()));
	    }
	} catch (IOException e) {
	    System.out.println("与服务器断开连接");
	} finally {
	    close();
	}
    }

    // 初始化程序
    private static boolean init() {
	System.out.println("正在连接至服务器...");
	try {
	    socket = SocketChannel
		    .open(new InetSocketAddress(serverHost, port));// 打开通道
	} catch (IOException e) {
	    System.out.println("无法连接到服务器");
	    return false;
	}
	System.out.println("已连接至服务器");

	try {
	    InetAddress address = InetAddress.getLocalHost();// 获取本机网络信息
	    ip = address.getHostAddress();// 本机ip
	    name = address.getHostName();// 主机名
	    socket.write(ByteBuffer.wrap((name+"-"+ip+"上线了").getBytes()));// 发送上线信息
	} catch (IOException e) {
	    System.out.println("网络异常");
	    return false;
	}

	Thread thread = new Thread(new Client());
	thread.setDaemon(true);// 设置后台线程
	thread.start();
	return true;
    }

    // 关闭通道
    private static void close() {
	try {
	    if (socket != null)
		socket.close();
	} catch (IOException e) {
	}
    }
}
写完感觉还是挺简单的,但是本人一直从事java web开发,异常处理做的比较少,不知道我这个处理的怎么样。

由于没有那么多好基友帮忙测试,我还写了虚拟客户端来模拟好基友:

package kindz.onlinechat;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class VClient implements Runnable {

    private String ip = null;
    private String name = null;
    private String serverHost = "127.0.0.1";// 服务器ip地址
    private int port = 3333;// 服务器端口号
    private SocketChannel socket = null;// 与服务器连接通道
    private static String[] msgs = {
	    "大家好",
	    "好困啊",
	    "今天该干什么啊",
	    "我这任务好多,先不聊了",
	    "jQuery是继prototype之后又一个优秀的Javascript库。它是轻量级的js库 ,它兼容CSS3,还兼容各种浏览器(IE 6.0+, FF 1.5+, Safari 2.0+, Opera 9.0+),jQuery2.0及后续版本将不再支持IE6/7/8浏览器。jQuery使用户能更方便地处理HTML(标准通用标记语言下的一个应用)、events、实现动画效果,并且方便地为网站提供AJAX交互。jQuery还有一个比较大的优势是,它的文档说明很全,而且各种应用也说得很详细,同时还有许多成熟的插件可供选择。jQuery能够使用户的html页面保持代码和html内容分离,也就是说,不用再在html里面插入一堆js来调用命令了,只需要定义id即可[8]。",
	    "谁那有咖啡", "谁有时间帮我去取个快递", ".exit" };
    private Random random=new Random();

    public VClient(String serverHost, int port) {
	this.serverHost = serverHost;
	this.port = port;
    }

    public void run() {
	try {
	    // 初始化失败退出程序
	    if (!init())
		return;

	    ByteBuffer buff = ByteBuffer.allocate(1500);// 字节缓冲器

	    while (!Thread.interrupted()&&socket.read(buff) != -1) {// 读取信息,中断退出
		String msg = new String(buff.array(), 0, buff.position());// 转成字符串
		buff.clear();// 清理
		System.out.println(msg);
	    }

	} catch (IOException e) {
	    System.out.println("与服务器断开连接");
	} finally {
	    close();
	    System.out.println("程序已退出");
	}
    }

    // 初始化程序
    private boolean init() {
	System.out.println("正在连接至服务器...");
	try {
	    socket = SocketChannel
		    .open(new InetSocketAddress(serverHost, port));// 打开通道
	} catch (IOException e) {
	    System.out.println("无法连接到服务器");
	    return false;
	}
	System.out.println("已连接至服务器");

	try {
	    InetAddress address = InetAddress.getLocalHost();// 获取本机网络信息
	    ip = address.getHostAddress();// 本机ip
	    name = address.getHostName();// 主机名
	    socket.write(ByteBuffer.wrap((name + "-" + ip + "上线了").getBytes()));// 发送上线信息
	} catch (IOException e) {
	    System.out.println("网络异常");
	    return false;
	}

	Thread thread = new Thread(new Daemon());// 私有内部类
	thread.setDaemon(true);// 设置后台线程
	thread.start();
	return true;
    }

    // 关闭通道
    private void close() {
	try {
	    if (socket != null)
		socket.close();
	} catch (IOException e) {
	}
    }

    // 私有内部类、守护线程、输出用
    private class Daemon implements Runnable {

	public void run() {
	    try {
		// 自动循环发送消息
		while (true) {
		    String msg = msgs[random.nextInt(msgs.length)];// 随便拿一个写好的信息

		    if (".exit".equals(msg)) {
			socket.write(ByteBuffer.wrap((name + "-" + ip + "下线了")
				.getBytes()));// 发送下线信息
			break;
		    }

		    msg = name + "-" + ip + ":" + msg;
		    socket.write(ByteBuffer.wrap(msg.getBytes()));

		    TimeUnit.SECONDS.sleep(random.nextInt(9) + 2);//模拟用户输入过程,2-10秒,平均6秒发一次
		}
	    } catch (IOException e) {
		System.out.println("与服务器断开连接");
	    } catch (InterruptedException e) {
		System.out.println("与服务器断开连接");
	    } finally {
		close();
	    }
	}
    }

}
虚拟客户端只是个线程,我给它配了个管理器:

package kindz.onlinechat;

import java.util.Random;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class VManager implements Runnable {

    private static String serverHost = "127.0.0.1";
    private static int port = 3333;
    private static int num = 3;// 初始化虚拟客户端数目
    private static int min = 5;// 最小虚拟客户端数目
    private static int max = 15;// 最大虚拟客户端数目
    private static Random random=new Random();

    @SuppressWarnings("resource")
    public static void main(String[] args) {

	if (args.length > 0) {

	    if (args[0].indexOf(':') == -1) {
		System.err.println("目标地址格式不正确");
		return;
	    }

	    serverHost = args[0].split(":")[0];
	    try {
		port = Integer.parseInt(args[0].split(":")[1]);
	    } catch (NumberFormatException e) {
		System.err.println("端口号只能为数字");
		return;
	    }
	}

	if (args.length > 1) {
	    try {
		num = Integer.parseInt(args[1]);
	    } catch (NumberFormatException e) {
		System.err.println("初始化数目只能为数字");
		return;
	    }
	}

	if (args.length > 2) {
	    try {
		min = Integer.parseInt(args[2]);
	    } catch (NumberFormatException e) {
		System.err.println("最小数目只能为数字");
		return;
	    }
	}

	if (args.length > 3) {
	    try {
		max = Integer.parseInt(args[3]);
	    } catch (NumberFormatException e) {
		System.err.println("最大数目只能为数字");
		return;
	    }
	}

	if (max < num) {
	    System.err.println("初始化数量不能大于最大数量");
	    return;
	}
	
	if (max < min) {
	    System.err.println("最小数量不能大于最大数量");
	    return;
	}
	
	Thread manager = new Thread(new VManager());
	manager.start();

	Scanner sc = new Scanner(System.in);

	String arg = null;// 指令
	while (sc.hasNextLine()) {
	    arg = sc.nextLine();// 输入指令

	    if ("shutdown".equals(arg)) {
		manager.interrupt();
		break;
	    } else {
		System.out.println("未知的指令");
	    }
	}
    }

    public void run() {
	ExecutorService manager = Executors.newFixedThreadPool(max);// 线程池管理器

	// 初始化几个客户端
	for (int i = 0; i < num; i++) {
	    manager.execute(new VClient(serverHost, port));
	}

	try {
	    int interval=(int)(2*48000.0/min)+1;//用户上线间隔时间范围
	    while (!Thread.interrupted()) {
		TimeUnit.MILLISECONDS.sleep(random.nextInt(interval));// 用户大概48秒会下线,尽量保持最少用户个数
		manager.execute(new VClient(serverHost, port));
	    }
	} catch (InterruptedException e) {
	} finally {
	    System.out.println("正在停止所有客户端...");
	    manager.shutdownNow();// 中断所有客户端
	}
    }
}
虚拟客户端大概平均6秒发一次消息,每次下线的几率是1/8,所以虚拟客户端大概在上线48秒左右的时候会下线,因此想要保证(只能是尽量保证)最小虚拟客户端数量,只要保证48秒内上线固定数量的用户即可,不过在这里,虚拟客户端增加的频率也是随机的,感觉更真实些。

本人想转Java底层的工作,因此在努力学习中,望大神们能够为小弟点出不对的地方。

想学习Java NIO的童鞋也可以借鉴一下我的代码。

愿与CSDN上的Coder们一起在技术的道路上飞奔。奋斗

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); } }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值