1.Socket服务端与客户端通话

1服务端

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks; namespace tSocket
{
class Program
{
byte[] bytes = new byte[1024];
Socket cSocket;
static void Main(string[] args)
{
Program p = new Program();
//打开链接
p.open();
//向服务端发送消息
Console.WriteLine("请输入你要对服务端发送的消息:");
string mes = Console.ReadLine();
string con = p.messge(mes);
Console.WriteLine("接受到服务端的消息:" + con); }
byte[] data = new byte[1024];
string messge(string mes)
{
//将发送的消息转成字节数组
bytes = Encoding.UTF8.GetBytes(mes);
//发送
cSocket.Send(bytes);
while (true)
{
//接受服务端发送的消息,放入字节数组
int len = cSocket.Receive(data);
//将字节数组转成可读明文
string con = Encoding.UTF8.GetString(data, 0, len);
////返回
return con;
} }
/// <summary>
/// 打开链接
/// </summary>
void open()
{
//创建Socket对象 指定连接方式
cSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//创建IP,端口
IPAddress ip = IPAddress.Parse("10.116.253.10");
int port = 7526;
//封装IP和端口
IPEndPoint Ipoint = new IPEndPoint(ip, port);
//打开链接
cSocket.Connect(Ipoint);
}
}
}

2.客户端

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks; namespace ServerSocket
{
class Program
{ static void Main(string[] args)
{
//创建Socket对象,指定他的链接方式
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//建立IP
string ip = "10.116.253.10";
//创建端口
int prot = 7526;//1~9999
IPAddress IPAdd = IPAddress.Parse(ip);
//封装IP和端口
IPEndPoint point = new IPEndPoint(IPAdd, prot);
//绑定IP和端口
serverSocket.Bind(point);
//开始监听
serverSocket.Listen(100);
Console.WriteLine("开始监听!"); int i = 0;
while (true)
{
i++;
//接受客户链接 Socket cSocket = serverSocket.Accept();
Console.WriteLine("接受第"+i+"个客户的连接!");
Client c = new Client(cSocket);
} }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace ServerSocket
{
class Client
{
Socket sSocket;
byte[] data = new byte[1024];
Thread t;
public Client(Socket cSocket)
{
//接受客户的连接
sSocket = cSocket;
//创建线程
t = new Thread(Mess);
//开始线程
t.Start();
} void Mess()
{
try
{
while (true)
{
//将用户发送的数据以一个字节数组装起
int length = sSocket.Receive(data);
Console.WriteLine("接受客户端发的消息!");
string mess = Encoding.UTF8.GetString(data, 0, length); if (mess == "con")
{
string con = "DataSource =.";
byte[] bytes = Encoding.UTF8.GetBytes(con);
sSocket.Send(bytes);
}
Console.WriteLine("接到用户的消息:" + mess);
}
}
catch (Exception)
{
sSocket.Close();
} }
}
}

2.DotNetty

DotNetty是微软的Azure团队,使用C#实现的Netty的版本发布。不但使用了C#和.Net平台的技术特点,并且保留了Netty原来绝大部分的编程接口。让我们在使用时,完全可以依照Netty官方的教程来学习和使用DotNetty应用程序。

Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。

优点

  1. 关注点分离——业务和网络逻辑解耦;
  2. 模块化和可复用性;
  3. 可测试性作为首要的要求

历史

  1. 阻塞Socket通信特点:
    1. 建立连接要阻塞线程,读取数据要阻塞线程
    2. 如果要管理多个客户端,就需要为每个客户端建立不同的线程
    3. 会有大量的线程在休眠状态,等待接收数据,资源浪费
    4. 每个线程都要占用系统资源
    5. 线程的切换很耗费系统资源
  2. 非阻塞Socket(NIO)特点:
    1. 如图,每个Socket如果需要读写操作,都通过事件通知的方式通知选择器,这样就实现了一个线程管理多个Socket的目的。
    2. 选择器甚至可以在所有的Socket空闲的时候允许线程先去干别的事情
    3. 减少了线程数量导致的资源占用,减少了线程切换导致的资源消耗

Netty设计的关键点

异步和事件驱动是Netty设计的关键

核心组件

  • Channel:一个连接就是一个Channel
  • 回调:通知的基础

官方也提供了一些例子。地址如下

https://github.com/Azure/DotNetty

3.Supersocket 

开源地址https://github.com/kerryjiang/SuperSocket

SuperSocket是重量轻的可扩展套接字应用程序框架。您可以使用它轻松构建始终连接的套接字应用程序,而无需考虑如何使用套接字,如何维护套接字连接以及套接字如何工作。这是一个纯C#项目,旨在进行扩展,因此只要以.NET语言开发它们,就可以轻松地将它们集成到您的现有系统中。

首先安装:SuperSocket.Engine

SuperSoket的三大对象:

Session: 每一个用户连接就是一个Session
AppServer: Socket服务器实例
Commands: 客户端向服务器发送消息的命令集合

首先在配置文件加入如下配置

<configSections>
<section name="superSocket" type="SuperSocket.SocketEngine.Configuration.SocketServiceConfig, SuperSocket.SocketEngine"/>
</configSections>
<superSocket>
<servers>
<server name="ChatSocket" textEncoding="gb2312"
serverType="XT.SocketService.AppServer.ChatServer, XT.SocketService"
ip="Any" port="2020"
maxConnectionNumber="1000">
</server>
<!-- 可以配置多个Server-->
</servers>
</superSocket>

AppServer代码如下

[AuthorisizeFilter]
public class ChatServer:AppServer<ChatSession>
{
protected override bool Setup(IRootConfig rootConfig, IServerConfig config)
{
Console.WriteLine("准备读取配置文件。。。。");
return base.Setup(rootConfig, config);
} protected override void OnStarted()
{
Console.WriteLine("Chat服务启动。。。");
base.OnStarted();
} protected override void OnStopped()
{
Console.WriteLine("Chat服务停止。。。");
base.OnStopped();
} /// <summary>
/// 新的连接
/// </summary>
/// <param name="session"></param>
protected override void OnNewSessionConnected(ChatSession session)
{
Console.WriteLine($"Chat服务新加入的连接:{session.LocalEndPoint.Address.ToString()}");
base.OnNewSessionConnected(session);
}
}

Session代码如下

/// <summary>
/// 表示用户连接
/// </summary>
//[AuthorisizeFilter]
public class ChatSession : AppSession<ChatSession>
{
public string Id { get; set; } public string PassWord { get; set; } public bool IsLogin { get; set; } public DateTime LoginTime { get; set; } public DateTime LastHbTime { get; set; } public bool IsOnline
{
get
{
return this.LastHbTime.AddSeconds(10) > DateTime.Now;
}
} /// <summary>
/// 消息发送
/// </summary>
/// <param name="message"></param>
public override void Send(string message)
{
Console.WriteLine($"准备发送给{this.Id}:{message}");
base.Send(message.Format());
} protected override void OnSessionStarted()
{
this.Send("Welcome to SuperSocket Chat Server");
} protected override void OnInit()
{
this.Charset = Encoding.GetEncoding("gb2312");
base.OnInit();
} protected override void HandleUnknownRequest(StringRequestInfo requestInfo)
{
Console.WriteLine("收到命令:" + requestInfo.Key.ToString());
this.Send("不知道如何处理 " + requestInfo.Key.ToString() + " 命令");
} /// <summary>
/// 异常捕捉
/// </summary>
/// <param name="e"></param>
protected override void HandleException(Exception e)
{
this.Send($"\n\r异常信息:{ e.Message}");
//base.HandleException(e);
} /// <summary>
/// 连接关闭
/// </summary>
/// <param name="reason"></param>
protected override void OnSessionClosed(CloseReason reason)
{
Console.WriteLine("链接已关闭。。。");
base.OnSessionClosed(reason);
}
}

Commands代码如下 : 客户端发送消息命令 Check 1 123456
Check 代表类名 ,1代表session.id(会话ID),1代表session.PassWord (会话密码)

public class Check : CommandBase<ChatSession, StringRequestInfo>
{
public override void ExecuteCommand(ChatSession session, StringRequestInfo requestInfo)
{
if (requestInfo.Parameters != null && requestInfo.Parameters.Length == 2)
{
ChatSession oldSession = session.AppServer.GetAllSessions().FirstOrDefault(a => requestInfo.Parameters[0].Equals(a.Id));
if (oldSession != null) // 说过之前有用户用这个Id 登录过
{
oldSession.Send("您的账号已经在他处登录,您已经被踢下线了");
oldSession.Close();
} #region 这里就可以连接数据库进行数据验证做登录
///---------------------
#endregion
session.Id = requestInfo.Parameters[0];
session.PassWord = requestInfo.Parameters[1];
session.IsLogin = true;
session.LoginTime = DateTime.Now; session.Send("登录成功"); { // 获取当前登录用户的离线消息
ChatDataManager.SendLogin(session.Id, c =>
{
session.Send($"{c.FromId} 给你发送消息:{c.Message} {c.Id}");
}); }
}
else
{
session.Send("参数错误");
}
}
}

离线消息存储的相关类

public class ChatDataManager
{
/// <summary>
/// key是用户id
/// List 这个用户的全部消息
/// </summary>
private static Dictionary<string, List<ChatModel>> Dictionary = new Dictionary<string, List<ChatModel>>(); public static void Add(string userId, ChatModel model)
{
if (Dictionary.ContainsKey(userId))
{
Dictionary[userId].Add(model);
}
else
{
Dictionary[userId] = new List<ChatModel>() { model };
}
}
public static void Remove(string userId, string modelId)
{
if (Dictionary.ContainsKey(userId))
{
Dictionary[userId] = Dictionary[userId].Where(m => m.Id != modelId).ToList();
}
} public static void SendLogin(string userId, Action<ChatModel> action)
{
if (Dictionary.ContainsKey(userId))
{
foreach (var item in Dictionary[userId])
{
action.Invoke(item);
item.State = 1;
}
}
}
}
/// <summary>
/// 一条消息的记录
/// </summary>
public class ChatModel
{
/// <summary>
/// 每条分配个唯一Id
/// </summary>
public string Id { get; set; }
/// <summary>
/// 来源编号
/// </summary>
public string FromId { get; set; }
/// <summary>
/// 目标编号
/// </summary>
public string ToId { get; set; }
/// <summary>
/// 消息内容
/// </summary>
public string Message { get; set; }
/// <summary>
/// 消息时间
/// </summary>
public DateTime CreateTime { get; set; }
/// <summary>
/// 消息状态 0未发送 1已发送待确认 2确认收到
/// </summary>
public int State { get; set; }
}

基本使用获取离线消息

public class Chat : CommandBase<ChatSession, StringRequestInfo>
{
public override void ExecuteCommand(ChatSession session, StringRequestInfo requestInfo)
{
// 还是传递两个参数 1、 要发给谁 ToId 2、消息内容
if (requestInfo.Parameters != null && requestInfo.Parameters.Length == 2)
{
string toId = requestInfo.Parameters[0];
string message = requestInfo.Parameters[1];
ChatSession toSession = session.AppServer.GetAllSessions().FirstOrDefault(a => toId.Equals(a.Id)); string modelId = Guid.NewGuid().ToString();
if (toSession != null) // 说过之前有用户用这个Id 登录过
{
toSession.Send($"{session.Id} 给你发消息:{message} {modelId}");
ChatDataManager.Add(toId, new ChatModel()
{
FromId = session.Id,
ToId = toId,
Message = message,
Id = modelId,
State = 1,// 待确认
CreateTime = DateTime.Now
});
}
else
{
ChatDataManager.Add(toId, new ChatModel()
{
FromId = session.Id,
ToId = toId,
Message = message,
Id = modelId,
State = 0,// 未发送
CreateTime = DateTime.Now
});
session.Send("消息未发送成功");
}
}
else
{
session.Send("参数错误");
}
}
}
public class Confirm : CommandBase<ChatSession, StringRequestInfo>
{
public override void ExecuteCommand(ChatSession session, StringRequestInfo requestInfo)
{
if (requestInfo.Parameters != null && requestInfo.Parameters.Length == 1)
{
string modelId = requestInfo.Parameters[0];
Console.WriteLine($"用户{session.Id} 已确认,收到消息{modelId}");
ChatDataManager.Remove(session.Id, modelId);
}
else
{
session.Send("参数错误");
}
}
}

心跳检测:主要就是定时发送消息,没接到消息就发起重连

public class HB : CommandBase<ChatSession, StringRequestInfo>
{
public override void ExecuteCommand(ChatSession session, StringRequestInfo requestInfo)
{
if (requestInfo.Parameters != null && requestInfo.Parameters.Length == 1)
{
if ("R".Equals(requestInfo.Parameters[0]))
{
session.LastHbTime = DateTime.Now;
session.Send("R");
}
else
{
session.Send("参数错误");
}
}
else
{
session.Send("参数错误");
}
}
}

SuperSocket的AOP的使用

class AuthorisizeFilterAttribute : CommandFilterAttribute
{ public override void OnCommandExecuting(CommandExecutingContext commandContext)
{
ChatSession session = (ChatSession)commandContext.Session;
string command = commandContext.CurrentCommand.Name;
if (!session.IsLogin)
{
if (!command.Equals("Check"))
{
session.Send($"请先登录,再操作");
commandContext.Cancel = true;
}
else
{ }
}
else if (!session.IsOnline)
{
session.LastHbTime = DateTime.Now;
} } public override void OnCommandExecuted(CommandExecutingContext commandContext)
{ }
}

C# Socket使用以及DotNetty和Supersocket 框架的更多相关文章

  1. SuperSocket框架中BinaryRequestInfo协议的使用

    一.开发环境 1.Windows 10 企业版 64位 2.Microsoft Visual Studio 2017 企业版 二.项目开始 1.新建控制台程序,项目名称“BinarySuperSock ...

  2. SuperSocket框架的系列博文

    官方文档 http://docs.supersocket.net/v1-6/zh-CN 对于我等小白,此系列博文,受益匪浅,慢慢看 https://www.cnblogs.com/fly-bird/c ...

  3. 我的第一个Socket程序-SuperSocket使用入门(一)

    第一次使用Socket,遇到过坑,也涨过姿势,网上关于SuperSocket的教程基本都停留在官方给的简单demo上,实际使用还是会碰到一些问题,所以准备写两篇博客,分别来介绍SuperSocket以 ...

  4. SuperSocket 1.6 创建一个简易的报文长度在头部的Socket服务器

    我们来做一个头为6位报文总长度,并且长度不包含长度域自身的例子.比如这样的Socket报文000006123456. 添加SuperSocket.Engine,直接使用Nuget搜索SuperSock ...

  5. [SuperSocket2.0]SuperSocket 2.0从入门到懵逼

    SuperSocket 2.0从入门到懵逼 SuperSocket 2.0从入门到懵逼 1 使用SuperSocket 2.0在AspNetCore项目中搭建一个Socket服务器 1.1 引入Sup ...

  6. 基于SuperSocket的IIS主动推送消息给android客户端

    在上一篇文章<基于mina框架的GPS设备与服务器之间的交互>中,提到之前一直使用superwebsocket框架做为IIS和APP通信的媒介,经常出现无法通信的问题,必须一天几次的手动回 ...

  7. C#网络编程技术微软Socket实战项目演练(三)

    一.课程介绍 本次分享课程属于<C#高级编程实战技能开发宝典课程系列>中的第三部分,阿笨后续会计划将实际项目中的一些比较实用的关于C#高级编程的技巧分享出来给大家进行学习,不断的收集.整理 ...

  8. SuperSocket 介绍

    一.总体介绍 SuperSocket 是一个轻量级的可扩展的 Socket 开发框架,由江振宇先生开发. 官方网站:http://www.supersocket.net/ SuperSocket具有如 ...

  9. C#SuperSocket的搭建--通过配置启动

    之前我们借助一个SuperSocket实现了一个简易版的服务器, 但是不管是Server还是Session都是使用框架的,本篇博客我们要实现自己的Server和Session,来重写框架原生的Serv ...

随机推荐

  1. Unable to locate package python3 错误解决办法

    错误 huny@DESKTOP-N1EBKQP:/mnt/c/Users/Administrator$ sudo apt-get install python3 Reading package lis ...

  2. Linux中influx数据库进程杀不掉,父进程为1

    influx数据库一直杀不掉,父进程为1是个僵尸进程 后来我才发现,influx是运行运行状态 我只需要使用命令,停掉influx即可停止改进程

  3. 编程入门选什么语言好?C 语言还是Python ?为你解析

    前面我分享过计算机行业已经成了学校选择排名第一,家长和学生都很看好计算机类专业.现在IT行业也越来越火爆,程序员越来越被人看好.面对相比同龄人高薪资的诱惑,人们很难不心动,即使秃头也值得! 那么问题来 ...

  4. java实验作业1

    1 //1已知圆的半径为10,求其周长及面积 2 package calsswork3; 3 4 public class test3_1 { 5 //求周长 6 public static doub ...

  5. framework中的sentinel

    引入切面: 切面+sentinel-web-servlet private void initDataSource() { String zkUrl = zaSentinelConfig.getDat ...

  6. CentOS下Mysql简易操作

    Mysql mysql的root密码重置 编辑mysql主配置文件 vim /etc/my.cnf 添加..grant参数 [mysqld] skip-grant 重启mysql服务 service ...

  7. js声明 对象,数组 的方法

    i={} 对象字面量 等同 i = new Object();i=[] 数组字面量 等同 i = new Array();

  8. IntelliJ IDEA 2020.3正式发布,年度最后一个版本很讲武德

    仰不愧天,俯不愧人,内不愧心.关注公众号[BAT的乌托邦],有Spring技术栈.MyBatis.JVM.中间件等小而美的原创专栏供以免费学习.分享.成长,拒绝浅尝辄止.本文已被 https://ww ...

  9. nginx反向代理docker容器化django

    1.新建Dockerfile FROM python:3.8.5 MAINTAINER ChsterChen ENV PYTHONUNBUFFERED 1 COPY pip.conf /root/.p ...

  10. 老猿学5G扫盲贴:NEF、NRF、AF、UPF以及DN的功能

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 NEF:Network Exposure Function ,网络开放 ...