浅谈C#网络编程(一)

基础

在现今软件开发中,网络编程是非常重要的一部分,本文简要介绍下网络编程的概念和实践。 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函数会一直等待发送缓冲区有空闲时完全拷贝完才会返回,继续处理后续连接请求。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏运维一切

ETCD的基本使用 转

原文地址: http://www.csdn.net/article/2015-01-22/2823659

15230
来自专栏PingCAP的专栏

TiDB 源码阅读系列文章(十九)tikv-client(下)

上篇文章 中,我们介绍了数据读写过程中 tikv-client 需要解决的几个具体问题,本文将继续介绍 tikv-client 里的两个主要的模块——负责处理分...

22300
来自专栏用户2442861的专栏

高性能网络编程4--TCP连接的关闭

TCP连接的关闭有两个方法close和shutdown,这篇文章将尽量精简的说明它们分别做了些什么。

11520
来自专栏java学习

面试题13(一个具有生命的线程有哪些状态)

考点:考察求职者对线程的理解 出现频率:★★★ 【面试题解析】线程的状态表示线程在某时间段内进行的活动和将要进行的任务程有创建、就绪、运行、阻塞、死亡5种状态。...

34550
来自专栏木头编程 - moTzxx

CentOS7 ab压力测试安装与解释

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011415782/article/de...

30720
来自专栏我的博客

nginx配置文件详解

nginx配置参考文档(附带中文说明),能解决大部分配置问题了! #定义Nginx运行的用户和用户组 user nginx nginx; #指定工作衍生进程数...

54160
来自专栏技术之路

Http概述(一)

Http使用的是可靠的数据传输协议,因此即使数据来自地球的另一端,也能够确保数据在传输过程中不会被损坏或产生混乱。 这样用户在访问信息时就不用担心其完整性了。 ...

21450
来自专栏Jackie技术随笔

基本TCP套接口编程

为了执行网络I/O,一个进程(无论是服务端还是客户端)必须做的第一件事情就是调用socket函数。

43760
来自专栏行者悟空

CentOS Linux安装nginx及负载均衡的配置

13530
来自专栏Golang语言社区

服务器端Go程序对长短链接的处理及运行参数的保存

对长、短连接的处理策略(模拟心跳) 作为一个可能会和很多Client进行通讯交互的Server,首先要保证的就是整个Server运行状态的稳定性,因此在和Cli...

43870

扫码关注云+社区

领取腾讯云代金券