主要流程:
如果处理简单的客户端请求单线程即可处理,此方法较为简单不做阐述。如果需要处理复杂的工作逻辑或者需要模拟机器人请求大量的服务器,需要两个线程,一个任务线程,一个工作线程,可达到每秒平均处理数千的并发量。
任务线程:
从任务队列获取任务,创建socket并设置非阻塞,将该socket加入epoll句柄,connect目标服务器。
sockfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, fcntl (socket_fd, F_GETFL,0) | O_NONBLOCK);
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLOUT | EPOLLET;
ev.data.ptr = my_ev;(my_ev保存了后续处理的所有数据);
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);
connect(sockfd,(struct sockaddr*)&my_ev->dest,sizeof(sockaddr_in))(无需判断connect返回值,后续会在epoll事件中触发);
工作线程:
等待epoll事件触发,如果返回(EPOLLHUP | EPOLLERR),则证明连接出错(目标ip不存在,目标端口不在监听状态等原因)。如果返回EPOLLOUT,则证明连接成功,可以进行写操作。如果返回EPOLLIN | EPOLLOUT,则证明可读可写,这时候就需要按照自定义的数据来判断了,如过接收完成则进行处理后续工作逻辑。无论事件成功或者失败处理完之后均要做后续清理工作。
epoll_wait(epfd,events,YOUR_SIZE,-1);
error_handle()
do_write()
do_read()
clean();
https特殊性:
http在tcp连接成功之后直接开始与对端交互,比较简单,此处省略,需要注意的是所有的收发数据均在非阻塞模式下进行,关于非阻塞socket的使用可以参考另一篇文章非阻塞socket使用。此文所使用的https库为openssl,tcp建立连接成功后进行ssl连接,连接时间可能较长,由于全部使用非阻塞,所以ssl连接是否成功需要事件返回后多次判断。
连接准备工作(ev为自定义数据的指针)
ev->ctx = SSL_CTX_new(SSLv23_client_method());
ev->ssl = SSL_new(ev->ctx);
SSL_set_mode(ev->ssl,SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_set_fd(ev->ssl,ev->sockfd);
ssl连接(需要多次判断)
int ssl_conn_ret = SSL_connect(ev->ssl);
if (1 == ssl_conn_ret)
{
// ssl连接成功,开始和对端交互
}
//连接失败,根据错误码判断是需要更多时间来完成连接还是连接失败。
int ssl_conn_err = SSL_get_error(ev->ssl,ssl_conn_ret);
if (SSL_ERROR_WANT_WRITE == ssl_conn_err||SSL_ERROR_WANT_READ == ssl_conn_err)
{
// 需要更多时间来完成ssl连接
}
else
{
//连接失,做清理工作
}
ssl读写判断
while((rtn = SSL_write(ev->ssl,ev->send_buf,ev->send_len)) <= 0)
{
int ssl_conn_err = SSL_get_error(ev->ssl,rtn);
if (SSL_ERROR_WANT_WRITE == ssl_conn_err && rtn != 0)
{
continue;
}
// 写失败
return -1;
}
返回值判断
rtn = send_len:发送成功
rtn < send_len:发送失败
do
{
rtn = SSL_read(ev->ssl,recv_buf+size,1024);
if(rtn < 0)
{
int ssl_conn_err = SSL_get_error(ev->ssl,rtn);
if (SSL_ERROR_WANT_READ == ssl_conn_err)
{
continue;
}
//读失败
free(recv_buf);
return -1;
}
size += rtn;
}
while(rtn > 0 && size < 1024*1024);
接收判断
size = 0:对端关闭
size > 0:接收成功
http读写
发送
while((size = send(ev->sockfd,ev->send_buf,ev->send_len,0)) < 0)
{
if(errno == EINTR || errno == EAGAIN)
{
continue;
}
//失败
return -1;
}
返回值判断
size = send_len:发送成功
size < send_len:发送失败
接收
do
{
rtn = recv(ev->sockfd,recv_buf+size,1024,0);
if(rtn < 0)
{
if((errno == EINTR || errno == EAGAIN))
{
continue;
}
//接收失败
free(recv_buf);
return -1;
}
size += rtn;
}
while(rtn > 0 && size < 1024*1023);
接收判断
size = 0:对端关闭
size > 0:接收成功
注意:
send分为阻塞和非阻塞,阻塞模式下,如果正常的话,会直到把你所需要发送的数据发完再返回;非阻塞,会根据你的socket在底层的可用缓冲区的大小,来将你的缓冲区当中的数据拷贝过去,有多大缓冲区就拷贝多少,缓冲区满了就立即返回,这个时候的返回值,只表示拷贝到缓冲区多少数据,但是并不代表发送多少数据,此时本程序直接认为发送失败,具体原因未知,可能缓冲区满了或者内存不够什么原因,
网友评论