C# Socket-TCP异步编程原理详解附源码
目录
异步原理
套接字编程原理:延续文件作用思想,打开-读写-关闭的模式。
C/S编程模式如下:
Ø 服务器端:
打开通信通道,告诉本地机器,愿意在该通道上接受客户请求——监听,等待客户请求——接受请求,创建专用链接进行读写——处理完毕,关闭专用链接——关闭通信通道(当然其中监听到关闭专用链接可以重复循环)
Ø 客户端:打开通信通道,连接服务器——数据交互——关闭信道。
Socket通信方式:
Ø 同步:客户端在发送请求之后必须等到服务器回应之后才可以发送下一条请求。串行运行
Ø 异步:客户端请求之后,不必等到服务器回应之后就可以发送下一条请求。并行运行
套接字模式:
Ø 阻塞:执行此套接字调用时,所有调用函数只有在得到返回结果之后才会返回。在调用结果返回之前,当前进程会被挂起。即此套接字一直被阻塞在网络调用上。
Ø 非阻塞:执行此套接字调用时,调用函数即使得不到得到返回结果也会返回。
套接字工作步骤:
Ø 服务器监听:监听时服务器端套接字并不定位具体客户端套接字,而是处于等待链接的状态,实时监控网络状态
Ø 客户端链接:客户端发出链接请求,要连接的目标是服务器端的套接字。为此客户端套接字必须描述服务器端套接字的服务器地址与端口号。
Ø 链接确认:是指服务器端套接字监听到客户端套接字的链接请求时,它响应客户端链接请求,建立一个新的线程,把服务器端套接字的描述发送给客户端,一旦客户端确认此描述,则链接建立好。而服务器端的套接字继续处于监听状态,继续接受其他客户端套接字请求。
主要方法

源码
Server源码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
public class Conn
{ //定义数据最大长度
public const int data = 1024;
//Socket
public Socket socket;
//是否使用
public bool isUse = false;
//Buff
public byte[] readBuff = new byte[data];
public int buffCount = 0;
//构造函数
public Conn()
{
readBuff = new byte[data];
}
//初始化
public void Init(Socket socket)
{
this.socket = socket;
isUse = true;
buffCount = 0;
}
//缓冲区剩余的字节数
public int BuffRemain()
{
return data - buffCount;
}
//获取客户端地址
public string GetAdress()
{
if (!isUse)
return "无法获取地址";
return socket.RemoteEndPoint.ToString();
}
//关闭
public void Close()
{
if (!isUse)
return;
Console.WriteLine("[断开链接]" + GetAdress());
socket.Close();
isUse = false;
}
}
public class Program
{
/// <summary>
/// 创建多个Conn管理客户端的连接
/// </summary>
public static Conn[] conns;
/// <summary>
/// 最大连接数
/// </summary>
public static int maxConn = 50;
/// <summary>
/// 将Socket定义为全局变量
/// </summary>
private static Socket serverSocket;
/// <summary>
/// 获取链接池索引,返回负数表示获取失败
/// </summary>
/// <returns></returns>
public static int NewIndex()
{
if (conns == null)
return -1;
for (int i = 0; i < conns.Length; i++)
{
if (conns[i] == null)
{
conns[i] = new Conn();
return i;
}
else if (conns[i].isUse == false)
{
return i;
}
}
return -1;
}
static void Main(string[] args)
{
//创建多个链接池,表示创建maxConn最大客户端
conns = new Conn[maxConn];
for (int i = 0; i < maxConn; i++)
{
conns[i] = new Conn();
}
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000);
serverSocket.Bind(ipEndPoint);//绑定IP和端口号
serverSocket.Listen(maxConn);//开始监听端口,0为监听无限个客户端
Console.WriteLine("[服务器]启动成功");
//开始调用异步连接
serverSocket.BeginAccept(AcceptCb, null);
//按下quit退出程序
while (true)
{
if (Console.ReadLine() == "quit") return;
}
}
/// <summary>
/// Accept回调
/// </summary>
/// <param name="ar"></param>
static void AcceptCb(IAsyncResult ar)
{
try
{
Socket socket = serverSocket.EndAccept(ar);//尝试进行异步连接
int index = NewIndex();
if (index < 0)
{
socket.Close();
Console.Write("[警告]链接已满");
}
else
{
Conn conn = conns[index];
conn.Init(socket);
string adr = conn.GetAdress();
Console.WriteLine("客户端连接 [" + adr + "] conn池ID:" + index);
conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReceiveCb, conn);
}
serverSocket.BeginAccept(AcceptCb, null);
}
catch (Exception e)
{
Console.WriteLine("AcceptCb失败:" + e.Message);
}
}
/// <summary>
/// 接收回调
/// </summary>
/// <param name="ar"></param>
static void ReceiveCb(IAsyncResult ar)
{
Conn conn = (Conn)ar.AsyncState;
try
{
int count = conn.socket.EndReceive(ar);
//关闭信号
if (count <= 0)
{
Console.WriteLine("收到 [" + conn.GetAdress() + "] 断开链接");
conn.Close();
return;
}
//数据处理
string str = Encoding.UTF8.GetString(conn.readBuff, 0, count);
Console.WriteLine("收到 [" + conn.GetAdress() + "] 数据:" + str);
str = conn.GetAdress() + "发送的:" + str;
byte[] bytes = System.Text.Encoding.UTF8.GetBytes("接收到" + str);
//广播
/*
for (int i = 0; i < conns.Length; i++)
{
if (conns[i] == null)
continue;
if (!conns[i].isUse)
continue;
Console.WriteLine("将消息转播给 " + conns[i].GetAdress());
conns[i].socket.Send(bytes);
}*/
//点播
for (int i = 0; i<=0;i++)
{
if (conns[i] == null)
continue;
if (!conns[i].isUse)
continue;
Console.WriteLine("将消息转播给 " + conns[i].GetAdress());
conns[i].socket.Send(bytes);
}
//继续接收
conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(),SocketFlags.None, ReceiveCb, conn);
}
catch (Exception e)
{
Console.WriteLine("收到 [" + conn.GetAdress() + "] 断开链接");
conn.Close();
}
}
}
}
Client源码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
public class Program
{
private static int datacount = 1024;//设置数组数据长度
private static Socket socket;
private static byte[] dataBuff = new byte[datacount];
private static string recvStr;
static void Main(string[] args)
{
//获取到Socket协议
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定IP与端口
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000);
socket.Connect(ipEndPoint);//与服务端进行连接
Console.WriteLine("客户端地址 " + socket.LocalEndPoint.ToString());//获取客户端地址
//开启异步接收模式
socket.BeginReceive(dataBuff, 0, datacount, SocketFlags.None, ReceiveCb, null);
socket.Send(Encoding.UTF8.GetBytes("123")); //封装好的一个Send方法,能够在方法中操作,比如说:释放资源
Send(socket, "456"); //简单的发送
while (true)
{
//向服务器发送数据
byte[] data = Encoding.UTF8.GetBytes(Console.ReadLine());//输入字符
//按下回车键发送数据
ConsoleKey inpt = Console.ReadKey().Key;
if (inpt == ConsoleKey.Enter)
{
socket.Send(data);
Console.WriteLine("发送成功");
}
}
}
private static void Send(Socket handler, String data)
{
byte[] byteData = Encoding.ASCII.GetBytes(data);
handler.BeginSend(byteData, 0, byteData.Length, 0,
new AsyncCallback(SendCallback), handler);
}
private static void SendCallback(IAsyncResult ar)
{
try
{
Socket handler = (Socket)ar.AsyncState;
int bytesSent = handler.EndSend(ar);
Console.WriteLine("Sent {0} bytes to client.", bytesSent);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private static void ReceiveCb(IAsyncResult ar)
{
//接收数据的长度
//数据处理
//将dataBuffer数组转码成字符的形式输出
//dataBuffer:需要转码的数组
//0:从数组的长度,0开始读取。
//count :读取数组的最大长度
int count = socket.EndReceive(ar);
if(count>0) //当接受数据长度大于0时处理
{
string str = System.Text.Encoding.UTF8.GetString(dataBuff, 0, count);
//获取服务器端的数据
Console.WriteLine("获取服务端的数据:" + str);
//继续接收
socket.BeginReceive(dataBuff, 0, datacount, SocketFlags.None, ReceiveCb, null);
}
}
}
}
实验效果(广播为例)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
public class Conn
{ //定义数据最大长度
public const int data = 1024;
//Socket
public Socket socket;
//是否使用
public bool isUse = false;
//Buff
public byte[] readBuff = new byte[data];
public int buffCount = 0;
//构造函数
public Conn()
{
readBuff = new byte[data];
}
//初始化
public void Init(Socket socket)
{
this.socket = socket;
isUse = true;
buffCount = 0;
}
//缓冲区剩余的字节数
public int BuffRemain()
{
return data - buffCount;
}
//获取客户端地址
public string GetAdress()
{
if (!isUse)
return "无法获取地址";
return socket.RemoteEndPoint.ToString();
}
//关闭
public void Close()
{
if (!isUse)
return;
Console.WriteLine("[断开链接]" + GetAdress());
socket.Close();
isUse = false;
}
}
public class Program
{
/// <summary>
/// 创建多个Conn管理客户端的连接
/// </summary>
public static Conn[] conns;
/// <summary>
/// 最大连接数
/// </summary>
public static int maxConn = 50;
/// <summary>
/// 将Socket定义为全局变量
/// </summary>
private static Socket serverSocket;
/// <summary>
/// 获取链接池索引,返回负数表示获取失败
/// </summary>
/// <returns></returns>
public static int NewIndex()
{
if (conns == null)
return -1;
for (int i = 0; i < conns.Length; i++)
{
if (conns[i] == null)
{
conns[i] = new Conn();
return i;
}
else if (conns[i].isUse == false)
{
return i;
}
}
return -1;
}
static void Main(string[] args)
{
//创建多个链接池,表示创建maxConn最大客户端
conns = new Conn[maxConn];
for (int i = 0; i < maxConn; i++)
{
conns[i] = new Conn();
}
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000);
serverSocket.Bind(ipEndPoint);//绑定IP和端口号
serverSocket.Listen(maxConn);//开始监听端口,0为监听无限个客户端
Console.WriteLine("[服务器]启动成功");
//开始调用异步连接
serverSocket.BeginAccept(AcceptCb, null);
//按下quit退出程序
while (true)
{
if (Console.ReadLine() == "quit") return;
}
}
/// <summary>
/// Accept回调
/// </summary>
/// <param name="ar"></param>
static void AcceptCb(IAsyncResult ar)
{
try
{
Socket socket = serverSocket.EndAccept(ar);//尝试进行异步连接
int index = NewIndex();
if (index < 0)
{
socket.Close();
Console.Write("[警告]链接已满");
}
else
{
Conn conn = conns[index];
conn.Init(socket);
string adr = conn.GetAdress();
Console.WriteLine("客户端连接 [" + adr + "] conn池ID:" + index);
conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReceiveCb, conn);
}
serverSocket.BeginAccept(AcceptCb, null);
}
catch (Exception e)
{
Console.WriteLine("AcceptCb失败:" + e.Message);
}
}
/// <summary>
/// 接收回调
/// </summary>
/// <param name="ar"></param>
static void ReceiveCb(IAsyncResult ar)
{
Conn conn = (Conn)ar.AsyncState;
try
{
int count = conn.socket.EndReceive(ar);
//关闭信号
if (count <= 0)
{
Console.WriteLine("收到 [" + conn.GetAdress() + "] 断开链接");
conn.Close();
return;
}
//数据处理
string str = Encoding.UTF8.GetString(conn.readBuff, 0, count);
Console.WriteLine("收到 [" + conn.GetAdress() + "] 数据:" + str);
str = conn.GetAdress() + "发送的:" + str;
byte[] bytes = System.Text.Encoding.UTF8.GetBytes("接收到" + str);
//广播
/*
for (int i = 0; i < conns.Length; i++)
{
if (conns[i] == null)
continue;
if (!conns[i].isUse)
continue;
Console.WriteLine("将消息转播给 " + conns[i].GetAdress());
conns[i].socket.Send(bytes);
}*/
//点播
for (int i = 0; i<=0;i++)
{
if (conns[i] == null)
continue;
if (!conns[i].isUse)
continue;
Console.WriteLine("将消息转播给 " + conns[i].GetAdress());
conns[i].socket.Send(bytes);
}
//继续接收
conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(),SocketFlags.None, ReceiveCb, conn);
}
catch (Exception e)
{
Console.WriteLine("收到 [" + conn.GetAdress() + "] 断开链接");
conn.Close();
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
public class Program
{
private static int datacount = 1024;//设置数组数据长度
private static Socket socket;
private static byte[] dataBuff = new byte[datacount];
private static string recvStr;
static void Main(string[] args)
{
//获取到Socket协议
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定IP与端口
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000);
socket.Connect(ipEndPoint);//与服务端进行连接
Console.WriteLine("客户端地址 " + socket.LocalEndPoint.ToString());//获取客户端地址
//开启异步接收模式
socket.BeginReceive(dataBuff, 0, datacount, SocketFlags.None, ReceiveCb, null);
socket.Send(Encoding.UTF8.GetBytes("123")); //封装好的一个Send方法,能够在方法中操作,比如说:释放资源
Send(socket, "456"); //简单的发送
while (true)
{
//向服务器发送数据
byte[] data = Encoding.UTF8.GetBytes(Console.ReadLine());//输入字符
//按下回车键发送数据
ConsoleKey inpt = Console.ReadKey().Key;
if (inpt == ConsoleKey.Enter)
{
socket.Send(data);
Console.WriteLine("发送成功");
}
}
}
private static void Send(Socket handler, String data)
{
byte[] byteData = Encoding.ASCII.GetBytes(data);
handler.BeginSend(byteData, 0, byteData.Length, 0,
new AsyncCallback(SendCallback), handler);
}
private static void SendCallback(IAsyncResult ar)
{
try
{
Socket handler = (Socket)ar.AsyncState;
int bytesSent = handler.EndSend(ar);
Console.WriteLine("Sent {0} bytes to client.", bytesSent);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private static void ReceiveCb(IAsyncResult ar)
{
//接收数据的长度
//数据处理
//将dataBuffer数组转码成字符的形式输出
//dataBuffer:需要转码的数组
//0:从数组的长度,0开始读取。
//count :读取数组的最大长度
int count = socket.EndReceive(ar);
if(count>0) //当接受数据长度大于0时处理
{
string str = System.Text.Encoding.UTF8.GetString(dataBuff, 0, count);
//获取服务器端的数据
Console.WriteLine("获取服务端的数据:" + str);
//继续接收
socket.BeginReceive(dataBuff, 0, datacount, SocketFlags.None, ReceiveCb, null);
}
}
}
}

参考博客
C# Socket编程 同步及异步通信:https://blog.csdn.net/bemodesty/article/details/84396658
C# Socket之异步连接(一):https://blog.csdn.net/u010511043/article/details/86435701
C# Socket之异步连接(二):https://blog.csdn.net/u010511043/article/details/86437192
C# Socket之客户端异步连接:https://blog.csdn.net/u010511043/article/details/86441724
C# Socket-TCP异步编程原理详解附源码的更多相关文章
- select用法&原理详解(源码剖析)(转)
今天遇到了在select()前后fd_set的变化问题,查了好久终于找到一个有用的帖子了,很赞,很详细!!原文链接如下: select用法&原理详解(源码剖析) 我的问题是: 如下图示:在se ...
- 线程池底层原理详解与源码分析(补充部分---ScheduledThreadPoolExecutor类分析)
[1]前言 本篇幅是对 线程池底层原理详解与源码分析 的补充,默认你已经看完了上一篇对ThreadPoolExecutor类有了足够的了解. [2]ScheduledThreadPoolExecut ...
- 【转载】Android异步消息处理机制详解及源码分析
PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbob ...
- 转载—— android 瀑布流的实现详解,附源码
介绍 参考自:https://github.com/dodola/android_waterfall,因为原来的代码封装不好,所以,我根据源码的思路,重新写了一遍,所以有了现在这个项目:https:/ ...
- struts2内置拦截器和自定义拦截器详解(附源码)
一.Struts2内置拦截器 Struts2中内置类许多的拦截器,它们提供了许多Struts2的核心功能和可选的高级特 性.这些内置的拦截器在struts-default.xml中配置.只有配置了拦截 ...
- Android应用AsyncTask处理机制详解及源码分析
1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知识点.前面我们分析了Handler异步机制原理(不了解的可以阅读我的<Android异步消息处理机 ...
- 【转载】Android应用AsyncTask处理机制详解及源码分析
[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个 ...
- php为什么需要异步编程?php异步编程的详解(附示例)
本篇文章给大家带来的内容是关于php为什么需要异步编程?php异步编程的详解(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 我对 php 异步的知识还比较混乱,写这篇是为了 ...
- 详解 QT 源码之 Qt 事件机制原理
QT 源码之 Qt 事件机制原理是本文要介绍的内容,在用Qt写Gui程序的时候,在main函数里面最后依据都是app.exec();很多书上对这句的解释是,使 Qt 程序进入消息循环.下面我们就到ex ...
随机推荐
- day 02 while 循环 格式化输出 运算符 and or not - 编码的初识
while 循环 while 条件: 循环体 循环如何终止? 改变条件. flag = Truewhile flag: print('狼的诱惑') print('我们不一样') ...
- sql server 角度与弧度相互转换的函数radians(x)和degrees(x)
--RANDIANS(x)将参数x由角度转换为弧度 --DEGREES(x)将参数x由弧度转换为角度. 示例:select RADIANS(90.0), RADIANS(180.0), DEGREES ...
- 剑指offer 剪绳子
题目描述 给你一根长度为n的绳子,请把绳子剪成m段(m.n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],...,k[m].请问k[0]xk[1]x...xk[m]可能 ...
- css中新增的属性calc()可以计算使用
什么是calc: calc是英文单词calculate(计算)的缩写,是css3的一个新增的功能,用来指定元素的长度.可以使用calc()给元素的border.margin.pading.font-s ...
- python实现加密的方式总结
python实现加密的方式总结 原文地址 目录 基础知识扫盲 Base64 MD5 DES 3DES AES RSA 基础知识扫盲 对称加密 对称密钥加密 , 又叫私钥加密.即信息发送的方和接受方用一 ...
- stars-one的原创工具——星之小说下载器(JavaFx应用 )
星之小说下载器Kotlin版 基于星之小说下载器Java版重构的Kotlin版本 github地址 使用说明 确保电脑有jdk8+以上的环境,双击即可运行(win10系统),win7则需要输入命令ja ...
- 电脑系统win7和samba服务器连接不上解决办法
1.修改本地安全策略运行secpol.msc打开“本地安全策略”窗体,依次点开“本地策略”->“安全选项”,修改“网络安全: LAN 管理器身份验证级别”的值为“发送 LM 和 NTLM – 如 ...
- Linux系统账户管理指令
sudo passwd -l weblogic 锁定账户 sudo passwd -u weblogic 解锁账户 useradd weblogic -p xxxxx 添加账户指定密码 sudo us ...
- zencart后台订单详细页显示产品图片和链接
方法一: 找到admin/order.php 大约491行 for ($i=0, $n=sizeof($order->products); $i<$n; $i++) { 与 if (iss ...
- 如何将vim打造成Linux下的source insight
编写者:龙诗科 邮箱:longshike2010@163.com 2016-01-06 众所周知,windows下的source insight是阅读项目代码的神器,其神奇之处在于可以根据当前鼠标所指 ...