Java NIO实战之聊天室功能详解
本文实例讲述了JavaNIO实战之聊天室功能。分享给大家供大家参考,具体如下:
在工作之余花了两个星期看完了《JavaNIO》,总体来说这本书把NIO写的很详细,没有过多的废话,讲的都是重点,只是翻译的中文版看的确实吃力,英文水平太低也没办法,总算也坚持看完了。《JavaNIO》这本书的重点在于第四章讲解的“选择器”,要理解透还是要反复琢磨推敲;愚钝的我花了大概3天的时间才将NIO的选择器机制理解透并能较熟练的运用,于是便写了这个聊天室程序。
下面直接上代码,jdk1.5以上经过测试,可以支持多人同时在线聊天;
将以下代码复制到项目中便可运行,源码下载地址:聊天室源码。
一、服务器端
packagecom.chat.server;
importjava.io.IOException;
importjava.net.InetSocketAddress;
importjava.nio.ByteBuffer;
importjava.nio.channels.SelectionKey;
importjava.nio.channels.Selector;
importjava.nio.channels.ServerSocketChannel;
importjava.nio.channels.SocketChannel;
importjava.text.SimpleDateFormat;
importjava.util.Date;
importjava.util.Iterator;
importjava.util.Vector;
/**
*聊天室:服务端
*@authorzing
*
*/
publicclassChatServerimplementsRunnable{
//选择器
privateSelectorselector;
//注册ServerSocketChannel后的选择键
privateSelectionKeyserverKey;
//标识是否运行
privatebooleanisRun;
//当前聊天室中的用户名称列表
privateVectorunames;
//时间格式化器
SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");
/**
*构造函数
*@paramport服务端监控的端口号
*/
publicChatServer(intport){
isRun=true;
unames=newVector();
init(port);
}
/**
*初始化选择器和服务器套接字
*
*@paramport服务端监控的端口号
*/
privatevoidinit(intport){
try{
//获得选择器实例
selector=Selector.open();
//获得服务器套接字实例
ServerSocketChannelserverChannel=ServerSocketChannel.open();
//绑定端口号
serverChannel.socket().bind(newInetSocketAddress(port));
//设置为非阻塞
serverChannel.configureBlocking(false);
//将ServerSocketChannel注册到选择器,指定其行为为"等待接受连接"
serverKey=serverChannel.register(selector,SelectionKey.OP_ACCEPT);
printInfo("serverstarting...");
}catch(IOExceptione){
e.printStackTrace();
}
}
@Override
publicvoidrun(){
try{
//轮询选择器选择键
while(isRun){
//选择一组已准备进行IO操作的通道的key,等于1时表示有这样的key
intn=selector.select();
if(n>0){
//从选择器上获取已选择的key的集合并进行迭代
Iteratoriter=selector.selectedKeys().iterator();
while(iter.hasNext()){
SelectionKeykey=iter.next();
//若此key的通道是等待接受新的套接字连接
if(key.isAcceptable()){
//记住一定要remove这个key,否则之后的新连接将被阻塞无法连接服务器
iter.remove();
//获取key对应的通道
ServerSocketChannelserverChannel=(ServerSocketChannel)key.channel();
//接受新的连接返回和客户端对等的套接字通道
SocketChannelchannel=serverChannel.accept();
if(channel==null){
continue;
}
//设置为非阻塞
channel.configureBlocking(false);
//将这个套接字通道注册到选择器,指定其行为为"读"
channel.register(selector,SelectionKey.OP_READ);
}
//若此key的通道的行为是"读"
if(key.isReadable()){
readMsg(key);
}
//若次key的通道的行为是"写"
if(key.isWritable()){
writeMsg(key);
}
}
}
}
}catch(IOExceptione){
e.printStackTrace();
}
}
/**
*从key对应的套接字通道上读数据
*@paramkey选择键
*@throwsIOException
*/
privatevoidreadMsg(SelectionKeykey)throwsIOException{
//获取此key对应的套接字通道
SocketChannelchannel=(SocketChannel)key.channel();
//创建一个大小为1024k的缓存区
ByteBufferbuffer=ByteBuffer.allocate(1024);
StringBuffersb=newStringBuffer();
//将通道的数据读到缓存区
intcount=channel.read(buffer);
if(count>0){
//翻转缓存区(将缓存区由写进数据模式变成读出数据模式)
buffer.flip();
//将缓存区的数据转成String
sb.append(newString(buffer.array(),0,count));
}
Stringstr=sb.toString();
//若消息中有"open_",表示客户端准备进入聊天界面
//客户端传过来的数据格式是"open_zing",表示名称为zing的用户请求打开聊天窗体
//用户名称列表有更新,则应将用户名称数据写给每一个已连接的客户端
if(str.indexOf("open_")!=-1){//客户端连接服务器
Stringname=str.substring(5);
printInfo(name+"online");
unames.add(name);
//获取选择器已选择的key并迭代
Iteratoriter=selector.selectedKeys().iterator();
while(iter.hasNext()){
SelectionKeyselKey=iter.next();
//若不是服务器套接字通道的key,则将数据设置到此key中
//并更新此key感兴趣的动作
if(selKey!=serverKey){
selKey.attach(unames);
selKey.interestOps(selKey.interestOps()|SelectionKey.OP_WRITE);
}
}
}elseif(str.indexOf("exit_")!=-1){//客户端发送退出命令
Stringuname=str.substring(5);
//删除此用户名称
unames.remove(uname);
//将"close"字符串附加到key
key.attach("close");
//更新此key感兴趣的动作
key.interestOps(SelectionKey.OP_WRITE);
//获取选择器上的已选择的key并迭代
//将更新后的名称列表数据附加到每个套接字通道key上,并重设key感兴趣的操作
Iteratoriter=key.selector().selectedKeys().iterator();
while(iter.hasNext()){
SelectionKeyselKey=iter.next();
if(selKey!=serverKey&&selKey!=key){
selKey.attach(unames);
selKey.interestOps(selKey.interestOps()|SelectionKey.OP_WRITE);
}
}
printInfo(uname+"offline");
}else{//读取客户端聊天消息
Stringuname=str.substring(0,str.indexOf("^"));
Stringmsg=str.substring(str.indexOf("^")+1);
printInfo("("+uname+")说:"+msg);
StringdateTime=sdf.format(newDate());
Stringsmsg=uname+""+dateTime+"\n"+msg+"\n";
Iteratoriter=selector.selectedKeys().iterator();
while(iter.hasNext()){
SelectionKeyselKey=iter.next();
if(selKey!=serverKey){
selKey.attach(smsg);
selKey.interestOps(selKey.interestOps()|SelectionKey.OP_WRITE);
}
}
}
}
/**
*写数据到key对应的套接字通道
*@paramkey
*@throwsIOException
*/
privatevoidwriteMsg(SelectionKeykey)throwsIOException{
SocketChannelchannel=(SocketChannel)key.channel();
Objectobj=key.attachment();
//这里必要要将key的附加数据设置为空,否则会有问题
key.attach("");
//附加值为"close",则取消此key,并关闭对应通道
if(obj.toString().equals("close")){
key.cancel();
channel.socket().close();
channel.close();
return;
}else{
//将数据写到通道
channel.write(ByteBuffer.wrap(obj.toString().getBytes()));
}
//重设此key兴趣
key.interestOps(SelectionKey.OP_READ);
}
privatevoidprintInfo(Stringstr){
System.out.println("["+sdf.format(newDate())+"]->"+str);
}
publicstaticvoidmain(String[]args){
ChatServerserver=newChatServer(19999);
newThread(server).start();
}
}
二、客户端
1、服务类,用于与服务端交互
packagecom.chat.client;
importjava.io.IOException;
importjava.net.InetSocketAddress;
importjava.nio.ByteBuffer;
importjava.nio.channels.SocketChannel;
publicclassClientService{
privatestaticfinalStringHOST="127.0.0.1";
privatestaticfinalintPORT=19999;
privatestaticSocketChannelsc;
privatestaticObjectlock=newObject();
privatestaticClientServiceservice;
publicstaticClientServicegetInstance(){
synchronized(lock){
if(service==null){
try{
service=newClientService();
}catch(IOExceptione){
e.printStackTrace();
}
}
returnservice;
}
}
privateClientService()throwsIOException{
sc=SocketChannel.open();
sc.configureBlocking(false);
sc.connect(newInetSocketAddress(HOST,PORT));
}
publicvoidsendMsg(Stringmsg){
try{
while(!sc.finishConnect()){
}
sc.write(ByteBuffer.wrap(msg.getBytes()));
}catch(IOExceptione){
e.printStackTrace();
}
}
publicStringreceiveMsg(){
ByteBufferbuffer=ByteBuffer.allocate(1024);
buffer.clear();
StringBuffersb=newStringBuffer();
intcount=0;
Stringmsg=null;
try{
while((count=sc.read(buffer))>0){
sb.append(newString(buffer.array(),0,count));
}
if(sb.length()>0){
msg=sb.toString();
if("close".equals(sb.toString())){
msg=null;
sc.close();
sc.socket().close();
}
}
}catch(IOExceptione){
e.printStackTrace();
}
returnmsg;
}
}
2、登陆窗体,用户设置名称
packagecom.chat.client;
importjava.awt.Toolkit;
importjava.awt.event.ActionEvent;
importjava.awt.event.ActionListener;
importjavax.swing.JButton;
importjavax.swing.JFrame;
importjavax.swing.JLabel;
importjavax.swing.JTextField;
/**
*设置名称窗体
*
*@authorzing
*
*/
publicclassSetNameFrameextendsJFrame{
privatestaticfinallongserialVersionUID=1L;
privatestaticJTextFieldtxtName;//文本框
privatestaticJButtonbtnOK;//ok按钮
privatestaticJLabellabel;//标签
publicSetNameFrame(){
this.setLayout(null);
Toolkitkit=Toolkit.getDefaultToolkit();
intw=kit.getScreenSize().width;
inth=kit.getScreenSize().height;
this.setBounds(w/2-230/2,h/2-200/2,230,200);
this.setTitle("设置名称");
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setResizable(false);
txtName=newJTextField(4);
this.add(txtName);
txtName.setBounds(10,10,100,25);
btnOK=newJButton("OK");
this.add(btnOK);
btnOK.setBounds(120,10,80,25);
label=newJLabel("[w:"+w+",h:"+h+"]");
this.add(label);
label.setBounds(10,40,200,100);
label.setText("在上面的文本框中输入名字
显示器宽度:"+w+"
显示器高度:"+h
+"");
btnOK.addActionListener(newActionListener(){
@Override
publicvoidactionPerformed(ActionEvente){
Stringuname=txtName.getText();
ClientServiceservice=ClientService.getInstance();
ChatFramechatFrame=newChatFrame(service,uname);
chatFrame.show();
setVisible(false);
}
});
}
publicstaticvoidmain(String[]args){
SetNameFramesetNameFrame=newSetNameFrame();
setNameFrame.setVisible(true);
}
}
3、聊天室窗体
packagecom.chat.client;
importjava.awt.event.ActionEvent;
importjava.awt.event.ActionListener;
importjava.awt.event.KeyEvent;
importjava.awt.event.KeyListener;
importjava.awt.event.WindowAdapter;
importjava.awt.event.WindowEvent;
importjavax.swing.DefaultListModel;
importjavax.swing.JButton;
importjavax.swing.JFrame;
importjavax.swing.JList;
importjavax.swing.JScrollPane;
importjavax.swing.JTextArea;
importjavax.swing.event.ListSelectionEvent;
importjavax.swing.event.ListSelectionListener;
/**
*聊天室窗体
*@authorzing
*
*/
publicclassChatFrame{
privateJTextAreareadContext=newJTextArea(18,30);//显示消息文本框
privateJTextAreawriteContext=newJTextArea(6,30);//发送消息文本框
privateDefaultListModelmodle=newDefaultListModel();//用户列表模型
privateJListlist=newJList(modle);//用户列表
privateJButtonbtnSend=newJButton("发送");//发送消息按钮
privateJButtonbtnClose=newJButton("关闭");//关闭聊天窗口按钮
privateJFrameframe=newJFrame("ChatFrame");//窗体界面
privateStringuname;//用户姓名
privateClientServiceservice;//用于与服务器交互
privatebooleanisRun=false;//是否运行
publicChatFrame(ClientServiceservice,Stringuname){
this.isRun=true;
this.uname=uname;
this.service=service;
}
//初始化界面控件及事件
privatevoidinit(){
frame.setLayout(null);
frame.setTitle(uname+"聊天窗口");
frame.setSize(500,500);
frame.setLocation(400,200);
//设置可关闭
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//不能改变窗体大小
frame.setResizable(false);
//聊天消息显示区带滚动条
JScrollPanereadScroll=newJScrollPane(readContext);
readScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
frame.add(readScroll);
//消息编辑区带滚动条
JScrollPanewriteScroll=newJScrollPane(writeContext);
writeScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
frame.add(writeScroll);
frame.add(list);
frame.add(btnSend);
frame.add(btnClose);
readScroll.setBounds(10,10,320,300);
readContext.setBounds(0,0,320,300);
readContext.setEditable(false);//设置为不可编辑
readContext.setLineWrap(true);//自动换行
writeScroll.setBounds(10,315,320,100);
writeContext.setBounds(0,0,320,100);
writeContext.setLineWrap(true);//自动换行
list.setBounds(340,10,140,445);
btnSend.setBounds(150,420,80,30);
btnClose.setBounds(250,420,80,30);
//窗体关闭事件
frame.addWindowListener(newWindowAdapter(){
@Override
publicvoidwindowClosing(WindowEvente){
isRun=false;
service.sendMsg("exit_"+uname);
System.exit(0);
}
});
//发送按钮事件
btnSend.addActionListener(newActionListener(){
@Override
publicvoidactionPerformed(ActionEvente){
Stringmsg=writeContext.getText().trim();
if(msg.length()>0){
service.sendMsg(uname+"^"+writeContext.getText());
}
//发送消息后,去掉编辑区文本,并获得光标焦点
writeContext.setText(null);
writeContext.requestFocus();
}
});
//关闭按钮事件
btnClose.addActionListener(newActionListener(){
@Override
publicvoidactionPerformed(ActionEvente){
isRun=false;
service.sendMsg("exit_"+uname);
System.exit(0);
}
});
//右边名称列表选择事件
list.addListSelectionListener(newListSelectionListener(){
@Override
publicvoidvalueChanged(ListSelectionEvente){
//JOptionPane.showMessageDialog(null,
//list.getSelectedValue().toString());
}
});
//消息编辑区键盘按键事件
writeContext.addKeyListener(newKeyListener(){
@Override
publicvoidkeyTyped(KeyEvente){
//TODOAuto-generatedmethodstub
}
//按下键盘按键后释放
@Override
publicvoidkeyReleased(KeyEvente){
//按下enter键发送消息
if(e.getKeyCode()==KeyEvent.VK_ENTER){
Stringmsg=writeContext.getText().trim();
if(msg.length()>0){
service.sendMsg(uname+"^"+writeContext.getText());
}
writeContext.setText(null);
writeContext.requestFocus();
}
}
@Override
publicvoidkeyPressed(KeyEvente){
//TODOAuto-generatedmethodstub
}
});
}
//此线程类用于轮询读取服务器发送的消息
privateclassMsgThreadextendsThread{
@Override
publicvoidrun(){
while(isRun){
Stringmsg=service.receiveMsg();
if(msg!=null){
//若是名称列表数据,则更新聊天窗体右边的列表
if(msg.indexOf("[")!=-1&&msg.lastIndexOf("]")!=-1){
msg=msg.substring(1,msg.length()-1);
String[]userNames=msg.split(",");
modle.removeAllElements();
for(inti=0;i
更多java相关内容感兴趣的读者可查看本站专题:《Java面向对象程序设计入门与进阶教程》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总》
希望本文所述对大家java程序设计有所帮助。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。