上一篇我们简单的分析了微信平台的实现原理,由于博主的表达比较欠缺,说的比较简单,可能看得不是很明白,不过随着一步步进行或者查看源码,大家就会清楚的明白了。
好了,不废话,这一篇我们先来实现客户端。
客户端的灵魂是基于socket实现即时通讯,消息的收发都要通过它。
为了看起累直观明了,我们为这个客户端包装一下,也就是加个界面,让它看起来更像一个客户端。
本文用的是wpf来做的界面,当然也可以以用winform窗体,看个人了。
本客户端主要有以下几个界面:
文件结构如下:
1.登录/注册页面,为了简单,点击登陆就获取对应的账号密码登录,点击注册用的也是同样的输入框。
2.登陆后的界面,底部有三个导航按钮,默认显示的是会话历史记录页面
3.联系人页面
4.“发现”页面,用于搜索公众号或者用户的界面
下面主要来看一下客户端核心代码
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Windows; namespace wechat { class Client { private static Interface_client client; private static int port = 8885; private static string serverIP = "127.0.0.1"; private static Socket clientSocket; private static byte[] recResult = new byte[1024]; private static Thread mainthread,recivethread; private static bool connected = false; public Client(int port, string serverIP, Interface_client client) { Client.port = port; Client.serverIP = serverIP; Client.client = client; } public Client( Interface_client client) { Client.client = client; } public void Start() { mainthread = new Thread(start); mainthread.IsBackground = true; mainthread.Start(); } public void Stop() { mainthread.Abort(); } public void Send(string content) { try { clientSocket.Send(Encoding.UTF8.GetBytes(content)); } catch { } } private void start() { //GetIPAndPort(); IPAddress ip = IPAddress.Parse(serverIP); clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ////是客户端,同时也是服务器 //clientSocket.Bind(new IPEndPoint(ip, port)); //clientSocket.Listen(20); ConnectToServer(ip,client); } /* * * 从输入获取ip和端口号 * */ private static void GetIPAndPort() { Console.WriteLine("enter the IPAddress ,default is :{0}", serverIP); GetAndCheckIpAddress();//判断输入的字符串是否为ip Console.WriteLine("enter the PORT ,default is :{0}", port); string tempPORT = Console.ReadLine(); if (tempPORT.Length > 0) port = Convert.ToInt32(tempPORT); } //接入服务器 private static void ConnectToServer(IPAddress ip, Interface_client client) { try { clientSocket.Connect(new IPEndPoint(ip, port)); Console.WriteLine("connect to the server successful!"); connected = true; clientSocket.Send(Encoding.UTF8.GetBytes("newconnection|"+Model.username)); //开启消息和接收消息的线程 //client.getMessage(clientSocket); recivethread = new Thread(RecMessage); recivethread.IsBackground = true; recivethread.Start(client); } catch (Exception e) { connected = false; MessageBox.Show("与连接失败......"); Console.WriteLine(e.Message); //GetIPAndPort(); Thread.Sleep(30000); MessageBox.Show("正在尝试重新连接......"); ConnectToServer(ip,client); } } private static void SendMessage() { while (true) try { clientSocket.Send(Encoding.UTF8.GetBytes(Console.ReadLine())); } catch { } } private static void RecMessage(Object o) { Interface_client client = o as Interface_client; while (true) { try { client.getMessage(clientSocket); //int recLength = clientSocket.Receive(recResult); //getMessage(Encoding.UTF8.GetString(recResult, 0, recLength)); //Console.WriteLine("recive message from {0} : {1}", // clientSocket.RemoteEndPoint.ToString(), // Encoding.UTF8.GetString(recResult, 0, recLength)); } catch (Exception e) { connected = false; //Console.WriteLine(e.Message); MessageBox.Show("与服务器断开连接......"); //clientSocket.Shutdown(SocketShutdown.Both); //clientSocket.Close(); Thread.Sleep(30000); MessageBox.Show("正在尝试重新连接......"); //ConnectToServer(IPAddress.Parse(serverIP), client); clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); while (!connected) { try { clientSocket.Connect(new IPEndPoint(IPAddress.Parse(serverIP), port)); Console.WriteLine("connect to the server successful!"); connected = true; clientSocket.Send(Encoding.UTF8.GetBytes("newconnection|" + Model.username)); //开启消息和接收消息的线程 //client.getMessage(clientSocket); recivethread = new Thread(RecMessage); recivethread.IsBackground = true; recivethread.Start(client); } catch (Exception e1) { MessageBox.Show("与连接失败......"); Console.WriteLine(e1.Message); //GetIPAndPort(); Thread.Sleep(30000); MessageBox.Show("正在尝试重新连接......"); } } break; } } } private static void GetAndCheckIpAddress() { string tempIP = Console.ReadLine(); if (tempIP.Length <= 0) return; if (IsIpAddress(tempIP)) { serverIP = tempIP; } else { Console.WriteLine("the IP you‘ve enter is not right,please enter again ,default is :127.0.0.1"); GetAndCheckIpAddress(); } } private static bool IsIpAddress(string str) { string[] strings = str.Split(‘.‘); if (strings.Length != 4) return false; else { for (int i = 0; i < 4; i++) { string tempstring = strings[i]; if (tempstring == "") return false; int temp = Convert.ToInt32(tempstring); if (temp >= 0 && temp <= 255) continue; else return false; } } return true; } } }
这个是socket客户端类Client,这个类开了一个专门接收消息的接口,实际用的时候要传入实现了 interface_client 接口的类的对象
使用接口是为了能够根据情况的不懂,对接收到的数据进行不同的解析方式,而且不会乱。
using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Text; using System.Windows; namespace wechat { class MyClient : Interface_client { public delegate void ReciveMessageHandle(string content); public static ReciveMessageHandle ReciveMessage; private static byte[] recResult = new byte[2048]; string getdata; void Interface_client.getMessage(System.Net.Sockets.Socket clientSocket) { //while (true) //{ try { int recLength = clientSocket.Receive(recResult); getdata = Encoding.UTF8.GetString(recResult, 0, recLength); if (getdata.Length > 0) ReciveMessage(getdata); //MessageBox.Show(getdata); //getMessage(Encoding.UTF8.GetString(recResult, 0, recLength)); //Console.WriteLine("recive message from {0} : {1}", // clientSocket.RemoteEndPoint.ToString(), // Encoding.UTF8.GetString(recResult, 0, recLength)); } catch (Exception e) { Console.WriteLine(e.Message); clientSocket.Shutdown(SocketShutdown.Both); clientSocket.Close(); //break; } //} } } }
实现了interface_client 接口的类 MyClient ,实现了具体接收数据的方法,这里仅简单的接收字符串。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using BaseSqlHelper; using System.Data.SqlClient; using System.Data; using System.Xml; using System.IO; namespace wechat { class DBTool { static SqlHelper helper = new SqlHelper(@"Data Source=MENG\SQLEXPRESS;Initial Catalog=wechat;Integrated Security=True"); public static int register(string username,string pwd){ try{ return helper.ExecuteNonQuery("insert into users(username,pwd,usertype) values (@username,@pwd,@usertype)", new SqlParameter("@username", username), new SqlParameter("@pwd", pwd), new SqlParameter("@usertype", "0")); } catch{return 0;} } public static int login(string username,string pwd){ try{ return (int)helper.ExecuteScalar("select count(1) from users where [email protected] and pwd = @pwd", new SqlParameter("@username", username), new SqlParameter("@pwd", pwd)); } catch{return 0;} } public static DataTable search(string username, string findusername) { return helper.GetDataSet(@"select * ,type = case when usertype=‘0‘ then ‘普通用户‘ else ‘公众号‘ end from users where username != @username and username like ‘%‘[email protected]+‘%‘", new SqlParameter("@username", username), new SqlParameter("@findusername", findusername)).Tables[0]; } public static DataTable getuserinfo(string username,string viewusername) { return helper.GetDataSet(@"select * ,type = case when usertype=‘0‘ then ‘普通用户‘ else ‘公众号‘ end , (select COUNT(1) from friend where [email protected] and [email protected]) isfriend from users where username [email protected]", new SqlParameter("@username", username), new SqlParameter("@viewusername", viewusername)).Tables[0]; } public static int addfriend(string username, string viewusername) { try { return helper.ExecuteNonQuery("insert into friend(username,friendname) values (@username,@viewusername)", new SqlParameter("@username", username), new SqlParameter("@viewusername", viewusername)); } catch { return 0; } } public static int delfriend(string username, string viewusername) { try { return helper.ExecuteNonQuery("delete from friend where [email protected] and [email protected]", new SqlParameter("@username", username), new SqlParameter("@viewusername", viewusername)); } catch { return 0; } } public static DataTable getmyfriend(string username) { return helper.GetDataSet(@"select users.* ,[type] = case when users.usertype=‘0‘ then ‘普通用户‘ else ‘公众号‘ end from friend left join users on users.username = friend.friendname where friend.username = @username ", new SqlParameter("@username", username)).Tables[0]; } public static int addhistdialog(string username, string viewusername) { try { return helper.ExecuteNonQuery(@"if exists (select * from histdialog where fromusername [email protected] and tousername [email protected]) return else insert into histdialog(fromusername,tousername) values (@fromusername,@tousername)", new SqlParameter("@tousername", username), new SqlParameter("@fromusername", viewusername)); } catch { return 0; } } public static DataTable gethistdialog(string username) { return helper.GetDataSet(@" select users.* ,[type] = case when users.usertype=‘0‘ then ‘普通用户‘ else ‘公众号‘ end , (select count(1) from messages where messages.tousername=histdialog.tousername and messages.fromusername=histdialog.fromusername and readstate=‘0‘ )unread from histdialog left join users on users.username = histdialog.fromusername where histdialog.tousername = @username", new SqlParameter("@username", username)).Tables[0]; } public static int getunreadcount(string username) { try { return (int)helper.ExecuteScalar("select count(1) from messages where [email protected] and readstate=‘0‘", new SqlParameter("@username", username)); } catch { return 0; } } public static int setreaded(string username,string fromusername) { try { return (int)helper.ExecuteScalar("update messages set readstate=‘1‘ where [email protected] and [email protected]", new SqlParameter("@username", username), new SqlParameter("@fromusername", fromusername)); } catch { return 0; } } public static DataTable getmessageofdialog(string username,string fromusername) { return helper.GetDataSet(@"select * from messages where ([email protected] and fromusername [email protected]) or ([email protected] and tousername [email protected]) order by id asc", new SqlParameter("@username", username), new SqlParameter("@fromusername", fromusername)).Tables[0]; } public static Dictionary<string ,string> getxmlstring(string xmlcontent) { DataSet dsData = new DataSet(); Dictionary<string, string> dictionary = new Dictionary<string, string>(); dsData.ReadXml(new XmlTextReader(new StringReader(xmlcontent))); DataTable dt = dsData.Tables["HH"]; foreach (DataRow dr in dt.Rows) { foreach (DataColumn dc in dr.Table.Columns) { string n = dc.ColumnName; string value = dr[n].ToString(); dictionary.Add(n, value); } } return dictionary; } } }
DBTool类是对数据库数据操作的类(这里需要说明的是,在实际的这类客户端开发中,并不会直接的链接到数据库读写数据,
而是通过向服务器发送请求,服务器返回比如xml或者json之类的数据,客户端再解析,我猜也因该是这样的。这里为了偷懒,就除了通讯,其它的聊天记录,联系人等直接通过数据库获得)
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace wechat { class Model { public static string userid = ""; public static string username = ""; public static string viewusername = "0"; public static string ontalkingusername = ""; public static Client client; public static void SendMessage(string content) { client.Send(Model.username + "|" + Model.viewusername + "|" + content + "|" + DateTime.Now.ToString()); } } }
这个模型类是一个静态类,里边有一些静态属性,在这里用于保存一些数据用于传递数据,这个是本文所采用的解决方案,目前也不知道有什么好的办法在wpf里边传递数据,望知道的朋友不吝赐教。
if (DBTool.login(text_username.Text, text_pwd.Password) == 1) { //MessageBox.Show("登录成功!"); Model.username = text_username.Text; new MainWindow().Show(); this.Hide(); Model.client = new Client(new MyClient()); Model.client.Start(); }
客户端在用户登录成功后才开始链接到我们开发的服务器(后面会讲到),只有连接上了服务器才能进行通讯。
注意:这里的client 用静态类里的client 来保存,是为了我们在其它地方发送消息时直接使用静态类里的client 发送消息即可。
前台界面用了较多的委托事件来触发消息的提示和某些ui的刷新,这个在这里也不是一言两语说得清楚,还不清楚委托的朋友可以先了解一下再看代码,这样有利于理解。