winform项目——仿QQ即时通讯程序14:接收、存储验证消息

上一篇文章,我们的CIM项目已经做到发送验证消息了,本篇文章将实现验证消息的接收和存储。

编程思路

既然已经实现了发送验证消息,如何对方在线,消息将直接转发给用户,如果不在线,则存储在数据库等待用户上线后转发给用户。不管是哪一种情况,客户端必须具有接受验证消息的功能。

验证消息

步骤1:我们需要在客户端程序上写一个实时接收服务端消息的方法。我们已经在前面提到过将普通消息和验证消息以类似的方式进行区分,因此这个接收消息的方法中必须要能够按照规则区分两种类型的消息。

步骤2:接收到验证消息后如何表现到界面上。也就是说怎样显示在会话列表中。

步骤3:如何在本地存储验证消息记录

本篇文章我们就要实现这三个步骤就能完成目标。那么开始吧。

客户端接收消息功能实现

我们要提一下之前的原则:客户端要时刻接收消息,必须开启独立的线程。所有的消息记录都将存储在本地。所以接收到消息后不仅要在界面上显示,还有在后台存储到本地文件中。

在Major主窗体的加载事件中补充一个线程:

//开启线程接收服务端发来的消息
Thread receiveThread = new Thread(receiveMsg);
receiveThread.IsBackground = true;
receiveThread.Start();

接着是receiveMsg的具体实现:

/// <summary>
        /// 实时接收服务端消息
        /// </summary>
        private void receiveMsg()
        {
            //死循环保证能一直接收消息
            while (true)
            {
                byte[] buffer = new byte[1024];
                int len = Common.socket.Receive(buffer);
                //该msg可以是普通消息,也可以是验证消息
                string msg = Encoding.UTF8.GetString(buffer, 0, len);
                //通过split将msg中的数据拆分出来
                string[] msgarr = msg.Split(new string[] {Common.splitFlag }, StringSplitOptions.None);
                //通过判断拆分后包含几段来区分是普通消息还是验证消息
                if(msgarr.Length == 3)
                {//是普通消息 先不做
                    MessageBox.Show("1");//测试用
                }
                else if(msgarr.Length ==2)
                {//是验证消息
                    //判断是否是验证消息的反馈,暂时只识别“同意”反馈
                    string[] text = msgarr[1].Split(new string[] {"~~~" }, StringSplitOptions.None);
                    if (text.Length == 3&&text[0] == "" && text[1] == "同"&& text[2]=="意")
                    {//说明是验证消息的反馈
                        //加载和对方的会话
                        TalkMessage tm = new TalkMessage();
                        tm.Account = msgarr[0];
                        tm.SubMessage = "你已和"+tm.Account+"成为好友";
                        //geiNickName是之前写好的函数
                        tm.NickName = getNickName(tm.Account);
                        //之前写好的loadtalk方法
                        //获取返回值的目的是为了后面判断该会话是否存在
                        Panel talkpanel = loadtalk(0,tm);
                        Common.existedTalk.Add(tm.Account, talkpanel);
                        //刷新会话列表界面
                        refreshTalkList();
                    }
                    else
                    {//普通验证消息
                        //1.会话列表出现验证消息会话
                        //判断界面上是否有了验证消息会话 有就修改,没有就添加
                        if (!Common.existedTalk.ContainsKey("验证消息"))
                        {
                            //不包含就添加到会话列表
                            TalkMessage tm = new TalkMessage();
                            tm.NickName = "验证消息";
                            tm.SubMessage = msgarr[0] + "请求添加你为好友";
                            tm.Account = "验证消息";
                            Panel talkpanle = loadConfirmTalk(tm);
                            Common.existedTalk.Add(tm.Account, talkpanle);
                            //刷新会话列表界面
                            refreshTalkList();
                        }
                        else
                        {
                            //已经存在会话列表中了,就修改上面的提示消息
                            Panel talkpanel = Common.existedTalk["验证消息"];
                            foreach (Control c in talkpanel.Controls)
                            {
                                if (c is Label)
                                {
                                    if (((Label)c).Name == "messageName")
                                    {
                                        ((Label)c).Text = msgarr[0] + "请求添加你为好友";
                                    }
                                }
                            }
                            //刷新会话列表界面
                            refreshTalkList();
                        }
                        //2.将消息保存到本地文件中,需要保存对方的账号、昵称、附加消息和状态
                        //为了存取方便,将四种数据存在文本文件的一行,一行就表示一个验证消息记录
                        Thread t = new Thread(saveConfirmMsg);
                        t.IsBackground = true;
                        //线程中方法传参,参数类型必须为object
                        object m = msgarr;
                        t.Start(m);
                    }
                }
            }
        }

这个方法里面包含了几个其它的方法,都很重要,首先是loadtalk方法,给它更改了返回值类型为Panel:

public Panel loadtalk(int talknum,TalkMessage talkMessage)
{
//不写这个会报错
//在某个线程上创建的控件不能成为在另一个线程上创建的控件的父级

if (this.InvokeRequired)
{
getTalkPanel a = loadtalk;
object o = this.Invoke(a,talknum,talkMessage);
return (Panel)o;
}
//模拟加载会话列表
Panel talkPanel = new Panel();
talkPanel.Name = talkMessage.Account;
talkPanel.Anchor = ((AnchorStyles.Top | AnchorStyles.Left)
| AnchorStyles.Right);
talkPanel.Size = new Size(346, 58);
talkPanel.Location = new Point(3, talknum*58);
talkPanel.MouseEnter += TalkPanel_MouseEnter;
talkPanel.MouseLeave += TalkPanel_MouseLeave;
talkPanel.Click += TalkPanel_Click;
talkPanel.DoubleClick += TalkPanel_DoubleClick;
//头像
PictureBox pb = new PictureBox();
pb.Size = new Size(35, 35);
pb.Location = new Point(8, 11);
pb.Image = Properties.Resources.知了;
pb.SizeMode = PictureBoxSizeMode.StretchImage;
pb.MouseEnter += TalkPanel_MouseEnter;
pb.MouseLeave += TalkPanel_MouseLeave;
talkPanel.Controls.Add(pb);
//昵称
Label nickname = new Label();
nickname.AutoSize = true;
nickname.Font = new Font("微软雅黑", 12F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(134)));
nickname.Location = new Point(56, 10);
nickname.Text = talkMessage.NickName;
nickname.MouseEnter += TalkPanel_MouseEnter;
nickname.MouseLeave += TalkPanel_MouseLeave;
talkPanel.Controls.Add(nickname);
//消息
Label message = new Label();
message.Name = "messageName";
message.AutoSize = true;
message.ForeColor = SystemColors.ControlDarkDark;
message.Location = new Point(58, 34);
message.Size = new Size(277, 15);
message.Text = talkMessage.SubMessage;
message.MouseEnter += TalkPanel_MouseEnter;
message.MouseLeave += TalkPanel_MouseLeave;
talkPanel.Controls.Add(message);
//时间
Label time = new Label();
time.Anchor = ((AnchorStyles)((AnchorStyles.Top | AnchorStyles.Right)));
time.AutoSize = true;
time.ForeColor = SystemColors.ControlDarkDark;
time.Location = new Point(220, 11);
if (talknum > 5)
{
time.Location = new Point(204, 11);
}
time.Text = talkMessage.Time;
time.MouseEnter += TalkPanel_MouseEnter;
time.MouseLeave += TalkPanel_MouseLeave;
talkPanel.Controls.Add(time);


leftPanel.Controls.Add(talkPanel);
return talkPanel;
}

这里要注意的是前面几行,从子线程中对主线程的控件进行操作会报这个错误,使用委托可以解决,需要在类外面定义好委托的方法(委托我也是现学的大笑):

public delegate Panel getTalkPanel(int talknum, TalkMessage talkMessage);

一定要注意写的位置,是在Major类上方,介于类和namespace命名空间之间。

接着是Common.existedTalk的意思,考虑到后面接收消息、显示消息的功能,我们需要在Common类中添加几个字段。目前的Common类如下:

class Common
{
//全局socket
public static Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//对发送方法进行简单封装
public static void sandMsg(string str)
{
socket.Send(Encoding.UTF8.GetBytes(str));
}
//全局用户账号
public static string Account;
//普通消息分隔符
public static string splitFlag = "&^%$#";
//验证消息分隔符
public static string splitConfirmFlag = "#$%^&";
//用于验证某一个会话是否存在于会话列表的list 全部用Account属性标记
public static Dictionary existedTalk = new Dictionary();
//用于验证某一个chat窗体是否存在的list 全部用Account属性标记
public static List existedChatForm = new List();
//用于验证某个账号是否有新消息的list 全部用Account属性标记
public static List haveNewMsg = new List();
//存储主界面,方便其他界面调用
public static Major majorForm;
}

具体的意思代码中都有详细的注释。

接着是刷新会话列表的方法:

public void refreshTalkList()
{
int i = Common.existedTalk.Values.Count;
foreach (var item in Common.existedTalk.Values)
{
item.Location = new Point(3, (--i) * 58);
}
}

在这里就用到了Common类中定义的existedTalk字典,从里面拿到所有的会话panel,然后调整它们的位置即可。

接着是loadConfirmTalk方法,其实它和loadTalk是一样的嘿嘿。方法体如下:

private Panel loadConfirmTalk(TalkMessage tm)
{
return loadtalk(0,tm);
}

到这里就已经实现再界面上显示出一个验证消息的会话列表了,如接收方法中写的一样,还需要存储验证消息记录到本地文件中。

会话列表

这是存储到本地文件中的方法:

private void saveConfirmMsg(object arr)
{
string[] msgarr = (string[])arr;
saveConfirmMsgToFile(msgarr[0], getNickName(msgarr[0]), msgarr[1], 0);
}

private void saveConfirmMsgToFile(string account, string nickName, string msg, int state)
{
StreamWriter sw = new StreamWriter(confirmFilePath,true);
sw.WriteLine(account + Common.splitConfirmFlag + nickName +

Common.splitConfirmFlag + msg + Common.splitConfirmFlag + state);
sw.Flush();
sw.Close();
}

这里的confirmFilePath是定义的字段:

private string confirmFilePath = "confirm.db";

存储的方式是按行存储,将一条验证消息记录保存在一个文件中的某一行中。可以看到参数有用户账号、验证状态等信息。当后面我们点击同意后会根据账号找到这一行数据,然后将这一行数据的状态修改即可。这里的状态我们用三种状态表示验证动作:0表示未处理的验证消息,1表示同意验证,2表示拒绝验证,3表示忽略验证。这个是在Confirm窗体中实现验证消息反馈功能时定义的。

验证消息

好了,到目前为止,我们就已经能够实现接收、存储验证消息了,而且还能显示一个验证消息的会话。双击验证会话可以弹出Confirm验证消息窗口。

Confirm验证消息窗体的初始化及本地反馈实现

我们打开验证消息时,要根据验证消息文件中的信息接在验证消息列表,而且要实现上面的同意、拒绝、忽略三个反馈按钮的点击事件。

我们写了个方法进行初始化Confirm窗体:

private void initUIfromFile()
{
List list = fileToList();
for (int i = 0; i < list.Count; i++)
{
add_panel(list[i][0],list[i][1],list[i][2],int.Parse(list[i][3]));
}
}

接着是fileToList方法,该方法从文件中获取验证消息并装到list中:

private List fileToList()
{
List list = new List();
StreamReader sr = new StreamReader(confirmFilePath);
string line = "";
while ((line = sr.ReadLine()) != null)
{
string[] arr = line.Split(new string[] { Common.splitConfirmFlag }, StringSplitOptions.None);
list.Add(arr);
}
sr.Close();
return list;
}

后面的add_panel方法之前已经写过了,下面是上面三个按钮的点击事件:

忽略按钮的点击事件:

private void Ignore_btn_MouseClick(object sender, MouseEventArgs e)
{
string account = ((Button)sender).Name.Substring(10);
Control.ControlCollection cc = ((Button)sender).Parent.Controls;
foreach (Control c in cc)
{
if(c is Button)
{
c.Visible = false;
}
if(c is Label)
{
if(c.Name == "state_lbl")
{
c.Text = "已忽略";
c.Visible = true;
//修改本地文件中的state
updateFileConfirmState(account,3);
}
}
}
}

拒绝按钮的点击事件:

private void Disagree_btn_MouseClick(object sender, MouseEventArgs e)
{
string account = ((Button)sender).Name.Substring(12);
Control.ControlCollection cc = ((Button)sender).Parent.Controls;
foreach (Control c in cc)
{
if (c is Button)
{
c.Visible = false;
}
if (c is Label)
{
if (c.Name == "state_lbl")
{
c.Text = "已拒绝";
c.Visible = true;
//修改本地文件中的state
updateFileConfirmState(account, 2);
}
}
}
}

同意按钮的点击事件:

private void Agree_btn_MouseClick(object sender, MouseEventArgs e)
{
string account = ((Button)sender).Name.Substring(9);
Control.ControlCollection cc = ((Button)sender).Parent.Controls;
foreach (Control c in cc)
{
if (c is Button)
{
c.Visible = false;
}
if (c is Label)
{
if (c.Name == "state_lbl")
{
c.Text = "已同意";
c.Visible = true;

//修改本地文件中的state
updateFileConfirmState(account, 1);

}
}
}
}

接着是三个事件都涉及到的updateFileConfirmState方法,这个方法可以修改验证消息记录行中表示验证状态的字段:

private void updateFileConfirmState(string account,int state)
{
List list = fileToList();
for (int i = 0; i < list.Count; i++)
{
if(list[i][0] == account)
{
//修改这一行数据
list[i][3] = state.ToString();
}
}
listToFile(list);
}

这个修改的原理是先将全部的行读出到list,修改了某一行的数据之后,先把原来的文件删除,然后再将list中的数据重新写入。

反馈实现

好了,本篇文章就到这里,下一篇文件将实现接收验证消息的反馈并添加好友,然后在界面上显示出会话。

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值