winform项目——仿QQ即时通讯程序12:服务端程序补充及优化

编程项目2019/12/22 21:13:07阅读:788

上一篇文章大概完成了服务端程序,今天继续做项目的时候发现还有一些功能没有做,还有几处地方不够完善。不做好就会影响客户端程序的编写,因此,本篇文章将对服务端程序进行补充和优化。

服务端程序

首先是未完成的功能:离线消息的检测与发送

用户连接到服务器之后,不仅要开启线程接收、转发消息,也要检测数据库中是否有离线消息,如果有就发送给用户并将离线消息删除。

思路:根据上线的用户账号,查找离线消息表,找到记录就发送给用户,然后将数据库记录删除即可。在昨天的开启接收消息转发消息的代码下面,继续开启检测离线消息的线程:

//检测是否有离线消息,如果有就发送到客户端
Thread check = new Thread(checkUnReceivemsg);
check.Start(Account);

然后是checkUnReceivemsg方法:

private void checkUnReceivemsg(object clientAccount)
{
string toAccount = clientAccount.ToString();
//根据该上线的账号查找是否有离线消息
SqlDataReader sdr = SqlHelper.ExecuteReader("select * from Temp_msg where ToAccount='" + toAccount + "'");
while(sdr.Read())
{
if (dic_socket.ContainsKey(toAccount))
{
//给该账号发送所有的普通离线消息
if(sdr[4].ToString().Trim() != "")
{
dic_socket[toAccount].Send(Encoding.UTF8.GetBytes(sdr[2].ToString().Trim() + splitFlag + sdr[3].ToString().Trim()+splitFlag+sdr[4].ToString().Trim()));
}
else
{
//发送离线的验证消息
dic_socket[toAccount].Send(Encoding.UTF8.GetBytes(sdr[2].ToString().Trim() + splitFlag + sdr[3].ToString().Trim()));
}
}
}
sdr.Close();
//发送完毕之后删除该账号的离线消息
SqlHelper.ExecuteSql("delete from Temp_msg where ToAccount='"+toAccount+"'");
}

注意这个方法里面的发送消息的方式,这里用了一个if判断该消息是普通聊天消息还是验证消息。这里涉及到一个核心的消息模型。

CIM消息模型

我们作为程序的创造者,在这里将CIM程序中所有涉及到的消息分为两类:普通消息和验证消息。为了区分这两类消息,我们定义了一个规则,普通消息发送的时候,带有三种属性:对方账号、消息内容和时间,而验证消息则去掉了时间,只带有对方账号和验证消息内容。这个规则是固定的。因此后面做到客户端接收消息的时候,也要根据这个规则去判断消息类型。

针对这个规则,我们需要对上一篇文章的服务端代码的receiveAndSend方法进行了一些修改,修改后的代码如下:

private void receiveAndsend(object clientAccount)
{
//服务端核心功能:接收消息并转发消息


//将object变成string 方便使用
string fromAccount = clientAccount.ToString();
try
{
//死循环保证能一直接收消息
while (true)
{
byte[] buffer = new byte[1024];
int len = dic_socket[fromAccount].Receive(buffer);
//接收到的普通消息含有三段,前一段是目标账号,后一段是内容,再后面是时间
string msg = Encoding.UTF8.GetString(buffer, 0, len);
//将两段消息分隔开
string[] msgarr = msg.Split(new string[] { splitFlag }, StringSplitOptions.None);

if(msgarr.Length == 3)
{//普通消息
string toAccount = msgarr[0];
string content = msgarr[1];
string time = msgarr[2];
//利用目标账号对字典进行检测,如果存在表示目标用户在线,可以转发
if (dic_socket.ContainsKey(toAccount))
{
//对方在线
dic_socket[toAccount].Send(Encoding.UTF8.GetBytes(fromAccount + splitFlag + content + splitFlag + time));
}
else
{
//对方不在线 将消息保存在离线消息临时表中
SqlHelper.ExecuteSql("insert into Temp_msg (ToAccount,FromAccount,MsgContent,Time) values ('" + toAccount + "','" + fromAccount + "','" + content + "','" + time + "')");
}
}else if(msgarr.Length == 2)
{
//验证消息
string toAccount = msgarr[0];
string content = msgarr[1];
//利用目标账号对字典进行检测,如果存在表示目标用户在线,可以转发
if (dic_socket.ContainsKey(toAccount))
{
//对方在线
dic_socket[toAccount].Send(Encoding.UTF8.GetBytes(fromAccount + splitFlag + content));
}
else
{
//对方不在线 将消息保存在离线消息临时表中
SqlHelper.ExecuteSql("insert into Temp_msg (ToAccount,FromAccount,MsgContent) values ('" + toAccount + "','" + fromAccount + "','" + content + "')");
}
}


}
}
catch (Exception ee)
{
//发生此异常是因为客户端连接断开,将这种异常认为是正常操作即可
//在这里当做客户端下线即可
if (dic_socket.ContainsKey(fromAccount))
{
dic_socket.Remove(fromAccount);
upAnddown.AppendText("账号" + fromAccount + "在" + DateTime.Now.ToString() + "下线\r\n");
label3.Text = (int.Parse(label3.Text) - 1).ToString();
online_list.Items.Remove(fromAccount);
}
}
}

可以看到,代码中新增了区分两种类型消息的逻辑。还有一点需要提及,前期我们分析数据库的时候,建立了一张验证消息表,那么按照我们今天的逻辑,将不再需要用另一个表去存储验证消息了,离线的验证消息当做普通消息处理,只不过最后的time字段不需要存储东西即可。验证消息的历时记录就存放在本地文件中,可以减轻服务器负担。

服务端程序编者已经测试运行了一下午,一直都没有停止运行,说明还是挺稳定的,上下线记录功能也能够正常工作。

服务端程序


好了,到目前为止,服务端程序算是真正做完了,下一篇文章将介绍客户端登录之后的初始化操作。

本文系小博客网站原创,转载请注明文章链接地址