在客户端使用epoll控制多个socket发送数据,与在服务器上是类似的,也是把一个连续的同步过程拆成多个非阻塞的阶段,在一个线程内实现高并发,而不是开多个线程。
客户端使用多个socket异步高并发,一般是对服务器做压力测试的代码。
如上图,把处理过程拆分为5个阶段:开始START,连接中CONNECTING,连接完成CONNECTED,发送完成HTTP_SENT,关闭CLOSE。
拆分开之后就需要一个上下文来记录状态和缓冲区信息,并且定义一个处理函数handler来根据不同的状态进行处理,见图中的client_t结构体。
按照代码从上到下的顺序,先说一下各个阶段的处理函数。
1,下图的client_connect()函数在START状态时调用,与服务器异步建立连接。
connect之后把写事件EPOLLOUT加入epoll描述符监控,然后把状态更新为CONNECTING连接中。
2,状态为CONNECTING时,调用client_connect_check()函数。
它判断是否为写事件,如果是则用getsockopt()读取错误码,然后填充http请求的缓冲区。
最后,设置状态为连接完成CONNECTED,继续监控写事件EPOLLOUT,等待发送数据。
一个文件描述符fd在第一次加入epoll时用EPOLL_CTL_ADD,第二次用EPOLL_CTL_MOD修改要监控的事件。
因为接下来要发送http请求,这里还是EPOLLOUT事件。
3,client_http_send()为发送函数,在socket可写时一直写数据,直到把缓冲区的http请求数据写完,然后监控socket的读事件EPOLLIN,等待服务器的响应。
如果被打断,errno为EINTR,继续写。
如果不可写,errno为EAGAIN,则退出等待下次可写事件。
写完之后,用epoll_ctl()函数把对应的事件改为EPOLLIN,参数依然是EPOLL_CTL_MOD。
client的状态变为HTTP_SENT。
4,在可读的时候,使用recv读数据,并记录读到的字节数。
这里我们没有做http解析,所以不知道数据什么时候读完。
recv()有可能返回0,这时socket里没数据可读,break。
5,client_close()函数,我们在CLOSE状态或者出错时调用,关闭socket,从新连接。
首先把socket的fd从epoll监控中删除,这里用EPOLL_CTL_DEL参数,并且epoll_event的指针为NULL。
然后关闭socket的fd,并把它设为-1,免得误用。
释放http请求数据的写缓冲区,把状态再次设置为START,再次开始。
下图为client_handler()函数,它会根据状态调用不同的函数处理,通过一个switch case语句。
如果出错则把状态设置为CLOSE。
如果发送完http请求了,则在3秒后把状态设置为CLOSE(默认3秒内读完了响应,读不完也没事)。
设置为CLOSE之后,连接会关闭,并再次开始。
client_handler实际管理了一个client的状态机,让客户端不断的重复与服务器的http请求过程。
最后是main函数,屏蔽了SIGPIPE信号,然后创建epfd。
初始化client数组,状态设置为START,fd为-1,记录时间。
之后是一个while循环,使用epoll_wait监控,500毫秒超时。
client_t的指针在往epfd里添加事件时加在epoll_event的data.ptr这里。
把ev->events复制给c->events 是为了在handler里对比是写事件还是读事件。
第二个for循环相当于处理定时器,这里没做定时器的管理结构,只是在handler里比较了当前时间和client记录的上次时间,固定为3秒。
我们把N_CLIENT的数字通过main函数的命令行参数传过来,就可以灵活选择客户端的数量了。
最后两张是运行结果图。
举报/反馈