winform项目——仿QQ即时通讯程序11:服务端程序

编程项目2019/12/21 20:30:32阅读:918

上一篇文章实现了注册和登录功能,其实只要能够登录的客户端程序,自登录的那一刻起就需要连接到服务器。所以本篇文章要编写CIM服务端程序,通信的原理在这一系列文章的第9篇已经介绍过,服务端程序就是从基础的通讯原理上针对CIM项目的业务逻辑进行扩展。

上一篇文章还有一点没有提到,就是对数据操作时,表中的数据类型不能为text,否则会报数据类型不兼容的错误:

报错

解决方法就是重新修改表中字段类型为varchar类型。

界面搭建

首先要搭建服务端程序的界面。界面布局如下图所示:

布局

布局中有一个TextBox和一个ListBox用于显示上下线记录和在线账号列表。label3用于显示当前在线人数。

首先我们需要添加五个字段作为全局变量:

//服务器IP
private string ip = "127.0.0.1";
//应用程序端口
private int port = 1234;
//用于监听的Socket对象
Socket listenSocket;

//用于保存连接到服务端socket的客户端socket,使用账号作为唯一标识
Dictionary dic_socket = new Dictionary();
//用于分隔账号和消息内容的分隔标志
//因为客户端发送消息时,必须指定发送的目标账号,所以需要用分隔标志将账号和内容分开
private string splitFlag = "&^%$#";

下面是“启动CIM服务”按钮的点击事件:

private void button1_Click(object sender, EventArgs e)
{
//1.new出socket对象
listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//2.绑定IP和端口
listenSocket.Bind(new IPEndPoint(IPAddress.Parse(ip), port));
//3.设置连接数量
listenSocket.Listen(1000);
//4.开启接收,新线程保证界面不卡
Thread acceptThread = new Thread(acceptClientSocket);
acceptThread.IsBackground = true;
acceptThread.Start();
//界面的提示信息
label1.Text = "已启动CIM服务!";
label1.ForeColor = Color.Green;
button1.Enabled = false;
}

然后是接收客户端连接的方法:

private void acceptClientSocket()
{
//死循环保证可以连接多个客户端socket
while (true)
{
//收到客户端连接后返回一个用于和客户端通信的socket对象 需要保存到字典中
Socket clientSocket = listenSocket.Accept();
byte[] buffer = new byte[1024];
//客户端连接上后,客户端必须主动发送自己的账号,所以这里需要接受这个账号
int len = clientSocket.Receive(buffer);
string clientAccount = Encoding.UTF8.GetString(buffer, 0, len);
//将用于和客户端通信的socket保存到字典中,客户端账号作为键,socket作为值
dic_socket.Add(clientAccount, clientSocket);
//界面的提示信息
upAnddown.AppendText("账号" + clientAccount + "在" + DateTime.Now.ToString() + "上线\r\n");
label3.Text = (int.Parse(label3.Text)+1).ToString();
online_list.Items.Add(clientAccount.ToString());


//新增线程用于客户端socket接收消息和转发消息
Thread receiveThread = new Thread(receiveAndsend);
receiveThread.IsBackground = true;
object Account = clientAccount;
//传送的一个参数必须是object类型
receiveThread.Start(Account);
}
}

具体过程代码中有详细的注释。接着是接收消息、转发消息的方法:

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);
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,Time)

values ('" + toAccount + "','" + fromAccount + "','" + content + "','" + DateTime.Now.ToString() + "')");
}
}
}
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);
}
}
}

到这里基本上就已经实现了CIM服务端程序,注意别忘了添加CheckForIllegalCrossThreadCalls = false;然后我们就可以接着上一篇文章,在登录后进行连接客户端操作。至于“关闭CIM服务”按钮的点击事件,目前不做,因为现在还没必要关闭服务,直接关上程序就行了。

登录成功后连接客户端

1.因为客户端socket在登录后,在软件关闭前一直是同一个,所以我们需要将这个socket变成在软件的所有界面都能够使用的方式,因此,我们定义一个Common.cs公共类,用于存放软件生存周期的全局变量。右键CIM项目中的Code文件夹,添加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;
}

这是目前能够用到的功能变量,后面有需要的时候,可以再添加。

接着我们可以在登录成功后,连接服务器。如下:

//连接服务器
Common.socket.Connect("127.0.0.1", 1234);
//向服务器发送账号
Common.sandMsg(tb_Account.Text);
Common.Account = tb_Account.Text;
this.Visible = false;
new Major().Show();

在new出主窗体之前,连接服务器。然后我们修改一下主界面Major窗体的关闭事件,因为隐藏了Login窗体后,关闭主窗体,程序时不会退出的。在Major关闭事件中写Application.exit();即可完全退出程序。

好了,到目前为止,我们就已经实现了服务端程序,并且实现了客户端连接服务器。下面看一下效果:

首先运行服务端程序,点击启动CIM服务。

服务器

然后运行客户端程序,按照正常步骤登录。登录成功后,可以看到服务端程序已经有了响应。

服务器响应了

然后关闭客户端程序,服务器也会记录下线操作。

记录

好了,本篇文章就到这里,下一篇文章将实现用户登录后Major主界面的初始化操作以及添加好友等。

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