控制台应用项目
Program.cs
cs
internal class Program
{
static Server server;
static void Main(string[] args)
{
Server server = new Server(IPAddress.Any,3333);
server.Start();// 除了服务器监听方法,监听客户连接的方法,扫描客户端是否在线的方法
//如果监听到有客户端连接的时候,打印哪个终端连入到服务器了 使用时间封装
server.有客户端连入的事件 += 有客户端连入服务器方法;// 绑定事件
server.客户端断开事件 += f2;
server.接受到消息的事件 += f3;
Console.ReadKey();
}
// 相当于点击之后的毁掉方法,在客户端连接成功之后调用这个方法
public static void 有客户端连入服务器方法(object obj)
{
TcpClient t1 = obj as TcpClient;
Console.WriteLine(t1.Client.RemoteEndPoint+"连接到服务器");
}
public static void f2(object obj)
{
Console.WriteLine(obj.ToString()+"断开连接");
}
public static void f3(TcpClient t1, byte[] b1)
{
t1.GetStream().Write(b1, 0, b1.Length);
}
}
Server.cs
cs
internal class Server
{
TcpListener listen;
// 1 通过构造函数创建服务器对象
public Server(IPAddress ip,int port)
{
listen = new TcpListener(ip, port);
}
// 2 封装开启监听的方法
public void Start()
{
listen.Start(100);// 开启监听
// 接受客户端的连接
StartConnect();
// 扫描心跳方法
SaoMiao();
}
// 3 接受客户端的连接 封装一监听客户端连接的方法
// 保存所有的客户端字典,键是ip 值是客户端
Dictionary<string, TcpClient> clientDic = new Dictionary<string, TcpClient>();
// 字段保存客户端和当前连接服务器时间点
Dictionary<string, DateTime> heartDic = new Dictionary<string, DateTime>();
public event Action<TcpClient> 有客户端连入的事件;
void StartConnect()
{
Task.Run(() =>
{
while (true)// 接入多个客户端
{
TcpClient client = listen.AcceptTcpClient();
string ip = client.Client.RemoteEndPoint.ToString();// 获取远程ip
// 保存当前客户端
clientDic.Add(ip, client);
// 记录当前客户端心跳 链接成功的时候记录当前客户端时间点
heartDic.Add(ip, DateTime.Now);
// 调用事件函数 触发事件
有客户端连入的事件?.Invoke(client);
// 4接受客户端发来的消息
ReceivMsg(client);
}
});
}
// 4 接受客户端发来的消息
// 封装接受的消息
public event Action<string> 客户端断开事件;// 当客户端断开时候调用
public event Action<TcpClient, byte[]> 接受到消息的事件;// 接收到消息调用
void ReceivMsg(TcpClient t1)
{
NetworkStream stream = t1.GetStream();
string ip = t1.Client.RemoteEndPoint.ToString();
byte[] bs = new byte[1024];
Task.Run(() =>
{
try
{
while (true)
{
int count = stream.Read(bs, 0, bs.Length);
// 必须判断是否是心跳包 事先约定好
if (count == 0)
{
// 客户端断开
throw new Exception("客户端断开连接");
}
// 如果接收数据长度不为0
// 必须判断是否心跳包 事先约定好:如果数据第一位是0的时候,当成普通包
// 如果数据第一位是1说明是心跳包
switch (bs[0]) // 判断第一位数据是不是0
{
case 0: // 普通数据 取出来的时候不需要显示第一位标识符
// skip 从第一位开始截取
// take 到指定位置的元素为止
// 第一位0、1代表是否心跳包标识符
//
byte[] body = bs.Skip(1).Take(count-1).ToArray();
// 要么群发 要么单发
接受到消息的事件?.Invoke(t1,body);
break;
case 1: // 发的是心跳包
// 修改是心跳包发的时间点
heartDic[ip] = DateTime.Now;
break;
}
}
}
catch (Exception e)
{
// 从字典里把客户端清除掉
clientDic.Remove(ip);
// 如果客户端打开了,打印客户
客户端断开事件?.Invoke(ip);
//删除心跳记录
heartDic.Remove(ip);
}
});
}
// 遍历所有客户端 扫描是否在未超时时间内
void SaoMiao()
{
Task.Run(() =>
{
while (true)
{
Thread.Sleep(4000);// 线程休眠4s
DateTime now1 = DateTime.Now;
foreach (var item in heartDic) // 遍历所以的心跳记录
{
// now1 当前时间点
// item.Value 服务器接受客户端发来的心跳包时间
if (now1-item.Value>new TimeSpan(0,0,4))
{
Console.WriteLine(item.Key+"掉线了");
}
else
{
Console.WriteLine(item.Key+"在线");
}
}
}
});
}
// 无参数的构造函数
public Server()
{
}
// 群发方法
public void Send()
{
}
// 指定给谁发
public void send指定()
{
}
//指定给哪些客户端发
}