前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >网络基础篇-网络编程

网络基础篇-网络编程

作者头像
Check King
发布2021-08-09 10:41:11
6740
发布2021-08-09 10:41:11
举报
文章被收录于专栏:互联网后台技术专栏

SOCKET

  • 服务端创建socket, 然后绑定一个ip和端口(bind),服务器如果有多个网卡,就会有多个ip, 然后调用listen()函数监听,等待客户端连接。
  • 客户端创建socket, 调用connect()函数连接服务端。
  • 进行完三次握手,服务端收到客户端的链接,accept()返回一个新的socket。
  • 客户端和服务端后面就可以正常读写数据。

在内核中,为每个socket维护两个队列,一个是已建立连接的队列,也就是完成了三次握手,处于established状态,一个是还没有完全建立连接的队列,处于sync_rcvd状态。

在linux中,socket是一个文件,有对应的文件描述符,网络读写都是通过这个文件描述符的。这个文件描述符有一个对应的socket结构,包含两个队列,一个是发送队列,一个是接收队列。

  • 一台服务器能建立多少个链接呢?

一个socket由五元组(如果不考虑domain,就是四元组):(本机ip, 本机端口,对端ip, 对端端口,domain), 其中对于一台服务器来说,本机IP, 本机端口,domain都是固定的。这样连接数由对端ip,对端端口数限制,也就是2^32*2^16=2^48。但是还受限于服务器能创建的句柄数,可以通过ulimit来修改,另一个限制是内存,因为维护每个socket是需要内存资源的。

select/poll/epoll

  • I/O多路复用

I/O多路复用可以让我们的应用程序同时处理多个I/O, 有I/O事件的时候通知应用程序去处理, 不需要应用程序一直等待某个I/O事件。有了I/O多路复用,应用程序不必要为每个socket链接开一个线程或者进程单独处理,这样就可以突破了C10K问题。

  • select

select将需要监听的fd列表拷贝到内核空间,如果有读写事件或者timeout了,应用层收到通知,然后遍历各个监听的fd,找到有事件的fd。

select的不足是包括,linux下最多能监听的事件数是1024,并且需要拷贝fd列表到内核空间,需要遍历fd列表才能找到具体事件的fd。

  • poll

poll对select做了一个优化,突破了最大监听数1024的限制,但是没有解决另外两个性能问题。

  • epoll

epoll是一个终极解决方案,通过mmap, 将需要监听的fd列表的内存空间映射成与内核空间同一份,避免用户态到内核态的拷贝。

epoll通过红黑树来组织fd集合,当有事件发生时,将有事件的fd列表返回给应用程序。这样应用程序只需要遍历有事件的fd。

边缘触发(ET)与条件触发(LT):边缘触发的意思是,只有第一次满足条件的时候才触发,之后就不会再传递同样的时间了。水平触发则是不断的把这个事件,传递给应用程序,知道应用程序处理这个事件了。

网络并发模型设计

  • 阻塞I/O+进程

这种方式最为简单,服务端接收每个连接,都fork一个独立的进程来处理这个链接的读写事件,各个链接互不影响。但是缺点比较明显,效率不高,扩展性差,资源占用率高。

代码语言:javascript
复制
for (;;) {
    fd <- accept()
    process <- fork()
    process_run(fd)
}
  • 阻塞I/O + 线程

和上面一个模型类似,只是把进程改成了线程,减少了资源占用。

代码语言:javascript
复制
for (;;) {
   fd <- accept()
   thread <- pthread_create()
   thread_run(td)
}

为了避免创建过多的线程数据,可以采用线程池的方式来处理。

代码语言:javascript
复制
for (;;) {
    fd <- accept()
    push_queue(fd)
}

for (;;) {
    fd <- get_from_queue()
    thread <- thread_pool.get()
    thread_run(fd)
}
  • 非阻塞I/O +事件通知+ 单线程

可以通过select/poll这样的I/O多路复用技术,来实现事件处理, 等待有事件的时候才唤醒处理

代码语言:javascript
复制
for (;;) {
   poller.disptch()
   for fd in registered_fdset {
         if (is_readable(fd)) {
            handle_read(fd)
         } else if (is_writeable(fd)) {
            handle_write(fd)
         }
   }
}

可以采用更高效的epoll,直接遍历有事件的fdset,就变成如下

代码语言:javascript
复制
for (;;) {
   poller.disptch()
   for fd in active_event_set {
         if (is_readable(fd)) {
            handle_read(fd)
         } else if (is_writeable(fd)) {
            handle_write(fd)
         }
   }
}
  • 非阻塞I/O +事件通知+ 多线程

引入多线程,可以利用cpu的多核能力,采用I/O多路复用和多线程来处理网络事件,这种模式也叫Recator模式。通常在实现的时候,一个主Recator(main reactor)用一个线程来监听网络连接,并接收socket,当接收到一个socket, 把socket交给某个子Reactor(sub reactor )去处理,有多个子Reactor, 每个子reactor对应一个线程,通过I/O多路复用处理自己所负责的网络连接的读写事件,以读取完整的请求包和写入完整的发送包。这里只是处理网络读写,业务逻辑往往也是交给独立的线程去处理,通常是一个线程池,网络读写的sub reactor和业务逻辑直接通过队列来解耦。线程池里的线程读取队列,并做业务逻辑处理和编解码。如下图:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SOCKET
  • select/poll/epoll
  • 网络并发模型设计
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档