掘金 后端 ( ) • 2024-04-26 11:00

写在文章开头

go语言对网络交互进行的高度的抽象,这使得我们进行网络IO编程时只需调用几个简单的API即可实现高性能的网络程序。而本文会从计算网络的角度来聊聊网络IO这一话题。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

详解网络IO

网络五层模型

学过计算机网络的同学都知道网络模型,这里笔者以5层网络模型为例,可以看到go语言对于网络的抽象是对传输层进行封装,这使得我们开发或者使用其应用层协议都会有着出色的性能表现:

TCP协议

我们这里也对传输层进行简单的复习,以TCP协议为例,两台计算机进行交互时必须经过三次握手确定双方收发能力正常,才能进行数据传输,且传输结束之后需要通过四次挥手断开连接:

Socket简化TCP通信

很明显TCP为了保证可靠传输对于通信机制做了很多的处理,所以为了便于开发者使用该协议,操作系统通过对socket完成对于TCP网络连接的抽象,基于socket的抽象,我们的网络交互流程从宏观角度来看是以下几个步骤:

  1. 服务端绑定端口监听连接接入。
  2. 客户端socket发起连接。
  3. 连接建立,开始进行网络数据交互。
  4. 结束通信双方关闭连接。

对应我们给出交互流程图:

网络IO模型

早期网络通信模型是一个socket监听连接,和客户端建立连接后为每个线程分配一个线程处理交互逻辑,也就是我们常说的阻塞时IO(BIO)很明显这种一连接一线程的设计如果遇到建立连接后很少进行数据收发的连接的情况,不仅会导致宝贵的线程资源浪费,在高并发的场景还会导致C10k问题:

于是我们提出IO多路复用,通过一个线程轮询处理建立连接socket,注意,这个轮询处理是非阻塞的,即某个socket没数据也会直接返回没有数据,轮询结束后,我们可以基于这个轮询结果找到有数据的socket进行读写操作,很明显这种做法会频繁在用户态和内核态之间来回切换,还是没有很好的解决c10k问题:

于是Linux系统就提出了epoll模型了,其工作原理收到新连接后,将其读写事件注册到epoll事件池中,程序只需调用epoll模型提供的apiepoll_wait(C语言内置函数)进行非阻塞获取所有就绪的socket并直接处理其读写事件:

这里我们也给出对应的C代码示例:

int listenfd = socket(AF_INET, SOCK_STREAM, 0);   
bind(listenfd, ...)
listen(listenfd, ...)

int epfd = epoll_create(...);
epoll_ctl(epfd, ...); //将所有需要监听的fd添加到epfd中

while(1){
    int n = epoll_wait(...)
    for(接收到数据的socket){
        //处理
    }
}

go语言内置的工具

自此我们完成了对于网络IO模型的扫盲,接下来我们就基于go语言的API实现一个简单的高性能网络交互程序,代码如下,可以看到只需我们进行连接监听和读写操作都是采用go语言封装好的上层函数,对于epoll等IO模型go语言会在编译时根据操作系统信息自行决定,而我们只需基于这些函数获取连接,并将连接交给协程处理即可实现一个高性能的goroutine-per-connection网络程序:

func main() {
 //创建socket
 listen, err := net.Listen("tcp", "localhost:8080")
 if err != nil {
  log.Fatal(err)
 }
 for {
  //基于epoll监听连接
  conn, err := listen.Accept()
  if err != nil {
   log.Fatal(err)
  }
  //开一个协程处理该连接
  go func() {
   //基于epoll进行写操作
   conn.Write([]byte("hello"))
   //关闭连接
   conn.Close()
  }()

 }

}

小结

以上便是笔者对于网络IO知识和go语言网络程序编写的入门扫盲,希望对你有帮助。

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

参考

基于抓包详解TCP协议 :https://blog.csdn.net/shark_chili3007/article/details/108930066

Go netpoller 网络模型之源码全面解析(二) :https://zhuanlan.zhihu.com/p/299047984

本文使用 markdown.com.cn 排版