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的集合并进行迭代 Iterator iter=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并迭代 Iterator iter=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感兴趣的操作 Iterator iter=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"; Iterator iter=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(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。