上一篇文章实现了注册和登录功能,其实只要能够登录的客户端程序,自登录的那一刻起就需要连接到服务器。所以本篇文章要编写CIM服务端程序,通信的原理在这一系列文章的第9篇已经介绍过,服务端程序就是从基础的通讯原理上针对CIM项目的业务逻辑进行扩展。
上一篇文章还有一点没有提到,就是对数据操作时,表中的数据类型不能为text,否则会报数据类型不兼容的错误:
![6f584e59d80764077ce1afbc83be27ed.png](https://i-blog.csdnimg.cn/blog_migrate/3038104a8389d732d336457b053ac361.jpeg)
解决方法就是重新修改表中字段类型为varchar类型。
界面搭建
首先要搭建服务端程序的界面。界面布局如下图所示:
![86d147564a911fc5698dd9cafffe6998.png](https://i-blog.csdnimg.cn/blog_migrate/4a97e82098276c3c83171bfe27ba3ca7.jpeg)
布局中有一个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() + "上线");
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() + "下线");
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服务。
![5694a5ffa9a150f3206b00764a177518.png](https://i-blog.csdnimg.cn/blog_migrate/3bdb2832cff2e891e561cd7e63d08924.jpeg)
然后运行客户端程序,按照正常步骤登录。登录成功后,可以看到服务端程序已经有了响应。
![e47c12cc1c55018eab25c1b9584cca51.png](https://i-blog.csdnimg.cn/blog_migrate/659d23cb3600fb3c27ac553d8b3361bb.jpeg)
然后关闭客户端程序,服务器也会记录下线操作。
![abb03af31732a17118354257409fa09e.png](https://i-blog.csdnimg.cn/blog_migrate/e68d9eb210e81193993d07b0e7bc8ce0.jpeg)
好了,本篇文章就到这里,下一篇文章将实现用户登录后Major主界面的初始化操作以及添加好友等。
本文系小博客网站原创,转载请注明文章链接地址