作为一个现代人,我们对当前众多的聊天通信平台并不陌生,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个类,我们就能实现简单的群聊服务器了!
相关推荐
在这个项目中,"javaWeb(四个范围)实现聊天室功能(群聊+私聊)"主要是利用了JavaWeb的四个作用域——Page、Request、Session和Application,来实现用户之间的即时通信。下面将详细解释这些知识点。 1. **Servlet...
综上所述,该多人聊天程序设计实现了基本的聊天功能,包括用户登录、私聊、群聊和退出系统,以及服务器对客户端连接的管理和消息转发。通过Java的Socket和多线程特性,实现了客户端和服务器之间的稳定通信。在实际...
现在,让我们关注这个项目的核心功能——私聊和群聊的实现。 私聊(Private Chat): 私聊是指用户之间的点对点通信,只有发送方和接收方能接收到消息。在Node.js和Socket.IO中,这通常通过为每个用户分配一个唯一...
为了实现聊天室的用户界面,Java提供了丰富的图形用户界面(GUI)工具,如Swing和JavaFX。在这个项目中,可能使用了Swing库,通过 `JFrame` 创建主窗口,`JTextArea` 显示聊天记录,`JTextField` 输入消息,以及 `...
本项目名为“控制台群聊程序”,它通过C++语言实现了基于控制台界面的多人聊天功能,集成了登陆、用户信息同步、聊天以及注销等基本操作。其核心技术主要包括Socket编程和多线程技术,同时采用了自定义的线程池进行...
5. **聊天室和群聊**:XMPP协议支持创建聊天室,允许多人参与讨论。开发者需要实现加入、离开聊天室以及发送和接收群聊消息的功能。 6. **实时推送**:为了提高用户体验,可以结合Google的Firebase Cloud Messaging...
【操作系统实验报告——VC++网络聊天室的实现】 本次计算机操作系统课程设计的目标是实现一个基于VC++和MFC的网络聊天室。该聊天室利用计算机网络技术,为用户提供实时通讯的功能,包括多人会话和一对一私聊。设计...
【计算机网络课程设计实验报告——QQ网络聊天室实现】 在这个实验报告中,我们将探讨如何实现一个简单的QQ网络聊天室,这是计算机网络课程设计的一部分。这个聊天室基于互联网技术,允许用户进行实时通信。以下是...
5. **多用户聊天(MUC)**:XMPP允许创建多人聊天室,实现群聊功能。 6. **在线状态与-presence**:用户可以广播自己的在线状态,接收其他用户的在线状态更新。 通过学习这本书,开发者将掌握如何结合JavaScript和...
本篇文章将深入探讨一个名为“chatroom.rar”的项目,这是一个用Go语言编写的聊天室程序,它涵盖了服务器端和客户端的实现,使用了协程和通道等高级特性,以支持高并发的多人聊天功能。 首先,我们来看一下这个聊天...
【小型聊天软件】是一款基于Java开发的简单通讯应用,它实现了基本的在线聊天功能,让用户能够像使用QQ一样进行实时沟通。这款软件的核心是利用计算机网络和网络编程技术,特别是套接字(Socket)通信机制,来搭建一...
本项目——"个人QQ项目",旨在实现一个功能完备、用户体验优良的即时通讯平台,包括登录、注册、个人资料修改以及多人聊天功能,并提供了项目答辩的PPT,为学习者提供了一个从理论到实践的完整案例。 一、登录与...
私聊和群聊功能是CookIM的基础,私聊允许用户与指定的个体进行一对一交流,而群聊则可以创建多人参与的讨论组。这两种模式满足了不同场景下的沟通需求,增强了用户体验。 在消息类型方面,CookIM不仅支持文本消息,...
- **群聊功能**:支持多人同时在线聊天。 - **表情包**:增加聊天趣味性,提升用户体验。 - **语音与视频通话**:提供高质量的语音和视频通信服务。 #### 四、性能优化策略 ##### 4.1 缓存机制 - **减少数据库访问...
一个简单的CS模式的聊天软件,用socket实现,比较简单。 凯撒加密解密程序 1个目标文件 1、程序结构化,用函数分别实现 2、对文件的加密,解密输出到文件 利用随机函数抽取幸运数字 简单 EJB的真实世界模型(源...
UDP在实现聊天应用时,关键知识点包括: 1. **UDP套接字编程**:如何创建和使用UDP套接字,以及如何使用sendto和recvfrom函数进行数据传输。 2. **多线程或多进程**:为了处理多个并发连接,通常需要在服务器端使用...
一个简单的CS模式的聊天软件,用socket实现,比较简单。 凯撒加密解密程序 1个目标文件 1、程序结构化,用函数分别实现 2、对文件的加密,解密输出到文件 利用随机函数抽取幸运数字 简单 EJB的真实世界模型(源代码...
一个简单的CS模式的聊天软件,用socket实现,比较简单。 凯撒加密解密程序 1个目标文件 1、程序结构化,用函数分别实现 2、对文件的加密,解密输出到文件 利用随机函数抽取幸运数字 简单 EJB的真实世界模型(源...
一个简单的CS模式的聊天软件,用socket实现,比较简单。 凯撒加密解密程序 1个目标文件 1、程序结构化,用函数分别实现 2、对文件的加密,解密输出到文件 利用随机函数抽取幸运数字 简单 EJB的真实世界模型(源...
《易语言组件聊天工具——构建网络通信的基石》 易语言作为一款国产的编程语言,以其独特的“易”字命名,旨在让编程变得更加简单、直观。它的组件聊天工具更是为开发者提供了一种快速构建网络聊天应用的解决方案。...