浅谈C#网络编程详解篇

 更新时间:2020年6月25日 11:21  点击:1637

阅读目录:

基础
Socket编程
多线程并发
阻塞式同步IO

基础
在现今软件开发中,网络编程是非常重要的一部分,本文简要介绍下网络编程的概念和实践。
Socket是一种网络编程接口,它是对传输层TCP、UDP通信协议的一层封装,通过友好的API暴露出去,方便在进程或多台机器间进行网络通信。

Socket编程

在网络编程中分客户端和服务端两种角色,比如通过打开浏览器访问到挂在Web软件上的网页,从程序角度上来看,即客户端(浏览器)发起了一个Socket请求到服务器端,服务器把网页内容返回到浏览器解析后展示。在客户端和服务端数据通信前,会进行三次确认才会正式建立连接,也即是三次握手。

  1. 客户端发送消息询问服务端是否准备好
  2. 服务端回应我准备好了,你呢准备好了吗
  3. 客户端回应服务端我也准备好了,可以通信了

TCP/IP协议是网络间通信的基础协议,在不同编程语言及不同操作系统下暴露的Socket接口用法也大同小异,仅是其内部实现有所不同,比如Linux下的epoll和windows下的IOCP。

服务端
  • 实例化Socket
  • 把公共地址端口绑定操作系统上
  • 开始监听绑定的端口
  • 等待客户端连接

IPEndPoint ip = new IPEndPoint(IPAddress.Any, 6389);
      Socket listenSocket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
      listenSocket.Bind(ip);
      listenSocket.Listen(100);
      listenSocket.Accept();

listen函数中有个int类型参数,它表示最大等待处理连接的数量,表示已建立连接但还未处理的数量,每调用Accept函数一下即从这个等待队列中拿出一个连接。 通常服务端要服务多个客户端请求的连接,所以会循环从等待队列中拿出连接,进行接收发送。

while (true) 
      { 
        var accept= listenSocket.Accept();
        accept.Receive(); 
        accept.Send(); 
      }

多线程并发
上面的服务端程序处理接收和发送消息都是在当前线程下完成的,这意味着要处理完一个客户端连接后才能去处理下一个连接,如果当前连接是进行数据库或者文件读取写入等IO操作,那会极大浪费服务器的CPU资源,降低了服务器吞吐量。

while (true)
      {
        var accept = listenSocket.Accept();
        ThreadPool.QueueUserWorkItem((obj) =>
        {
          byte[] receive = new byte[100];
          accept.Receive(receive);
          byte[] send = new byte[100];
          accept.Send(receive);
        });
      }

如例子中,当监听到有新连接请求过来时,调用Accept()取出当前连接的socket,使用新的线程去处理接收和发送信息,这样服务端就能实现并发处理多个客户端了。 上述代码中,在高并发下其实是有问题的,如果客户端连接请求成千上万个,那线程数量也会有这么多,每个线程的栈空间都需要消耗部分内存,再加上线程上下文切换,容易导致服务器负载过高,吞吐量大大下降,严重时会引起宕机。 当前例子中使用系统ThreadPool的话,线程数量会固定在一个数量上,默认是1000,不会无限制开线程,会把处理超出线程数量的请求放到线程池中的队列上面。
在unix下类似的实现有2种:

fork一个新进程去处理客户端的连接:

var connfd = Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len); 
var m = fork(); 
if(m == 0) 
{
 //do something 
}

创建一个新的线程处理限流:

var *clientsockfd = accept(serversockfd,(struct sockaddr *)&clientaddress, (socklent *)&clientlen);
 if(pthreadcreate(&thread, NULL, recdata, clientsockfd)!=0) 
{ //do something 
}

阻塞式同步IO
上述例子中使用的即是该模型,使用起来简单方便。

while (true)
      {
        var accept = listenSocket.Accept();
        byte[] receive = new byte[100];
        accept.Receive(receive);
        byte[] send = new byte[100];
        accept.Send(receive);
      }

从调用Receive函数起到接受到客户端发过来的数据期间,该函数会一直阻塞等待着,这个阻塞期间处理流程如下:

  1. 客户端发送数据
  2. 通过广域网局域网发送到服务端机器网卡缓冲区上
  3. 网卡驱动对CPU发送中断指令
  4. CPU把数据拷贝到内核缓冲区
  5. CPU再把内核缓冲区的数据拷贝用户缓冲区,上面的receive字节数组。

至此处理成功,开始处理下一个连接请求。 调用发送函数同样会阻塞在当前,然后把用户缓冲区(send字节数组)数据拷贝到内核中TCP发送缓冲区中。 TCP的发送缓冲区也有一定的大小限制,如果发送的数据大于该限制,send函数会一直等待发送缓冲区有空闲时完全拷贝完才会返回,继续处理后续连接请求。

异步IO
上篇提到用多线程处理多个阻塞同步IO而实现并发服务端,这种模式在连接数量比较小的时候非常适合,一旦连接过多,性能会急速下降。 在大多数服务端网络软件中会采用一种异步IO的方式来提高性能。

同步IO方式:连接Receive请求->等待->等待->接收成功
异步IO方式:连接Receive请求->立即返回->事件或回调通知
采用异步IO方式,意味着单线程可以处理多个请求了,连接发起一个Receive请求后,当前线程可以立即去做别的事情,当数据接收完毕通知线程处理即可。
其数据接收分2部分:

数据从别的机器发送内核缓冲区
内核缓冲区拷贝到用户缓冲区
第二部分示例代码:

byte[] msg = new byte[256]; socket.Receive(msg);

介绍这2部分的目的是方便区分其他几种方式。 对于用户程序来说,同步IO和异步IO的区别在于第二部分是否需要等待。

非阻塞式同步IO
非阻塞式同步IO,由同步IO延伸出来,把这个名词拆分成2部分描述:

  • 非阻塞式,指的是上节"数据从别的机器发送内核缓冲区"部分是非阻塞的。
  • 同步IO,指的是上节"内核缓冲区拷贝到用户缓冲区"部分是等待的。

既然是第一部分是非阻塞的,那就需要一种方法得知什么时候内核缓冲区是OK的。 设置非阻塞模式后,在连接调用Receive方法时,会立即返回一个标记,告知用户程序内核缓存区有没有数据,如果有数据开始进行第二部分操作,从内核缓冲区拷贝到用户程序缓冲区。 由于系统会返回个标记,那可以通过轮询方式来判断内核缓冲区是否OK。

设置非阻塞模式参考代码:

SocketInformation sif=new SocketInformation();
sif.Options=SocketInformationOptions.NonBlocking;
sif.ProtocolInformation = new byte[24];
Socket socket = new Socket(sif);

轮询参考代码:

while(true) 
{
byte[] msg = new byte[256];
var temp = socket.Receive(msg);
if (temp=="OK"){
//do something
}else{ continue }
}

这种方式近乎淘汰了,了解即可。

基于回调的异步IO
上面介绍过:

异步IO方式:连接Receive请求->立即返回->事件或回调通知
当回调到执行时,数据已经在用户程序缓冲区已经准备好了,在回调代码中对这部分数据进行相应的逻辑即可。

发出接收请求:

static byte[] msg = new byte[256]; 
var temp = socket.BeginReceive(msg, 0, msg.Length, 0, new AsyncCallback(ReadCallback), socket);

回调函数中对数据做处理:

public static void ReadCallback(IAsyncResult ar) 
{ 
var socket = (Socket)ar.AsyncState;
 int read = socket.EndReceive(ar);
DoSomething(msg); 
socket.BeginReceive(msg, 0, msg.Length, 0, new AsyncCallback(Read_Callback), socket);
}

当回调函数执行时,表示数据已经准备好,需要先结束接收请求EndReceive,以便第二次发出接收请求。 在服务端程序中要处理多个客户端的接收,再次发出BeginReceive接收数据请求即可。

这里的回调函数是在另外一个线程的触发,必要时要对数据加锁防止数据竞争:

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

针对C#网络编程的介绍就到这了,具体的大家可以查看猪先飞之前发布的文章。

[!--infotagslink--]

相关文章

  • C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • C#中截取字符串的的基本方法详解

    这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#实现简单的Http请求实例

    这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
  • C#中new的几种用法详解

    本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • 使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)

    这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#开发Windows窗体应用程序的简单操作步骤

    这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • 经典实例讲解C#递归算法

    这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • 轻松学习C#的基础入门

    轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
  • C#变量命名规则小结

    本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
  • c#中(&&,||)与(&,|)的区别详解

    这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • C#绘制曲线图的方法

    这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#中list用法实例

    这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25