作为一个现代人,我们对当前众多的聊天通信平台并不陌生,facebook,qq,微信等都是大部分人每天都会接触的。那你有想过构建一个自己打造的聊天室,按照自己喜欢的模式,然后和朋友一起使用吗?下面就讲下聊天室的前身——群聊服务器的实现,之后你就可以在其基础上设计独属的聊天室了。
     要实现群聊服务器,首先要先了解下面的几个问题?
   1.Socket 与ServerSocket区别!?
   2.程序中的阻塞机制是怎样的?怎样解决“阻塞”现象?
   3.群聊服务器至少需要几个类才能实现?各个类应该实现的功能?

接下来将结合代码,逐步带大家了解这些问题的答案,认识体会实现过程的思路与期间遇到的种种问题。

最先,当然就是服务器的创建。建立绑定在指定端口的服务器,然后调用服务器的接受方法,进入阻塞状态。这相当于你开了家咖啡厅,然后等待客人的光顾。不过比较遗憾的是,当前的咖啡厅只能迎接一个客人!!

ServerSocket server=new ServerSocket(port);//建立绑定在指定端口的服务器"+port);   
   while(true)
   {
   Socket client=server.accept();//调用服务器接受方法,进入阻塞状态
   System.out.println("连接上"+client.getRemoteSocketAddress());

..........}

看着这几行代码,就涉及到我们上面提到的前两个问题:

对于第一个问题,ServerSocket是对应服务器,而Socket是对应客户端;ServerSocket用于绑定端点,提供服务,并接受连接请求。Socket就是普通的连接套接字,在建立网络连接时使用的;在连接成功时,应用程序两端都会产生一个Socket实例,就相当于两头连通了一根电话线,能完成所需的会话。因此,真正进行通信的是服务器端的Socket与客户端的Socket,在ServerSocket 调用accept方法之后,它就“默默退出舞台“,由accept方法产生的Socket主权。

那么程序中的阻塞机制又是怎么回事呢?事实上,当ServerSocket调用accept方法时,会产生服务端的Socket实例,在没有用户连接上绑定的端口号时,此Socket会一直等待连接,相当于”阻塞“。当一个用户连接端口号后,服务端的Socket和用户的Socket就会连接上。若有其他用户连接端口号,会出现无法连接服务器的现象,直到之前连接的用户与服务器断开连接后才能轮到下一位,简而言之就是”一对一“;这也即”阻塞“现象。那怎么破呢???答案就是我们熟悉的线程!一个线程处理一个用户的Socket,这样多线程处理多用户,就能完美解决”僧多粥少“的困境。

因此我们要创建一个线程类,负责处理与用户的连接。当新的用户连接上端口号,就会创建一个线程,传入一个连接。

/*
 * 线程类,实现一个线程绑定一个处理对象
 */
public class ServerThread extends Thread{

private  Socket client;
 private OutputStream ous;
 private UserInfo user;
 
 //构造函数
 public ServerThread(Socket cs)
 {
  this.client=cs;
 }
 
 //取得线程对象所处理的用户对象
 public UserInfo getRelatedUser()
 {
  return this.user;
 }
  
  //处理连接对象的方法,传送服务器与客户端之间的字符串实现聊天功能
  private void Process(Socket client) throws IOException
  {
     
   InputStream ins=client.getInputStream();
      ous=client.getOutputStream();
   //将输入流封ins装为可以读取一行字符串,也就是以\r\n结尾的字符串
      BufferedReader bfr=new BufferedReader(new InputStreamReader(ins));
    
      //获取到用户输入的用户名与密码
      readmsg("请输入你的用户名");
      String username=bfr.readLine();
      readmsg("请输入你的密码");
      String pwd=bfr.readLine();
     
      //创建UserInfo对象,将它用所给信息实体化,以待与数据库中信息比较核实
      user=new UserInfo();
      user.setUsername(username);
      user.setCode(pwd);
     
      //调用数据库模块,验证用户信息是否存在
      boolean loginstate=DaoTool.UserLogin(user);
      if(!loginstate)
      {
       readmsg("您输入的用户不存在,请重新输入");
       this.closeUp();
       return;
      }    
     
      ChatTool.addClient(this);//调用管理处理类中的方法
     
   String s="你好,可以开始与服务器正式通话";
   this.readmsg(s);
   
   //String input=ReadString(ins);//调用读取字符串的方法,读取输入流中的字符串
   String input=bfr.readLine();//一行行读取用户输入的信息
   while(!input.equals("bye"))
   {
    System.out.println(this.user.getUsername()+"说:"+input);
    //将每条信息传到其他客户上
    ChatTool.sendMsg(this.user, input);
    //s="服务器收到:"+input+"\r\n";
    //this.readmsg(s);
    //input=ReadString(ins);//接受下一次通话信息
    input=bfr.readLine();
   }
   
   ChatTool.clearClient(this);//用户下线,调用管理类的方法
   
   s="通话结束,欢迎再次连接";
   this.readmsg(s);
   
   this.closeUp();
  }
  //读取信息的方法,传入前信息不用加\r\n
  public void readmsg(String str)
  {
   str+="\r\n";
   byte[] data=str.getBytes();
   try {
    ous.write(data);
    ous.flush();
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
   
  }
  
  //读取字符串的方法
  private String ReadString(InputStream ins) throws IOException
  {
   StringBuffer stb=new StringBuffer();//创建字符串缓冲区
   char c=0;
   //读取字符串,当按下空格键时表示一个字符串输入完成
   while(c!=13)
   {
    
    int ch=ins.read();
    c=(char)ch;//强制转换,将c转换成字符型
    stb.append(c);//将读取的字符添加到字符串缓冲区上   
   }
   String str=stb.toString().trim();//读取字符串缓冲区中的完整字符串,去掉空格
   return str;
  }
  public void run()
  {
   //在线程中调用连接处理方法     
    try {
     Process(this.client);
    } catch (IOException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }   
   //处理方法结束后,线程就退出
   
  }
  
  //关闭线程处理对象
  public void closeUp()
  {
   try {
    client.close();
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
}

其中有几个地方要解释下,归纳如下:

1、输出要调用flush函数:flush函数是把缓冲区的数据强行输出。在读写时,数据是先被读到内存当中,后再写到文件里;故当数据读完并不意味着数据写完,若此时调用close方法关闭IO流,遗留在内存缓冲区上的数据就会流失。所以要在close前先flush一下,正如买的饮料要喝完才丢掉瓶子!

2.发送字符串时,首先调用字符串的 getBytes()方法,得到组成这个字符串的字节数组,发送出的实际上是这个字节数组。读取字符串的时候,有两种方法。第一种,因为系统本身是一个字节一个字节的读取,我们

可以创建一个字符串缓冲区,每次把读的字节转换成字符形式再放进去,当读到回车再把缓冲区的数据输出来,如此便能读取字符串了。第二种,就是使用了系统提供的 BufferedReader API,包装了从 Socket
上得到的输入流对象,调用其已有的 readLine()方法读取一行字符串,如此在读取中文时不会乱码。

对于群聊服务器,其实最初应该有一个验证环节。用户输入用户名和密码,然后系统核实后才能连接上。所以还需要有三个类:用户信息类、数据访问类、连接处理类。

1)用户信息类,就是定义用户属性,如用户名、密码、登陆时间、地址等等。

2)数据访问类,即是对用户数据进行增、删、查、改,即 CRUD 操作。这样的一个类命名时,通常以 Dao 作为前缀( Data Access Object)。这里暂时只提供确认用户存在的方法,其他方法大家可以自行添加。

/*
 * 数据对象访问类
 */
public class DaoTool {
    
 //用户信息数据库,储存所有用户信息
 private static Map<String,UserInfo> UserDP=new HashMap();
 
 //确认用户存在的方法,核对用户名是否与用户信息数据库中的匹配
 public static boolean UserLogin(UserInfo ui)
 {
  if(UserDP.containsKey(ui.getUsername()))
  {
   return true;
  }
  
  System.out.println("您输入的用户不存在,请重新输入!");
  return false;
 }
 
 //静态块,在类自动加载之前先存入10个用户信息
 static
 {
  for(int i=0;i<10;i++)
  {
   UserInfo user=new UserInfo();
   user.setUsername("user"+i);
   user.setCode("pwd"+i);
   UserDP.put(user.getUsername(), user);//存入用户信息数据库
  }
 }
 
}

这里出现了静态块,它里面的内容会在类加载之前就先执行。java中的类加载机制分为预先加载和依需求加载。java运行所需的基本类是预先加载,而我们平常用的new关键字就是进行依需求加载;如定义一个类实例时,Student stu=new Student();此时JRE才真正把Student类加载进来。

3)用户连接类,用于管理连接处理的线程对象。

//连接处理类,管理连接处理线程对象
public class ChatTool {
 
   private static List <ServerThread>stList=new ArrayList();//创建队列,存储所有的连接处理线程
   private ChatTool(){};//设置构造器私有,其他类无法生成该类对象,但可以调用其方法,适用于工具类

/*
    * 当新的用户连接上服务器时,会同时产生一个连接处理类线程对象,把这个线程添加到队列中
    */
 public static void addClient(ServerThread st) throws IOException
 {
 stList.add(st);//添加线程到队列上
 sendMsg(st.getRelatedUser(),"我上线了,大家好!!目前在线人数:"+stList.size()); 
 
 }
/*
 * 当用户退出连接时,对应的线程对象也从队列中移除
 */
 public static void clearClient(ServerThread st)
 {
  stList.remove(st);//从队列中移除线程
  sendMsg(st.getRelatedUser(),st.getRelatedUser().getUsername()+"下线了,目前在线人数:"+stList.size());
 }
 
 /*
  * 发送信息给每个在线的用户
  */
public static void sendMsg(UserInfo user,String msg)
{

msg=user.getUsername()+"说"+msg;
 
 for(int i=0;i<stList.size();i++)
 {
  ServerThread st=stList.get(i); //从队列中获取到所有用户
  st.readmsg(msg);//将信息输出给每个用户
 }
 
}

}

这里大家应该会注意到构造器的私有设定!当构造器私有时,它只能被包含它的类自身所访问,而无法在类的外部调用,故而可以阻止对象的生成。这种构造器私有的用法一般是针对工具类的,如字符串的验证、枚举转换之类的,可以认为是作为静态接口被外部调用。我们不需要实例它们,只是需要类名调用到它们里面的方法即可。

通过这5个类,我们就能实现简单的群聊服务器了!

实现多人聊天——简单群聊服务器的实现相关推荐

  1. 多线程+socket 实现群聊服务器

    通过多线程+Socket,实现群聊服务器. 服务端: 每当有一个连接时,服务端起一个线程去维护: .将收到的信息转发给所有的客户端: 当某个客户端断开连接时需要处理断开连接 客户端: 接收与发送信息 ...

  2. Python+Socket实现多人聊天室,功能:好友聊天、群聊、图片、表情、文件等

    一.项目简介 本项目主要基于python实现的多人聊天室,主要的功能如下: 登录注册 添加好友 与好友进行私聊 创建群聊 邀请/申请加入群聊 聊天发送图片 聊天发送表情 聊天发送文件 聊天记录保存在本 ...

  3. java仿QQ聊天室群聊(快速写一个简易QQ)

    [mie haha的博客]转载请注明出处(万分感谢!): https://blog.csdn.net/qq_40315080/article/details/83052689 用java写聊天室实现群 ...

  4. c++/MFC CSocket仿QQ聊天软件,实现1对1聊天,群聊

    学习,c++有2个星期了.本来,本人是做php出身的.做php快2年了,最近身边多了很多高手.让自己对c开始感兴趣了,就开始学习c++了.首先接触的就是mfc.前几天,看到了一个博文,是有关,mfc网 ...

  5. openfire android 发送图片,基于openfire+smack开发Android即时聊天应用[四]-单人聊天、群聊、发送接收文件等...

    这篇文章主要介绍如何实现点对点单人聊天.多人的群聊.以及如何给对方发送文件,如何发送图片消息和语音消息等功能. 1.单人聊天 1.首先创建聊天对象 /** * 创建聊天窗口 * @param jid ...

  6. 【Android】基于Socket的即时聊天(群聊)

    近来感觉秋招无望,学习Socket的时候,便做了个基于Socket的群聊工具: 先看看最终效果吧 项目GitHub通道(详细代码请自行copy) 如何利用Socket通信 socket又称为" ...

  7. java仿qq群聊_[转载]仿QQ聊天室群聊的练习心得

    javase的学习即将告一段落,作为最后的一个项目练习,仿聊天室的程序编写让我很是头疼了一阵子.说起来还是自己java基础不牢的缘故导致的,虽然整体框架都已经很清晰了但是实际编写过程中却依然磕磕绊绊, ...

  8. Netty中实现多客户端连接与通信-以实现聊天室群聊功能为例(附代码下载)

    场景 Netty的Socket编程详解-搭建服务端与客户端并进行数据传输: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/1086 ...

  9. Java基于Socket实现聊天、群聊、敏感词汇过滤功能

    首先的话,这个代码主要是我很久以前写的,然后当时还有很多地方没有理解,现在再来看看这份代码,实在是觉得丑陋不堪,想改,但是是真的改都不好改了- 所以,写代码,规范真的很重要. 实现的功能: 用户私聊 ...

最新文章

  1. processing python模式_python学习Processing
  2. 八进制转换成十进制c语言程序,C语言程序 十进制、八进制、十六进制的相互转化...
  3. python 判断文件夹是否存在 否则创建_10行Python代码自动清理电脑内重复文件,解放双手!...
  4. 打怪升级的monteCarlo仿真方法
  5. Android 异步任务
  6. 6. PHP之适配器模式
  7. TCP/IP协议-三次握手四次挥手
  8. 微博之争,没有硝烟的互联网战争
  9. 【运筹优化】SOA海鸥优化算法求解无约束多元函数最值(Java代码实现)
  10. 解决sublime text2字体显示模糊问题
  11. 数据链路层的功能与设备
  12. 不为人知的黑科技||双十一薅羊毛正确姿势
  13. 软件测试学习(基础篇)— —第5天:JS基础
  14. eclipse开发android入门学习
  15. 雷石服务器系统怎么设置分辨率,雷石传奇改惊艳触摸屏不能校准 触摸不准问题?...
  16. 加工制造业采购商城平台:实现加工制造业采购自动化、协同化管理
  17. isotope自动布局
  18. Thesus!王子归来!
  19. 极其强大的数据统计软件 Stata 安装教程
  20. java解答约瑟夫问题

热门文章

  1. 旧机宝开发笔记之:目录
  2. 幼儿园环境创设的四大要点
  3. iOS 组件化的三种方案
  4. 【皮皮哥资源网】问道 (二)
  5. 探秘福特,科技造就节油王
  6. linux中vim中swapfile,在vim中撤消从交换文件恢复(Undo recovery from swap file in vim)
  7. String Bulilder
  8. ArcGIS面缝隙检查
  9. 橡胶促进剂MBTS(DM)市场现状及未来发展趋势
  10. 导出Excel设置文件名