socket接受线程:C语言为了高并发所以选择了epoll。当程序启动的时候(g_net_update.c文件中main函数,会启动一个thread见函数create_accept_task)这个thread就处理一件事情,只管接收客户端的连接,当有连接进来的时候 通过epoll_ctl函数,把socket fd 加入到epoll里面去,epoll设置监听事件EPOLLIN | EPOLLET主要是监听的是加入到epoll中的socket是否可读(因为我的需求是客户端连上了server就会马上向server发送一份数据的)。其它的部分在主线程中处理。
主线程:是一个无线循环,epoll_wait 函数相当于把客户端的连接从epoll中拿出来(因为我们监听的是EPOLLIN | EPOLLET)说明这个时候客户端有数据发送过来)。再通过recv_buffer_from_fd 函数把客户端发送过来的数据读出来。然后其他的一切就抛给线程池去处理。
线程池:(代码中我会在池里面创建15个线程) 双向链表。加入线程就是在链表后面加一个链表项,链表的前面会一个一个被拿出来处理。主要是malloc 函数free函数,sem_wait函数sem_post的处理(sem_wait 会阻塞当值大于0是会减一,sem_post是值加一)。typedef void* (FUNC)(void arg, int index)是我们自定义的线程的逻辑处理部分,arg是参数,index是第几个线程处理(我们隐形的给每个线程都标了号),例如代码中的respons_stb_info,更加具体可以看看代码里面是怎么实现的。聪明的你也可以改掉这块的内容改成动态线程池,当某个时刻的处理比较多的时候能够动态的增加线程,而不像我代码里面的是固定的。
数据库连接池:按照我的需求在处理客户端请求数据的时候是要访问数据库的。就是一下子创建出一堆的数据连接。要访问数据库的时候先去数据库连接池中找出空闲的连接,具体可以看下代码。使用的时候可以参考下database_process.c文件(代码中数据库连接池和线程池中的个数是一样的)。这里我想说下get_db_connect_from_pool这个函数,我用了随机数,我是为了不想每次都从0开始去判断哪个连接没有用到。为了数据库连接池中的每个链接都能等概率的使用到,具体的还是可以看下代码的实现。
高并发C10KC10k:服务器同时处理1W个TCP连接。
C10M:服务器同时处理1kw个TCP连接。
实现高并发的本质技术是 事件驱动 和 异步开发 。协程也是依靠这俩实现高并发的。
最简单的,就是一个 UDP请求 。
因为一个UDP请求通常仅由一个网络报文组成。
请求——事件——回调函数
因为常见的HTTP等协议都是基于TCP实现的,TCP是一个面向字节流的协议,所以会导致HTTP请求的大小不受限制。
当一个HTTP请求的大小超过TCP报文的最大长度的时候,就会被拆分为多个报文,然后在接收端的缓冲区重组和排序,所以说,不是每个到达的TCP报文都能生成事件(不完整)。
事件只有2种类型——
第二次握手结束,客户端->服务器的通道就建立好了,客户端就会产生写事件。
第三次握手,客户端发送的ACK报文到达服务器后,服务器产生读事件。
看着有些多此一举
写缓冲区,write函数,进程
关闭连接的时候,被动方会产生读事件,从而调用close函数关闭连接。
服务器里面对资源的操作从快到慢,依次是CPU,内存,磁盘,网络。
前两个都很快,所以不需要考虑事件驱动。
然而磁盘和网络都需要采用 事件驱动的异步方式 来处理。
当下的事件驱动,针对的都是网络事件。
网络事件是由内核产生的,进程使用epoll这样的多路复用技术就可以获取到它们(网络事件)。
太复杂了慢慢看
好我们这里直接进入总结——
网络报文到达后,内核就产生了读写事件,epoll函数的作用则是可以 让进程高效地收集这些事件 。
还要确保在进程中处理每个事件的时间足够短,从而才能及时地处理所有的请求——这个过程中,要避免使用 阻塞socket ,也要把耗时长的操作拆分成多份。
epoll多路复用是一种机制,可以实现C10M的高并发服务。
因为网络消息的传输第一比较慢,第二也不可控,所以用网络事件驱动请求的性价比最高。
TCP报文是如何产生事件的?
可以用多路复用技术获取事件。epoll的优势在于取消了收集事件的时候重复传递大量的socket参数。
欢迎分享,转载请注明来源:夏雨云
评论列表(0条)