热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

使用非阻塞通信的简单聊天工具

*应用通讯协议*发送在线名单:00+所有人&姓名1&姓名2*上线:01+姓名*下线:02+姓名

 

/*应用通讯协议
 * 发送在线名单:00+所有人&姓名1&姓名2......
 * 上线:01+姓名
 * 下线:02+姓名
 * 向所有人发送消息:03+发送者+消息
 * 客户端向某个人发送消息:04+发送对象+发送者+消息         服务端再变成04+发送者+消息     发送给发送对象
 *
 */
由于原定的登录界面(每个用户有唯一的账户)没写故使用了用户名做客户的标志
所以在使用时客户端的姓名不能重复,不然会出错~
发送的字符长度不能超过1024字节
若存在其他bug欢迎提出好改正~谢谢~
 
服务端代码:
 
package Server;
import java.io.*; 
import java.nio.*; 
import java.nio.channels.*; 
import java.nio.charset.*; 
import java.net.*; 
import java.util.*; 
import java.util.jar.Attributes.Name;
import javax.swing.text.AbstractDocument.BranchElement;
 
public class ser 

 //创建一个Selector对象 
    private Selector selector = null; 
    //创建一个ServerSocketChannel对象 
    private ServerSocketChannel serverSocketChannel = null; 
    //设置端口号
    private int port = 8885; 
    //编码和解码需要的
    private Charset charset = Charset.forName("GBK");
    //散列表用来存放对应的每个人的姓名和通讯通道
    private static Hashtable hast = new Hashtable();
    //存放在线名单
    private static ArrayList allName=new ArrayList();
    //构造函数
    public ser() throws IOException 
    { 
     
        selector = Selector.open(); 
        serverSocketChannel = ServerSocketChannel.open();
        //使得在同一个主机上关闭了服务器程序,紧接着再启动该服务器程序时,可以顺利绑定到相同的端口
        serverSocketChannel.socket().setReuseAddress(true);
        //使ServerSocketChannel工作于非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //把服务器进程与一个本地端口绑定
        serverSocketChannel.socket().bind(new InetSocketAddress(port)); 
        System.out.println("服务器启动"); 
    } 
 
    public void service() throws IOException 
    { 
     //添加一个接收连接的事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 
        //当触发事件时
        while( selector.select() > 0 ) 
        { 
         //获得Selector的selected-keys集合
            Set readyKeys = selector.selectedKeys(); 
            Iterator it = readyKeys.iterator(); 
            //循环事件的集合
            while( it.hasNext() ) 
            {  //
                SelectionKey key = null; 
                try 
                {  //取出一个SelectionKey 
                    key = (SelectionKey)it.next(); 
                    it.remove();  //
                    if( key.isAcceptable() ) 
                    {
                     //处理接收连接就绪事件; 
                     //获得与SelectionKey关联的ServerSocketChannel
                        ServerSocketChannel ssc = (ServerSocketChannel)key.channel(); 
                      //获得与客户连接的SocketChannel
                        SocketChannel socketChannel = (SocketChannel)ssc.accept(); 
                     
                        System.out.println("接收到客户连接,来自:" + socketChannel.socket().getInetAddress()
                                + ":" + socketChannel.socket().getPort()); 
                      //把SocketChannel设置为非阻塞模式
                        socketChannel.configureBlocking(false); 
                     
                      //SocketChannel向Selector注册读就绪事件, 关联了一个buffer附件
                        socketChannel.register(selector, SelectionKey.OP_READ ); 
                    } 
                    if( key.isReadable() ) 
                    { 
                     //处理读就绪事件;
                        receive(key); 
                    } 
                   
                } 
                catch( IOException e ) 
                { 
                 //某个和客户端的通讯发送错误就关闭该通道,继续其他通讯
                    e.printStackTrace(); 
                    try 
                    { 
                        if( key != null ) 
                        { 
                            key.cancel(); 
                            key.channel().close(); 
                        } 
                    } 
                    catch( Exception ex ) 
                    { 
                        e.printStackTrace(); 
                    } 
                } 
            }
        }
    } 
  //发送消息
    public void send(SocketChannel socketChannel, String msg) throws IOException
 { 
     //发送编码后的消息
  socketChannel.write(encode(msg));
  
 }
    //向某个客户端发送消息
  public void sendOne(String name,String msg)throws IOException
  {
   //获取该用户的通道
   SocketChannel soc=hast.get(name);
   //向该用户发送消息
   send(soc, msg);
  }
  //向所有人发送消息
  public void sendAll(String msg)throws IOException
  {
   //获取所有的连接通道
   Enumeration em = hast.elements();
      while(em.hasMoreElements())
  {//循环发送
  SocketChannel soc=(SocketChannel)em.nextElement();
  send(soc, msg);
  }
  }
//  读取数据
    public void receive(SelectionKey key) throws IOException 
    { 
       
        SocketChannel socketChannel = (SocketChannel)key.channel();
        //创建一个用于存放用户发送来的数据的缓冲区 
        ByteBuffer readBuff = ByteBuffer.allocate(1024); 
      
        if (socketChannel.read(readBuff) >= 0) {
            readBuff.flip();
 //解码
            String msg =  decode(readBuff);
            readBuff.clear();//清除缓冲区
            if(msg.length()>2)
            {
             //读取前2位的应用通讯协议。这个程序员自己定义
            String mark=msg.substring(0,2);
           //由于java的switch跟C#不一样不支持string类型所以转换成int类型,
            Integer markerInteger=new Integer(mark);
            switch(markerInteger)
            {
            case 1://上线处理
             {
              
              //获取上线者姓名
              String name =msg.substring(2);
                 sendAll(msg);
                 //添加上线用户和通讯通道
                 hast.put(name, socketChannel);
                 String getAllName="所有人";
                 //生成用户名单字符串
                 for(int i=0;i                  {
                  getAllName+="&";
                  getAllName+=allName.get(i);
                  
                 }
                 //发送给刚上线的用户在线名单
                 send(socketChannel, "00"+getAllName);
                 //添加刚上线用户的姓名
                 allName.add(name);
              break;
             }
             case 2://下线处理
             {
              
              String name =msg.substring(2);
              //删除该用户的通讯
              hast.remove(name);
              //从名单去除
              allName.remove(name);
              //发送消息通知所有用户有人下线
              sendAll(msg);
              break;
             }
             case 3://向所有人发送消息
             {
              sendAll(msg);
              break;
             }
             case 4://向某个人发送消息
             {
              //获取发送的对象
              String sendName=msg.substring(2,12).trim();
              msg="04"+msg.substring(12);
              sendOne(sendName, msg);
              break;
             }
            }
            }
           //后台输出。验证使用可以删除
            System.out.println(msg);
        }
     
      
    } 
 
    public String decode(ByteBuffer buffer) 
    { //解码 
        CharBuffer charBuffer = charset.decode(buffer); 
        return charBuffer.toString(); 
    } 
 
    public ByteBuffer encode(String str) 
    { //编码 
        return charset.encode(str); 
    } 
  //主函数入口
    public static void main(String args[]) throws Exception 
    { 
        ser server = new ser(); 
        server.service(); 
    } 

 
 
客户端代码:
package Client;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
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.Date;
import java.util.Iterator;
import java.util.Set;
public class ChatClient extends JFrame implements ActionListener,Runnable{
 JTextArea jtamsg;
 JTextField jtasname,jtaport,jtfsendmsg,jtfname;
 JLabel jlsname,jlsport,jlmsg,jlname;
 JButton jbcon,jbsend;
 JScrollPane jsp;
 JComboBox combox;
 BufferedReader br;
 
 String ip;
 int port;
 String name;
 String message;
 String strcombox;
 String time;
 Thread thread;
 private SocketChannel socketChannel = null; 
    
    private Charset charset = Charset.forName("GBK"); 
    private Selector selector;
 /**
  * 构造方法是实现窗口的界面布局
  */
 public ChatClient()
 {
  jlsname = new JLabel("服务器名");
  jlsname.setBounds(new Rectangle(40,25,80,30));
  jtasname = new JTextField("192.168.0.20");
  jtasname.setBounds(new Rectangle(110,25,80,30));
  
  jlsport = new JLabel("端口");
  jlsport.setBounds(new Rectangle(210,25,80,30));
  jtaport = new JTextField("8885");
  jtaport.setBounds(new Rectangle(240,25,50,30));
  
  jlname = new JLabel("姓名");
  jlname.setBounds(300,25,50,30);
  jtfname = new JTextField();
  jtfname.setBounds(340,25,80,30);
  
  jbcon = new JButton("连接");
  jbcon.setBounds(new Rectangle(440,25,80,30));
  
  jlmsg = new JLabel("输入信息");
  jlmsg.setBounds(new Rectangle(40,65,80,30));
  
  jtfsendmsg = new JTextField();
  jtfsendmsg.setBounds(new Rectangle(110,65,210,30));
  
  combox = new JComboBox();
  combox.setBounds(new Rectangle(340,65,80,30));
  combox.addItem("所有人");
  
  jbsend = new JButton("发送");
  jbsend.setBounds(new Rectangle(440,65,80,30));
  
  jtamsg = new JTextArea();
  jtamsg.setEditable(false);
  jsp = new JScrollPane(jtamsg);
  jsp.setBounds(new Rectangle(40,105,480,240));
  
  jbcon.addActionListener(this);
  jbsend.addActionListener(this);
  this.setLayout(null);
  this.add(jlsname);
  this.add(jtasname);
  
  this.add(jlsport);
  this.add(jtaport);
  this.add(jbcon);
  
  this.add(jlname);
  this.add(jtfname);
  
  this.add(jlmsg);
  this.add(jtfsendmsg);
  this.add(combox);
  this.add(jbsend);
  this.add(jsp);
  
  this.setSize(550, 400);
  this.setResizable(false);
  this.setVisible(true);
  
  
  //添加关闭窗口事件处理,发送下线消息
  this.addWindowListener(new WindowAdapter()
  {
   public void windowClosing(WindowEvent e)
            {
    if(socketChannel == null)
     System.exit(0);
    else
    {
     try {
      //下线
      down();
      
     }
     catch (Exception e2) {
      // TODO: handle exception
     }
     finally
     {
      //关闭窗口
     System.exit(0);
     }
    }
     
            }
  });
 }
 
 /**
  * 事件处理函数
  * 有连接和发送两个事件
  * 连接:判断基本信息是否填写好;
  *      如果填写好,则调用connect()函数建立连接
  *      接着禁用连接按钮及相关编辑框
  * 发送:判断是对个人说还是对所有人说
  *      根据对象的不同对message进行加工,
  *      最后都是通过send()函数发送消息
  */
 public void actionPerformed(ActionEvent e)
 {
  //连接事件处理
  if(e.getSource() == jbcon)
  {
   name = jtfname.getText().trim();
   ip = jtasname.getText().trim();
   this.setTitle("客户端:"+name);
   port = Integer.parseInt(jtaport.getText().trim());
   if(!name.equals("")&&!ip.equals("")&&!jtaport.getText().trim().equals(""))
   {
    //姓名限制在10个字符内
    if(name.length()>10)
    {
     JOptionPane.showMessageDialog(null, "名字在10个字符以内" +
       "请谅解!","提示",JOptionPane.INFORMATION_MESSAGE);
     
    }
    else {
     
    
    try {
     //连接服务器
     connect(ip,port);
     jtasname.setEditable(false);
     jtaport.setEditable(false);
     jtfname.setEditable(false);
     jbcon.setEnabled(false);
    } catch (Exception e1) {
     
    
    }
    }
    
   }
   else JOptionPane.showMessageDialog(null, "请填写基本信息","提示",JOptionPane.INFORMATION_MESSAGE);
  }
  
  //发送事件处理
  if(e.getSource() == jbsend)
  {
   try
   {
         if(!jtfsendmsg.getText().trim().equals(""))
         {
          message = jtfsendmsg.getText();
          strcombox = (String) combox.getSelectedItem();
          if(strcombox.equals("所有人"))
          {
           send("03"+makeName(name)+message);
           //jtamsg.append("你 对 所有人 说:\n"+jtfsendmsg.getText()+"\n");
          }
          else
          {
           showTime();
                 jtamsg.append("你  对  "+strcombox+" 说:\n"+message+"\n");
           message = "04"+makeName(strcombox)+ makeName(name)+message;
           send(message);
          }
          jtfsendmsg.setText("");
         }
         else
          JOptionPane.showMessageDialog(null, "发送内容不能为空,请重新发送","提示",JOptionPane.WARNING_MESSAGE);
   }
         catch(IOException ex)
      {}
  }
 }
 private String makeName(String name)
 {//将姓名变成长度为10的字符串
  String setName=name;
  while(setName.length()<10)
   setName+=" ";
  return setName;
 }
 /**
  * 函数实现了连接指定服务器的功能
 
  * @param ip    服务器IP
  * @param port  服务器端口号
  * @throws Exception 异常处理
  */
 
 public void connect(String ip,int port) throws Exception
 {
   socketChannel = SocketChannel.open(); 
        
         InetSocketAddress isa = new InetSocketAddress(ip, port); 
         socketChannel.connect(isa); //采用阻塞模式连接服务器 
         socketChannel.configureBlocking(false); //设置为非阻塞模式 
         send(jtfname.getText());
         selector = Selector.open();
         socketChannel.register(selector, SelectionKey.OP_READ);
  jtamsg.append("连接成功"+"\n");
  
  send("01"+name);
  thread = new Thread(this);
//启动另外一个线程监视通信事件
  thread.start();
 }
 
 /**
  * 下线通知函数
  */
 public void down()throws IOException
 {
  send("02"+name);
  socketChannel.close();
 
 }
 
 /**
  * 发送消息函数
  * 将消息写入数据输出流中输出
  * @param message  要发送的消息
  * @throws IOException
  */
 public void send(String msg) throws IOException
 { 
  socketChannel.write(charset.encode(msg));
 }
 
  
 
 /**
  * 函数实现了消息的接受和显示
  * 首先创建也Socket绑定的输入流
  * 其次建立相应的数据输入流,并读取消息
  * 消息分为3类,一类是用户在线情况通知,以“&”区分
  * 一类是用户下线通知,以“下线”区分
  * 最后一类是聊天的消息
  * 将着三类消息进行处理
  */
 
 public void run()
 {
  try {
   while (true)
   {
           
                     selector.select();
                     Iterator iter = selector.selectedKeys().iterator();
                               
                while (iter.hasNext())
                {
                 SelectionKey key = iter.next();
                 iter.remove();
                    if (key.isReadable())
                    {
                     receive(key);  
                    }
   
                }
   }
  }
           catch (IOException e)
           {
                   e.printStackTrace();
           }
       
   
             
 }
  public void sendd(SelectionKey key) throws IOException 
     { 
      
         ByteBuffer buffer = (ByteBuffer)key.attachment(); 
         SocketChannel socketChannel = (SocketChannel)key.channel(); 
         buffer.flip(); //把极限设为位置,把位置设为0 
         
         socketChannel.write(buffer);
        // System.out.print(data); 
       
        
     } 
 public void receive(SelectionKey key) throws IOException 
    { 
       
        SocketChannel socketChannel2 = (SocketChannel)key.channel(); 
        ByteBuffer readBuff = ByteBuffer.allocate(1024); 
      
        if (socketChannel2.read(readBuff) >= 0) {
            readBuff.flip();
          
           String msg =  decode(readBuff);
           readBuff.clear();
           String mark=msg.substring(0,2);
           Integer intmark=new Integer(mark);
           switch(intmark)
           {
           case 0:
           {
         //接收所有在线名单
            String allNameString=msg.substring(2);
            String []allName=allNameString.split("&");
            combox.removeAllItems();
           
            for(int i=0;i             {
             combox.addItem(allName[i]);
            }
            break;
           }
           case 1:
           {
           
            String theName =msg.substring(2).trim();
            combox.addItem(theName);
            showTime();
            jtamsg.append(theName+" 上线了\n");
            break;
          
           }
           case 2:
           {
           
            String theName =msg.substring(2).trim();
            combox.removeItem(theName);
            showTime();
            jtamsg.append(theName+" 下线了\n");
            break;
           }
           case 3:
           {
          //接收群发消息
            String sender=msg.substring(2,12).trim();
            showTime();
            jtamsg.append(sender+" 对所有人说:\n");
            jtamsg.append(msg.substring(12));
            break;
           }
           case 4:
           {
           
          //接收某人消息
            String theSendName =msg.substring(2,12).trim();
            String theMsg=msg.substring(12);
            showTime();
            jtamsg.append(theSendName+" 对你说:\n");
            jtamsg.append(theMsg);
            break;
           }
          
           }
          
          
        }
      
     
    }
  public String decode(ByteBuffer buffer) 
     { //解码 
         CharBuffer charBuffer = charset.decode(buffer); 
         return charBuffer.toString(); 
     } 
  
     public ByteBuffer encode(String str) 
     { //编码 
         return charset.encode(str); 
     } 
 /**
  * 显示时间函数,目的是压缩代码
  */
 public void showTime()
 {
  Date date = new Date();
  time = date.toLocaleString();
  jtamsg.append(time+"\n");
 }
 
 
 public static void main(String arg[])
 {
  new ChatClient();
  
 }
}
 
下面是简单的流程图
 

推荐阅读
  • 使用freemaker生成Java代码的步骤及示例代码
    本文介绍了使用freemaker这个jar包生成Java代码的步骤,通过提前编辑好的模板,可以避免写重复代码。首先需要在springboot的pom.xml文件中加入freemaker的依赖包。然后编写模板,定义要生成的Java类的属性和方法。最后编写生成代码的类,通过加载模板文件和数据模型,生成Java代码文件。本文提供了示例代码,并展示了文件目录结构。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • (三)多表代码生成的实现方法
    本文介绍了一种实现多表代码生成的方法,使用了java代码和org.jeecg框架中的相关类和接口。通过设置主表配置,可以生成父子表的数据模型。 ... [详细]
  • 本文介绍了在sqoop1.4.*版本中,如何实现自定义分隔符的方法及步骤。通过修改sqoop生成的java文件,并重新编译,可以满足实际开发中对分隔符的需求。具体步骤包括修改java文件中的一行代码,重新编译所需的hadoop包等。详细步骤和编译方法在本文中都有详细说明。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 开发笔记:spring boot项目打成war包部署到服务器的步骤与注意事项
    本文介绍了将spring boot项目打成war包并部署到服务器的步骤与注意事项。通过本文的学习,读者可以了解到如何将spring boot项目打包成war包,并成功地部署到服务器上。 ... [详细]
  • 本文介绍了解决java开源项目apache commons email简单使用报错的方法,包括使用正确的JAR包和正确的代码配置,以及相关参数的设置。详细介绍了如何使用apache commons email发送邮件。 ... [详细]
  • 使用Spring AOP实现切面编程的步骤和注意事项
    本文介绍了使用Spring AOP实现切面编程的步骤和注意事项。首先解释了@EnableAspectJAutoProxy、@Aspect、@Pointcut等注解的作用,并介绍了实现AOP功能的方法。然后详细介绍了创建切面、编写测试代码的过程,并展示了测试结果。接着讲解了关于环绕通知的使用方法,并修改了FirstTangent类以添加环绕通知方法。最后介绍了利用AOP拦截注解的方法,只需修改全局切入点即可实现。使用Spring AOP进行切面编程可以方便地实现对代码的增强和拦截。 ... [详细]
author-avatar
财珍文纶1
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有