unified diagnostic services,unifiedremote官网

  

  套接字编程基础   

  

  在开始之前,我们先梳理几个需要提前了解的概念:   

  

  Socket:字面翻译为“插座”。在计算机通信领域,socket翻译为“套接字”,是计算机之间的一种约定或通信方式。通过套接字,一台计算机可以从其他计算机接收数据,并向其他计算机发送数据。我们可以通过把插头插入插座从电网获得电力供应。同样,应用程序需要连接到互联网上才能与远程计算机进行数据传输,而socket就是用来连接互联网的工具。   

  

  另外,我们需要了解socket编程的基本流程。   

  

     

  

  2.同步阻塞IO。首先回顾以下概念:阻塞IO是指如果发起系统调用的线程从应用进程发起IO调用到内核执行IO操作并返回结果这段时间处于等待状态,那么这个IO操作就被阻塞了。   

  

     

  

  代码很简单,直接看注释就可以了,运行结果如上图所示,但是有几个问题需要强调一下:   

  

  正在等待服务器套接字。Accept()在连接时,线程被阻塞!客户端套接字。在数据接收处接收(缓冲),线程被阻塞!会导致什么问题:   

  

  只能在一次数据读取后接受下一次连接请求,并且只能接收一次数据同步非阻塞IO。看完你可能会说,这两个问题好解决。只需创建一个新线程来接收数据。于是就有了下面的代码改进。   

  

     

  

  没错,多线程解决了上面的问题,但是看上面的图,你应该会发现一个问题:才建立4个客户端连接,CPU的占用率就开始直线上升了。.   

  

  这个问题的本质是服务器的IO模型是一个阻塞IO模型。为了解决阻塞带来的问题,采用重复轮询,导致系统调用无效,从而导致CPU不断上升。   

  

  既然知道了IO复用的原因,那就来改造一下吧。应用异步方法处理连接、接收和发送数据。   

  

  首先看运行结果。从下图可以看出,除了连接建立时CPU的抖动,在消息收发阶段CPU占用率趋于平缓,占用率较低。   

  

     

  

  分析代码后,我们发现:   

  

  CPU使用率下降了,但是代码复杂度上升了。使用异步接口处理客户端连接:BeginAccept和EndAccept使用异步接口接收数据:BeginReceive和EndReceive使用异步接口发送数据:BeginSend和EndSend使用ManualResetEvent同步线程,避免线程空闲。那么你可能会好奇,上面的模型是什么样的IO复用模型?好问题,我们来看看。   

  

  为了验证I/O模型,您只需要确定在应用程序运行时启动了哪些系统调用。对于Linux系统,我们可以借助strace命令跟踪指定应用程序发起的系统调用和信号。   

  

  验证由同步阻塞I/O发起的系统调用可以使用VSCode Remote连接到您自己的Linux系统,然后创建一个新的项目Io。演示,用上面非阻塞IO的代码测试,执行下面的启动跟踪命令:   

  

  在另一个命令行上,执行nc localhost5001来模拟客户端连接。   

  

  使用netstat命令查看已建立的连接。   

  

  `   

  

  在另一个命令行上,执行ps-h|grep dotnet来获取进程Id。   

  

  从上面可以看出,进程Id是3763。通过依次执行以下命令,可以查看该进程的线程和文件描述符:   

  

  从上面的输出可以看出,当。NET核心控制台应用程序启动,多线程启动,套接字监控在文件描述符10、11、29、31中启动。哪个文件描述符正在监听端口5001?   

  

  可以看到,inode为43429的Socket正在端口号5001上侦听,所以可以找到上面的输出行LRWX-1盛杰盛杰645 month 101633603729-' socket 336043429 ',然后判断端口号5001上侦听的socket对应的文件描述符是29。   

  

  当然,你也可以从strace目录下记录的日志文件中找到线索。在本文中,我们提到了socket服务器编程的一般过程。   

,都要经过socket->bind->accept->read->write流程。所以可以通过抓取关键字,查看相关系统调用。

  

从上可知,在主线程也就是 io.3763 线程的系统调用文件中,将29号文件描述符与监听在 127.0.0.1:5001 的socket进行了绑定。同时也明白了.NET Core自动建立的另外2个socket是与diagnostic相关。 接下来咱们重点看下3763号线程产生的系统调用。

  

从中我们可以发现几个关键的系统调用: socket、 bind、 listen、 accept4、 recvmsg、 sendmsg 通过命令 man 命令可以查看下 accept4 和 recvmsg 系统调用的相关说明:

  

也就是说 accept4 和 recvmsg 是阻塞式系统调用。

  

验证I/O多路复用发起的系统调用同样以上面I/O多路复用的代码进行验证,验证步骤类似:

  

过滤 strace2 目录日志,抓取监听在 localhost:5001 socket对应的文件描述符。

  

从中可以看出同样是29号文件描述符,相关系统调用记录中 io.2449 文件中,打开文件,可以发现相关系统调用如下:

  

从中我们可以发现 accept4 直接返回-1而不阻塞,监听在 127.0.0.1:5001 的socket对应的29号文件描述符最终作为 epoll_ctl 的参数关联到 epoll_create1 创建的32号文件描述符上。最终32号文件描述符会被 epoll_wait 阻塞,以等待连接请求。我们可以抓取 epoll 相关的系统调用来验证:

  

因此我们可以断定同步非阻塞I/O的示例使用的时IO多路复用的epoll模型。

  

关于epoll相关命令,man命令可以查看下 epoll_create1 、 epoll_ctl 和、 epoll_wait 系统调用的相关说明:

  

简而言之,epoll通过创建一个新的文件描述符来替换旧的文件描述符来完成阻塞工作,当有事件或超时时通知原有文件描述符进行处理,以实现非阻塞的线程模型。

  

总结写完这篇文章,对I/O模型的理解有所加深,但由于对Linux系统的了解不深,所以难免有纰漏之处,大家多多指教。 同时也不仅感叹Linux的强大之处,一切皆文件的设计思想,让一切都有迹可循。现在.NET 已经完全实现跨平台了,那么Linux操作系统大家就有必要熟悉起来了。

相关文章