DotNetty系列六:将服务端和客户端改为Winform窗口,使用Redis做为缓存,实现用户登录,好友,群组上下线显示。
这次改动挺大的。
1.服务端和客户端改为Winform窗口。好多细节未处理,只是实现了功能。
2.使用Redis做为缓存,版本redis-3.0.1,和RedisDesktopManager做管理。增加二个类库,一个用于Redis数据实体,一个Redis操作和测试。
数据实体部份:
用户:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MessagePack;
namespace CommonModel
{
[MessagePackObject]
public class Users
{
public static string cacheKey = "Users";
[Key(0)]
public int ID { get; set; }
[Key(1)]
public string name { get; set; }
[Key(2)]
public string password { get; set; }
private DateTime? registered;
[Key(3)]
public DateTime? createtime
{
get
{
if (registered == null)
{
registered = DateTime.Now;
}
return registered.Value;
}
set { registered = value; }
}
public List<Users> ListDatas()
{
List<Users> users = new List<Users>
{
new Users{ ID=0,name="zs",password="zs" },
new Users{ ID=1,name="ls",password="ls" },
new Users{ ID=2,name="ww",password="ww" },
new Users{ ID=3,name="zl",password="zl" },
new Users{ ID=4,name="wb",password="wb" }
};
return users;
}
}
public class userFriends
{
/// <summary>
/// 好友ID
/// </summary>
public int friend_id { set; get; }
/// <summary>
/// 好友名称
/// </summary>
public string name { set; get; }
/// <summary>
/// 是否在线
/// </summary>
public bool isOnline { set; get; }
}
public class userGroups
{
public string group_id { set; get; }
public string group_name { set; get; }
public int user_id { set; get; }
public string user_name { set; get; }
/// <summary>
/// 是否在线
/// </summary>
public bool isOnline { set; get; }
}
}
好友:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CommonModel
{
public class Friends
{
public static string cacheKey = "Friends";
public int ID { get; set; }
public int user_id { get; set; }
public int friend_id { get; set; }
private DateTime? registered;
public DateTime? createtime
{
get
{
if (registered == null)
{
registered = DateTime.Now;
}
return registered.Value;
}
set { registered = value; }
}
public List<Friends> ListDatas()
{
List<Friends> friends = new List<Friends>
{
new Friends{ ID=0,user_id=0,friend_id=2 },
new Friends{ ID=1,user_id=0,friend_id=4 },
new Friends{ ID=3,user_id=1,friend_id=3 },
new Friends{ ID=4,user_id=1,friend_id=4 },
new Friends{ ID=5,user_id=2,friend_id=3 },
new Friends{ ID=6,user_id=2,friend_id=4 },
new Friends{ ID=7,user_id=2,friend_id=0 },
new Friends{ ID=8,user_id=3,friend_id=1 },
new Friends{ ID=9,user_id=3,friend_id=2 },
new Friends{ ID=10,user_id=3,friend_id=4 },
new Friends{ ID=11,user_id=4,friend_id=0 },
new Friends{ ID=12,user_id=4,friend_id=1 },
new Friends{ ID=13,user_id=4,friend_id=2 },
new Friends{ ID=14,user_id=4,friend_id=3 }
};
return friends;
}
}
}
群组:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CommonModel
{
public class Groups
{
public static string cacheKey = "Groups";
public int ID { get; set; }
public string group_id { get; set; }
public string group_name { get; set; }
public List<int> lUser { set; get; }
private DateTime? registered;
public DateTime? createtime
{
get
{
if (registered == null)
{
registered = DateTime.Now;
}
return registered.Value;
}
set { registered = value; }
}
public List<Groups> ListDatas()
{
List<Groups> groups = new List<Groups>
{
new Groups{ ID=0, group_id="gOne", group_name="一组", lUser=new List<int>{0,1,2 } },
new Groups{ ID=0, group_id="gTwo", group_name="二组", lUser=new List<int>{4,1,2 } },
new Groups{ ID=1, group_id="gThree", group_name="三组", lUser=new List<int>{3,4 } }
};
return groups;
}
}
}
Redis测试项目:用于将数据写入缓存,直接读取使用,直接运行测试,就将数据写入。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Cache.Redis;
using CommonModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace RedisUnitTest
{
[TestClass]
public class RedisTest
{
/// <summary>
/// 写入用户数据
/// </summary>
[TestMethod]
public void TestWriteUsersObject()
{
ICache cache = CacheFactory.Cache();
List<Users> u = new Users().ListDatas();
cache.Write(Users.cacheKey, u);
List<Users> temp = cache.Read<List<Users>>(Users.cacheKey);
var us = temp.Find(t => t.name.Equals("张三")&&t.password.Equals("zs"));
}
/// <summary>
/// 写入好友数据
/// </summary>
[TestMethod]
public void TestWriteFriendsObject()
{
ICache cache = CacheFactory.Cache();
List<Friends> u = new Friends().ListDatas();
cache.Write(Friends.cacheKey, u);
List<Friends> temp = cache.Read<List<Friends>>(Friends.cacheKey);
var us = temp.FindAll(t => t.user_id.Equals(4));
}
/// <summary>
/// 写入群组数据
/// </summary>
[TestMethod]
public void TestWriteGroupsObject()
{
ICache cache = CacheFactory.Cache();
List<Groups> u = new Groups().ListDatas();
cache.Write(Groups.cacheKey, u);
List<Groups> temp = cache.Read<List<Groups>>(Groups.cacheKey);
var us = temp.FindAll(t => t.ID.Equals(0));
}
/// <summary>
/// 测试删除数据
/// </summary>
[TestMethod]
public void TestRemove()
{
ICache cache = CacheFactory.Cache();
cache.Remove(Groups.cacheKey);
cache.RemoveAll();
}
}
}
。。。。Redis类库,代码太多,这里省略。
3.用户好友,群组,直接写入缓存读取使用。
using Cache.Redis;
using CommonModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace WinServer.UserInfo
{
public class GetUser
{
ICache cache = CacheFactory.Cache();
public Users GetUserById(int id) => cache.Read<List<Users>>(Users.cacheKey).Find(t => t.ID == id);
public Users UserLogin(Users temp)
{
List<Users> users = cache.Read<List<Users>>(Users.cacheKey);
Users self = users.Find(t => t.name.Equals(temp.name) && t.password.Equals(temp.password));
return self;
}
public IEnumerable<userFriends> GetFriendsByID(int id)
{
List<Users> users = cache.Read<List<Users>>(Users.cacheKey);
var lf = cache.Read<List<Friends>>(Friends.cacheKey).FindAll(t => t.user_id.Equals(id)).Select(
s => new userFriends
{
friend_id = s.friend_id,
name = users.Find(u => u.ID.Equals(s.friend_id)).name
});
return lf;
}
public IEnumerable<userGroups> GetGroupsByID(int id)
{
List<Users> users = cache.Read<List<Users>>(Users.cacheKey);
List<Groups> gtem = cache.Read<List<Groups>>(Groups.cacheKey).FindAll(t => t.lUser.Contains(id));
List<userGroups> ldGroup = new List<userGroups>();
gtem.ForEach(o =>
{
o.lUser.ForEach(oo =>
{
var user = users.FindAll(b => b.ID.Equals(oo)).Select(r =>
new userGroups
{
group_id = o.group_id,
group_name = o.group_name,
user_id = r.ID,
user_name = r.name
});
ldGroup.AddRange(user);
});
});
return ldGroup;
}
/// <summary>
/// 根据用户ID得到用户群组内用户列表
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public List<int> GetGroupMembersByUserId(int id)
{
List<Groups> gtem = cache.Read<List<Groups>>(Groups.cacheKey).FindAll(t => t.lUser.Contains(id));
List<int> lGUs = new List<int>();
if (gtem.Count > 0)
{
gtem.ForEach(s => lGUs.AddRange(s.lUser));
lGUs = lGUs.Distinct().ToList();
lGUs.RemoveAll(j => j.Equals(id));
}
return lGUs;
}
}
}
4.好友,群组内组员,服务端广播,客户端上下线变红变灰。
服务端:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using CommonLib;
using CommonModel;
using DotNetty.Handlers.Timeout;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Groups;
namespace WinServer
{
public class ServerHandler : ChannelHandlerAdapter, IDisposable
{
public event CommandReceiveEvent MessageReceived;
public event MessageSendEvent MessageSend;
public event MessageGroupSendEvent MessageGroupSend;
public event MessageOfflineEvent MessageOffline;
public IChannelHandlerContext _Socket { get; set; }
private void OnMessageReceive(Message msg) => MessageReceived?.Invoke(this, new MessageEventArgs(msg));
private void OnMessageSend(Message msg) => MessageSend?.Invoke(this, new MessageEventArgs(msg));
private void OnMessageGroupSend(Message msg) => MessageGroupSend?.Invoke(this, new MessageEventArgs(msg));
private void OnMessageOffline(int Uid) => MessageOffline?.Invoke(this, Uid);
public void AddUser(Users temp)
{
AllClients.AddOrUpdate(temp.ID, _Socket.Channel, (k, v) => v);
}
/// <summary>
/// 得到该用户所有好友在线状态
/// </summary>
/// <param name="fs"></param>
/// <returns></returns>
public List<userFriends> GetAllFriends(List<userFriends> fs)
{
Action<userFriends> IsOnline = s =>
{
if (AllClients.ContainsKey(s.friend_id))
{
s.isOnline = true;
}
};
fs.ForEach(IsOnline);
return fs;
}
/// <summary>
/// 得到该用户所在群组内的用户在线状态
/// </summary>
/// <param name="fs"></param>
/// <returns></returns>
public List<userGroups> GetGroupsUsers(List<userGroups> fs)
{
Action<userGroups> IsOnline = s =>
{
if (AllClients.ContainsKey(s.user_id))
{
s.isOnline = true;
}
};
fs.ForEach(IsOnline);
return fs;
}
/// <summary>
/// 给在线好友广播发放此用户上线,下线
/// </summary>
/// <param name="uf"></param>
/// <param name="ms"></param>
public void SendOnlineNotifyToFriends<T>(IEnumerable<userFriends> uf, T obj)
{
try
{
uf.ToList().ForEach(async s =>
{
if(AllClients.ContainsKey(s.friend_id))
{
await AllClients[s.friend_id].WriteAndFlushAsync(obj);
}
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
/// <summary>
/// 给用户所在所有群组内在线成员发送,上线通知
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="lgus"></param>
/// <param name="obj"></param>
public void SendOnlineNotifyToGroupsUsers<T>(List<int> lgus,T obj)
{
try
{
lgus.ForEach(async s =>
{
if (AllClients.ContainsKey(s))
{
await AllClients[s].WriteAndFlushAsync(obj);
}
});
}
catch (Exception ex) { }
}
/// <summary>
/// 单发数据
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
public async void SendData<T>(T obj)
{
try
{
await _Socket.WriteAndFlushAsync(obj);
}
catch (Exception ex) { }
}
/// <summary>
/// 群发数据
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
public void GroupSendData<T>(T obj)
{
try
{
AllClients.Values.ToList().ForEach(async s => await s.WriteAndFlushAsync(obj));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private int lossConnectCount = 0;
public override async void UserEventTriggered(IChannelHandlerContext context, object evt)
{
await Task.Run(() =>
{
//已经15秒未收到客户端的消息了!
if (evt is IdleStateEvent eventState)
{
if (eventState.State == IdleState.ReaderIdle)
{
lossConnectCount++;
if (lossConnectCount > 2)
{
//("关闭这个不活跃通道!");
context.CloseAsync();
}
}
}
else
{
base.UserEventTriggered(context, evt);
}
});
}
public override bool IsSharable => true;//标注一个channel handler可以被多个channel安全地共享。
// 重写基类的方法,当消息到达时触发,这里收到消息后,在控制台输出收到的内容,并原样返回了客户端
public override async void ChannelRead(IChannelHandlerContext context, object message)
{
await Task.Run(() =>
{
if (message is Message oo)
{
OnMessageReceive(oo);
}
});
}
static volatile ConcurrentDictionary<int, IChannel> AllClients = new ConcurrentDictionary<int, IChannel>();
//客户端连接进来时
public override async void HandlerAdded(IChannelHandlerContext context)
{
await Task.Run(() =>
{
//Console.WriteLine($"客户端{context}上线.");
base.HandlerAdded(context);
//AllClients.AddOrUpdate((context.Channel.RemoteAddress as IPEndPoint).Port, context.Channel, (k, v) => v);
});
}
//客户端下线断线时
public override async void HandlerRemoved(IChannelHandlerContext context)
{
await Task.Run(() =>
{
//Console.WriteLine($"客户端{context}下线.");
base.HandlerRemoved(context);
var key = AllClients.Where(q => q.Value == context.Channel).SingleOrDefault();//.Select(q => q.Key); //get all keys
AllClients.TryRemove(key.Key, out IChannel temp);
//给好友广播下线通知
OnMessageOffline(key.Key);
//AllClients.TryRemove((context.Channel.RemoteAddress as IPEndPoint).Port, out IChannel temp);
//Message ms = new Message { Command = COMMAND.Message, Content = $"恭送{context.Channel.RemoteAddress}离开." };
//OnMessageGroupSend(ms);
});
}
//服务器监听到客户端活动时
public override async void ChannelActive(IChannelHandlerContext context)
{
_Socket = context;//赋值
await Task.Run(() =>
{
//Console.WriteLine($"客户端{context.Channel.RemoteAddress}在线.");
base.ChannelActive(context);
});
}
//服务器监听到客户端不活动时
public override async void ChannelInactive(IChannelHandlerContext context)
{
await Task.Run(() =>
{
//Console.WriteLine($"客户端{context.Channel.RemoteAddress}离线了.");
base.ChannelInactive(context);
});
}
// 输出到客户端,也可以在上面的方法中直接调用WriteAndFlushAsync方法直接输出
public override async void ChannelReadComplete(IChannelHandlerContext context) => await Task.Run(() => { context.Flush(); });
//捕获 异常,并输出到控制台后断开链接,提示:客户端意外断开链接,也会触发
public override async void ExceptionCaught(IChannelHandlerContext context, Exception exception)
{
await Task.Run(() =>
{
//Console.WriteLine("异常: " + exception);
context.CloseAsync();
});
}
public async void Dispose()
{
await _Socket.DisconnectAsync();
await _Socket.CloseAsync();
}
}
}
服务端页面:
using Cache.Redis;
using CommonLib;
using CommonModel;
using DotNetty.Codecs;
using DotNetty.Handlers.Timeout;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using WinServer.UserInfo;
namespace WinServer
{
public partial class ChatServer : Form
{
IChannel boundChannel;
// 主工作线程组,设置为1个线程
MultithreadEventLoopGroup bossGroup = new MultithreadEventLoopGroup(1);
// 工作线程组,默认为内核数*2的线程数
MultithreadEventLoopGroup workerGroup = new MultithreadEventLoopGroup();
ServerBootstrap bootstrap;
static ServerHandler serverHandler;
List<Users> us = new List<Users>();
public ChatServer()
{
InitializeComponent();
}
private async void btn_ConnectServer_Click(object sender, EventArgs e)
{
if (boundChannel == null)
{
await RunServerAsync();
}
}
private async void btn_DisConnectServer_ClickAsync(object sender, EventArgs e)
{
//关闭服务
if (boundChannel != null)
{
await boundChannel.CloseAsync();
await Task.WhenAll(
bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),
workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)));
boundChannel = null;
bootstrap = null;
//serverHandler.Dispose();
//bootstrap = null;
}
}
private async Task RunServerAsync()
{
try
{
//声明一个服务端Bootstrap,每个Netty服务端程序,都由ServerBootstrap控制,
//通过链式的方式组装需要的参数
bootstrap = new ServerBootstrap();
bootstrap
.Group(bossGroup, workerGroup) // 设置主和工作线程组
.Channel<TcpServerSocketChannel>() // 设置通道模式为TcpSocket
.Option(ChannelOption.SoBacklog, 100) // 设置网络IO参数等,这里可以设置很多参数,当然你对网络调优和参数设置非常了解的话,你可以设置,或者就用默认参数吧
.Option(ChannelOption.SoKeepalive, true)//保持连接
.Option(ChannelOption.RcvbufAllocator, new AdaptiveRecvByteBufAllocator(1024, 1024, 65536))
.ChildHandler(new ActionChannelInitializer<ISocketChannel>(channel =>
{
//工作线程连接器 是设置了一个管道,服务端主线程所有接收到的信息都会通过这个管道一层层往下传输
//同时所有出栈的消息 也要这个管道的所有处理器进行一步步处理
IChannelPipeline pipeline = channel.Pipeline;
//实体类编码器
pipeline.AddLast(new CommonEncoder<CommonLib.Message>());
pipeline.AddLast(new CommonDecoder());
pipeline.AddLast("framing-enc", new LengthFieldPrepender(4, false));
pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 4, 0, 4));
// IdleStateHandler 心跳
//服务端为读IDLE
pipeline.AddLast(new IdleStateHandler(180, 0, 0));//第一个参数为读,第二个为写,第三个为读写全部
//业务handler ,这里是实际处理业务的Handler
serverHandler = new ServerHandler
{
_Socket = pipeline.FirstContext()
};
serverHandler.MessageReceived += ServerHandler_MessageReceived;
serverHandler.MessageSend += ServerHandler_MessageSend;
serverHandler.MessageGroupSend += ServerHandler_MessageGroupSend;
serverHandler.MessageOffline += ServerHandler_MessageOffline; ;
pipeline.AddLast(serverHandler);
}));
// bootstrap绑定到指定端口的行为 就是服务端启动服务,同样的Serverbootstrap可以bind到多个端口
boundChannel = await bootstrap.BindAsync(3399);
//Console.WriteLine("服务启动");
}
catch (Exception ex)
{
string me = ex.Message;
}
finally
{
//释放工作组线程
//await Task.WhenAll(
// bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),
// workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)));
}
}
private async void ServerHandler_MessageOffline(object sender, int e)
{
try
{
var client = sender as ServerHandler;
await Task.Run(() =>
{
GetUser gu = new GetUser();
//用户好友列表
List<userFriends> fs = gu.GetFriendsByID(e).ToList();
//得到好友在线状态
fs = client.GetAllFriends(fs);
//给在线好友广播发放此用户上线
var online = fs.Where(s => s.isOnline);
if (online.Count() > 0)
{
var ms = new CommonLib.Message
{
Command = COMMAND.ServerToClient,
EchatMode = ChatMode.GetInfo,
Eoperation = Operation.OfflineNotify,
strJson = JsonHelper.SerializeObject(e)
};
client.SendOnlineNotifyToFriends(online, ms);
}
UpdateUI.SetServerOffline(lb_UserList, e);
us.RemoveAll(j => j.ID.Equals(e));
//给用户群组广播发送此用户下线
//在线群组
var lgus = gu.GetGroupMembersByUserId(e);
//给群组内成员发送上线通知
var gms = new CommonLib.Message
{
Command = COMMAND.ServerToClient,
EchatMode = ChatMode.GetInfo,
Eoperation = Operation.GroupsUserOfflineNotify,
strJson = JsonHelper.SerializeObject(e)
};
client.SendOnlineNotifyToGroupsUsers(lgus, gms);
});
}
catch (Exception ex) { }
}
private async void ServerHandler_MessageGroupSend(object sender, MessageEventArgs e)
{
try
{
var clients = sender as ServerHandler;
await Task.Run(() => { clients.GroupSendData(e.CMD); });
}
catch (Exception ex) { }
}
private async void ServerHandler_MessageSend(object sender, MessageEventArgs e)
{
try
{
var client = sender as ServerHandler;
await Task.Run(() => { client.SendData(e.CMD); });
}
catch (Exception ex) { }
}
private async void ServerHandler_MessageReceived(object sender, MessageEventArgs e)
{
try
{
var client = sender as ServerHandler;
await Task.Run(() =>
{
switch (e.CMD.Command)
{
case COMMAND.ClientToServer:
switch ((e.CMD as CommonLib.Message).Eoperation)
{
case Operation.UserLogin:
Users temp = JsonHelper.DeserializeJsonToObject<Users>((e.CMD as CommonLib.Message).strJson);
GetUser gu = new GetUser();
var self = gu.UserLogin(temp);
if (self is null)
{
ServerHandler_MessageSend(sender, new MessageEventArgs(new CommonLib.Message
{
Command = COMMAND.ServerToClient,
EchatMode = ChatMode.Error,
Eoperation = Operation.UserLogin,
Content = "无此用户或者用户密码错误"
}));
Thread.Sleep(1000);
client._Socket.CloseAsync();
}
else
{
//添加用户信息
client.AddUser(self);
//服务端列表显示该用户
us.Add(self);
UpdateUI.SetText(lb_UserList, us);
//Application.DoEvents();
//用户信息
ServerHandler_MessageSend(sender, new MessageEventArgs(new CommonLib.Message
{
Command = COMMAND.ServerToClient,
EchatMode = ChatMode.GetInfo,
Eoperation = Operation.UserLogin,
Content = "登录成功",
strJson = JsonHelper.SerializeObject(self)
}));
//用户好友列表
List<userFriends> fs = gu.GetFriendsByID(self.ID).ToList();
//得到好友在线状态
fs = client.GetAllFriends(fs);
Thread.Sleep(1000);
ServerHandler_MessageSend(sender, new MessageEventArgs(new CommonLib.Message
{
Command = COMMAND.ServerToClient,
EchatMode = ChatMode.GetInfo,
Eoperation = Operation.GetFriends,
strJson = JsonHelper.SerializeObject(fs)
}));
//给在线好友广播发放此用户上线
var online = fs.Where(s => s.isOnline);
if (online.Count() > 0)
{
var ms = new CommonLib.Message
{
Command = COMMAND.ServerToClient,
EchatMode = ChatMode.GetInfo,
Eoperation = Operation.OnlineNotify,
strJson = JsonHelper.SerializeObject(self.ID)
};
client.SendOnlineNotifyToFriends(online, ms);
}
Thread.Sleep(1000);
//用户群组内组员列表
var gs = gu.GetGroupsByID(self.ID).ToList();
gs = client.GetGroupsUsers(gs);
ServerHandler_MessageSend(sender, new MessageEventArgs(new CommonLib.Message
{
Command = COMMAND.ServerToClient,
EchatMode = ChatMode.GetInfo,
Eoperation = Operation.GetGroups,
strJson = JsonHelper.SerializeObject(gs)
}));
//在线群组
var lgus = gu.GetGroupMembersByUserId(self.ID);
//给群组内成员发送上线通知
var gms = new CommonLib.Message
{
Command = COMMAND.ServerToClient,
EchatMode = ChatMode.GetInfo,
Eoperation = Operation.GroupsUserOnlineNotify,
strJson = JsonHelper.SerializeObject(self.ID)
};
client.SendOnlineNotifyToGroupsUsers(lgus, gms);
}
return;
default:
return;
}
case COMMAND.Message:
return;
default:
return;
}
});
}
catch (Exception ex) { }
}
}
}
客户端只改动页面部份:
using CommonLib;
using CommonModel;
using DotNetty.Handlers.Timeout;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using DotNetty.Codecs;
namespace WinClient
{
public partial class ChatForm : Form
{
private WaitForm wfDlg = new WaitForm();
MultithreadEventLoopGroup group = new MultithreadEventLoopGroup();
IChannel clientChannel;
Users onlineUser;
string userName = string.Empty, Password = string.Empty;
public ChatForm()
{
InitializeComponent();
}
private void InterChatMenuItem_Click(object sender, EventArgs e)
{
UpdateUI.SetText(lb_conection, "登录");
LoginForm loginForm = new LoginForm();
if(loginForm.ShowDialog()==DialogResult.OK)
{
userName = loginForm.txtUserName.Text;
Password = loginForm.txtPassword.Text;
loginForm.Close();
Task.Run(() => RunClientAsync());
}
txtChatContent.Focus();
Application.DoEvents();
//wfDlg.ShowDialog();
}
private async void OutInterChatMenuItem_ClickAsync(object sender, EventArgs e)
{
if (clientChannel != null)
{
await clientChannel.CloseAsync();
await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));
UpdateUI.SetText(lb_conection, "就绪");
}
}
private async Task RunClientAsync()
{
try
{
var bootstrap = new Bootstrap();
bootstrap
.Group(group)
.Channel<TcpSocketChannel>()
.Option(ChannelOption.TcpNodelay, true)
.Option(ChannelOption.RcvbufAllocator,new AdaptiveRecvByteBufAllocator(1024,1024,65536))
.Handler(new ActionChannelInitializer<ISocketChannel>(c =>
{
IChannelPipeline pipeline = c.Pipeline;
//实体类编码器
pipeline.AddLast(new CommonEncoder<CommonLib.Message>());
pipeline.AddLast(new CommonDecoder());
pipeline.AddLast("framing-enc", new LengthFieldPrepender(4, false));
pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 4, 0, 4));
// IdleStateHandler 心跳
//客户端为写IDLE
pipeline.AddLast(new IdleStateHandler(0, 180, 0));//第一个参数为读,第二个为写,第三个为读写全部
ClientHandler clientHandler = new ClientHandler
{
_Socket = pipeline.FirstContext()
};
clientHandler.MessageReceived += ClientHandler_MessageReceived;
clientHandler.MessageSend += ClientHandler_MessageSend;
pipeline.AddLast(clientHandler);
}));
clientChannel = await bootstrap.ConnectAsync(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 3399));
UpdateUI.SetText(lb_conection, "连接至服务器");
}
catch (Exception ex)
{
UpdateUI.SetText(lb_conection, "无法连接服务器");
}
finally
{
//await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));
}
}
private async void ClientHandler_MessageSend(object sender, MessageEventArgs e)
{
try
{
var client = sender as ClientHandler;
await Task.Run(() =>
{
switch (e.CMD.Command)
{
case COMMAND.ClientToServer:
CommonLib.Message ms = e.CMD as CommonLib.Message;
ms.strJson = JsonHelper.SerializeObject(new Users { name = userName, password = Password });
client.SendData(ms);
break;
default:
client.SendData(e.CMD);
break;
}
});
}
catch (Exception ex) { }
}
private async void ClientHandler_MessageReceived(object sender, MessageEventArgs e)
{
try
{
await Task.Run(() =>
{
var client = sender as ClientHandler;
var ms = e.CMD as CommonLib.Message;
switch (ms.Command)
{
case COMMAND.ServerToClient:
switch (ms.Eoperation)
{
case Operation.UserLogin:
if(ms.EchatMode.Equals(ChatMode.Error))
{
UpdateUI.SetText(lb_conection, $"{ms.Content}");
//Application.DoEvents();
}
else
{
onlineUser = JsonHelper.DeserializeJsonToObject<Users>(ms.strJson);
UpdateUI.SetText(lb_conection, $"{onlineUser.name}");
//Application.DoEvents();
}
return;
case Operation.GetFriends:
List<userFriends> us = JsonHelper.DeserializeJsonToObject<List<userFriends>>(ms.strJson).OrderByDescending(s => s.isOnline).ToList();
UpdateUI.SetText(lbUserFriends, us);
//Application.DoEvents();
return;
case Operation.GetGroups:
List<userGroups> gs = JsonHelper.DeserializeJsonToObject<List<userGroups>>(ms.strJson).OrderByDescending(s => s.isOnline).ToList();
UpdateUI.SetTreeView(tv_groups, gs);
//Application.DoEvents();
return;
case Operation.OnlineNotify:
string uId = JsonHelper.DeserializeJsonToObject<string>(ms.strJson);
UpdateUI.SetOnlineOffline(lbUserFriends,ms.Eoperation, uId);
//Application.DoEvents();
return;
case Operation.OfflineNotify:
string uuId = JsonHelper.DeserializeJsonToObject<string>(ms.strJson);
UpdateUI.SetOnlineOffline(lbUserFriends, ms.Eoperation, uuId);
//Application.DoEvents();
return;
case Operation.GroupsUserOnlineNotify:
string guId = JsonHelper.DeserializeJsonToObject<string>(ms.strJson);
UpdateUI.SetGroupsUsersOnlineOffline(tv_groups, ms.Eoperation, guId);
//Application.DoEvents();
return;
case Operation.GroupsUserOfflineNotify:
string guoId = JsonHelper.DeserializeJsonToObject<string>(ms.strJson);
UpdateUI.SetGroupsUsersOnlineOffline(tv_groups, ms.Eoperation, guoId);
//Application.DoEvents();
return;
default:
return;
}
default:
return;
}
});
}
catch (Exception ex) { }
}
private void lbUserFriends_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index >= 0)
{
var tem = (lbUserFriends.Items[e.Index] as userFriends);
e.DrawBackground();
Brush mybsh = Brushes.Black;
// 判断是什么类型的标签
if (tem.isOnline)
{
mybsh = Brushes.Red;
}
else
{
mybsh = Brushes.Gray;
}
// 焦点框
//e.DrawFocusRectangle();
//文本
e.Graphics.DrawString(tem.name, e.Font, mybsh, e.Bounds, StringFormat.GenericDefault);
}
}
private void bt_send_Click(object sender, EventArgs e)
{
var to = lbUserFriends.SelectedItem as userFriends;
if (to != null&& to.isOnline)
{
var receiver = to as userFriends;
}
else
{
MessageBox.Show("此用户不在线");
}
}
}
}
5.改动自定义协议。
/// <summary>
/// 基本通信协议
/// </summary>
public enum COMMAND
{
/// <summary>
/// 心跳
/// </summary>
HeartBeat = 1000,
/// <summary>
/// 消息
/// </summary>
Message = 1,
/// <summary>
/// 空消息
/// </summary>
NULL = 0,
/// <summary>
/// 客户端到服务器
/// </summary>
ClientToServer = 7,
/// <summary>
/// 客户端到客户端
/// </summary>
ClientToClient = 8,
/// <summary>
/// 服务器到客户端
/// </summary>
ServerToClient = 9,
/// <summary>
/// 异常捕获
/// </summary>
ExceptionCaught = 6,
}
/// <summary>
/// 协议里的操作内容
/// </summary>
public enum Operation
{
/// <summary>
/// 用户登陆
/// </summary>
UserLogin = 10,
/// <summary>
/// 得到帐户信息
/// </summary>
GetUser =11,
/// <summary>
/// 得到好友列表
/// </summary>
GetFriends = 0,
/// <summary>
/// 得到群组列表
/// </summary>
GetGroups = 1,
/// <summary>
/// 普通消息
/// </summary>
NormalMessage=2,
/// <summary>
/// 上线通知
/// </summary>
OnlineNotify = 1001,
/// <summary>
/// 下线通知
/// </summary>
OfflineNotify = 1002,
/// <summary>
/// 群组成员上线通知
/// </summary>
GroupsUserOnlineNotify = 1003,
/// <summary>
/// 群组成员下线通知
/// </summary>
GroupsUserOfflineNotify = 1004,
}
/// <summary>
/// 聊天模式
/// </summary>
public enum ChatMode
{
/// <summary>
/// 单聊
/// </summary>
SingleChat=0,
/// <summary>
/// 群聊
/// </summary>
GroupChat = 1,
/// <summary>
/// 私聊
/// </summary>
PrivateChat = 2,
/// <summary>
/// 得到信息
/// </summary>
GetInfo = 3,
/// <summary>
/// 发生错误
/// </summary>
Error = 5000,
}
6.还有杂七杂八的好多东西。代码太多,下载源码看吧。
7.完成后的效果。