【发布时间】:2019-05-20 23:56:13
【问题描述】:
我正在为从桌面客户端到浏览器的视频流编写 IOCP 服务器。 双方都使用 WebSocket 协议来统一服务器的架构(因为浏览器没有其他方法可以进行全双工交换)。
工作线程是这样开始的:
unsigned int __stdcall WorkerThread(void * param){
int ThreadId = (int)param;
OVERLAPPED *overlapped = nullptr;
IO_Context *ctx = nullptr;
Client *client = nullptr;
DWORD transfered = 0;
BOOL QCS = 0;
while(WAIT_OBJECT_0 != WaitForSingleObject(EventShutdown, 0)){
QCS = GetQueuedCompletionStatus(hIOCP, &transfered, (PULONG_PTR)&client, &overlapped, INFINITE);
if(!client){
if( Debug ) printf("No client\n");
break;
}
ctx = (IO_Context *)overlapped;
if(!QCS || (QCS && !transfered)){
printf("Error %d\n", WSAGetLastError());
DeleteClient(client);
continue;
}
switch(auto opcode = client->ProcessCurrentEvent(ctx, transfered)){
// Client owed to receive some data
case OPCODE_RECV_DEBT:{
if((SOCKET_ERROR == client->Recv()) && (WSA_IO_PENDING != WSAGetLastError())) DeleteClient(client);
break;
}
// Client received all data or the beginning of new message
case OPCODE_RECV_DONE:{
std::string message;
client->GetInput(message);
// Analizing the first byte of WebSocket frame
switch( opcode = message[0] & 0xFF ){
// HTTP_HANDSHAKE is 'G' - from GET HTTP...
case HTTP_HANDSHAKE:{
message = websocket::handshake(message);
while(!client->SetSend(message)) Sleep(1); // Set outgoing data
if((SOCKET_ERROR == client->Send()) && (WSA_IO_PENDING != WSAGetLastError())) DeleteClient(client);
break;
}
// Browser sent a closing frame (0x88) - performing clean WebSocket closure
case FIN_CLOSE:{
websocket::frame frame;
frame.parse(message);
frame.masked = false;
if( frame.pl_len == 0 ){
unsigned short reason = 1000;
frame.payload.resize(sizeof(reason));
frame.payload[0] = (reason >> 8) & 0xFF;
frame.payload[1] = reason & 0xFF;
}
frame.pack(message);
while(!client->SetSend(message)) Sleep(1);
if((SOCKET_ERROR == client->Send()) && (WSA_IO_PENDING != WSAGetLastError())) DeleteClient(client);
shutdown(client->Socket(), SD_SEND);
break;
}
IO 上下文结构:
struct IO_Context{
OVERLAPPED overlapped;
WSABUF data;
char buffer[IO_BUFFER_LENGTH];
unsigned char opcode;
unsigned long long debt;
std::string message;
IO_Context(){
debt = 0;
opcode = 0;
data.buf = buffer;
data.len = IO_BUFFER_LENGTH;
overlapped.Offset = overlapped.OffsetHigh = 0;
overlapped.Internal = overlapped.InternalHigh = 0;
overlapped.Pointer = nullptr;
overlapped.hEvent = nullptr;
}
~IO_Context(){ while(!HasOverlappedIoCompleted(&overlapped)) Sleep(1); }
};
客户端发送功能:
int Client::Send(){
int var_buf = O.message.size();
// "O" is IO_Context for Output
O.data.len = (var_buf>IO_BUFFER_LENGTH)?IO_BUFFER_LENGTH:var_buf;
var_buf = O.data.len;
while(var_buf > 0) O.data.buf[var_buf] = O.message[--var_buf];
O.message.erase(0, O.data.len);
return WSASend(connection, &O.data, 1, nullptr, 0, &O.overlapped, nullptr);
}
当桌面客户端断开连接时(它只使用 closesocket() 来执行此操作,没有 shutdown())GetQueuedCompletionStatus 返回 TRUE 并将 transfered 设置为 0 - 在这种情况下 WSAGetLastError() 返回 64(指定的网络名称不再是可用),并且它很有意义 - 客户端已断开连接(与if(!QCS || (QCS && !transfered)) 一致)。但是当浏览器断开连接时,错误代码让我很困惑……可以是0、997(等待操作)、87(无效参数)……并且没有与连接结束相关的代码。
为什么 IOCP 选择此事件?它如何选择挂起的操作?为什么传输0字节时错误为0?它还会导致无休止地尝试删除与重叠结构关联的对象,因为析构函数调用~IO_Context(){ while(!HasOverlappedIoCompleted(&overlapped)) Sleep(1); } 进行安全删除。在DeleteClient 调用中,套接字以closesocket() 关闭,但是,如您所见,我在它之前发布了一个shutdown(client->Socket(), SD_SEND); 调用(在FIN_CLOSE 部分)。
我了解连接有两端,在服务器端关闭它并不意味着另一端也会关闭它。但我需要创建一个稳定的服务器,不受坏连接和半开连接的影响。例如,Web 应用程序的用户可以快速按 F5 重新加载页面几次(是的,有些家伙这样做:)) - 连接将重新打开几次,并且服务器不能因此而延迟或崩溃。
如何在 IOCP 中处理这种“坏”事件?
【问题讨论】:
标签: c++ sockets winapi winsock iocp