better threadsafe connecting

This commit is contained in:
André Mussche 2014-03-07 13:40:04 +01:00
parent d28d5b1457
commit b2ffe540df
2 changed files with 118 additions and 62 deletions

View file

@ -497,17 +497,25 @@ var
i: Integer;
sKey, sResponseKey: string;
sSocketioextended: string;
bLocked: boolean;
begin
Assert((IOHandler = nil) or not IOHandler.IsWebsocket);
//remove from thread during connection handling
TIdWebsocketMultiReadThread.Instance.RemoveClient(Self);
//reset pending data
if IOHandler <> nil then
IOHandler.Clear;
bLocked := False;
strmResponse := TMemoryStream.Create;
Self.Lock;
try
//reset pending data
if IOHandler <> nil then
begin
IOHandler.Lock;
bLocked := True;
if IOHandler.IsWebsocket then Exit;
IOHandler.Clear;
end;
//special socket.io handling, see https://github.com/LearnBoost/socket.io-spec
if SocketIOCompatible then
begin
@ -693,6 +701,10 @@ begin
Request.Clear;
strmResponse.Free;
if bLocked and (IOHandler <> nil) then
IOHandler.Unlock;
Unlock;
//add to thread for auto retry/reconnect
TIdWebsocketMultiReadThread.Instance.AddClient(Self);
end;
@ -1204,7 +1216,7 @@ begin
end;
//reconnect needed? (in background)
if FReconnectlist.Count > 0 then
if (FReconnectlist <> nil) and (FReconnectlist.Count > 0) then
begin
if FReconnectThread = nil then
FReconnectThread := TIdWebsocketQueueThread.Create(False{direct start});
@ -1217,7 +1229,7 @@ begin
while FReconnectlist.Count > 0 do
begin
chn := nil;
try
try
//get first one
l := FReconnectlist.LockList;
try
@ -1239,15 +1251,18 @@ begin
if ( (ws = nil) or
(SecondsBetween(Now, ws.LastActivityTime) >= 5) ) then
begin
try
if ws <> nil then
ws.LastActivityTime := Now;
chn.ConnectTimeout := 1000;
if (chn.Host <> '') and (chn.Port > 0) then
chn.TryUpgradeToWebsocket;
except
//just try
end;
try
if not chn.Connected then
begin
if ws <> nil then
ws.LastActivityTime := Now;
//chn.ConnectTimeout := 1000;
if (chn.Host <> '') and (chn.Port > 0) then
chn.TryUpgradeToWebsocket;
end;
except
//just try
end;
end;
//remove from todo list

View file

@ -1,5 +1,7 @@
unit IdIOHandlerWebsocket;
{$DEFINE DEBUG_WS}
//The WebSocket Protocol, RFC 6455
//http://datatracker.ietf.org/doc/rfc6455/?include_text=1
@ -209,6 +211,8 @@ begin
//SetLength(Result, Length(aValue));
for i := 0 to High(AValue) do
begin
if AValue[i] = 0 then Exit;
if (AValue[i] < 33) or
( (AValue[i] > 126) and
(AValue[i] < 161) )
@ -550,24 +554,40 @@ begin
if UseSingleWriteThread and IsWebsocket and (GetCurrentThreadId <> TIdWebsocketWriteThread.Instance.ThreadID) then
Assert(False, 'Write done in different thread than TIdWebsocketWriteThread!');
if not IsWebsocket then
Result := inherited WriteDataToTarget(ABuffer, AOffset, ALength)
else
begin
Lock;
try
if FWriteTextToTarget then
Result := WriteData(ABuffer, wdcText, True{send all at once},
webBit1 in ClientExtensionBits, webBit2 in ClientExtensionBits, webBit3 in ClientExtensionBits)
else
Result := WriteData(ABuffer, wdcBinary, True{send all at once},
webBit1 in ClientExtensionBits, webBit2 in ClientExtensionBits, webBit3 in ClientExtensionBits);
except
Unlock; //always unlock when socket exception
FClosedGracefully := True;
Raise;
Lock;
try
Result := -1;
if not IsWebsocket then
begin
{$IFDEF DEBUG_WS}
if Debughook > 0 then
OutputDebugString(PChar(Format('Send (non ws, TID:%d, P:%d): %s',
[getcurrentthreadid, Self.Binding.PeerPort, BytesToStringRaw(ABuffer)])));
{$ENDIF}
Result := inherited WriteDataToTarget(ABuffer, AOffset, ALength)
end
else
begin
{$IFDEF DEBUG_WS}
if Debughook > 0 then
OutputDebugString(PChar(Format('Send (ws, TID:%d, P:%d): %s',
[getcurrentthreadid, Self.Binding.PeerPort, BytesToStringRaw(ABuffer)])));
{$ENDIF}
try
if FWriteTextToTarget then
Result := WriteData(ABuffer, wdcText, True{send all at once},
webBit1 in ClientExtensionBits, webBit2 in ClientExtensionBits, webBit3 in ClientExtensionBits)
else
Result := WriteData(ABuffer, wdcBinary, True{send all at once},
webBit1 in ClientExtensionBits, webBit2 in ClientExtensionBits, webBit3 in ClientExtensionBits);
except
FClosedGracefully := True;
Result := -1;
Raise;
end;
end;
Unlock; //normal unlock (no double try finally)
finally
Unlock;
end;
end;
@ -596,27 +616,44 @@ begin
IsWebsocket := True;
end;
if not IsWebsocket then
Result := inherited ReadDataFromSource(VBuffer)
else
begin
Lock;
try
//we wait till we have a full message here (can be fragmented in several frames)
Result := ReadMessage(VBuffer, wscode);
Result := -1;
Lock;
try
if not IsWebsocket then
begin
Result := inherited ReadDataFromSource(VBuffer);
{$IFDEF DEBUG_WS}
if Debughook > 0 then
OutputDebugString(PChar(Format('Received (non ws, TID:%d, P:%d): %s',
[getcurrentthreadid, Self.Binding.PeerPort, BytesToStringRaw(VBuffer)])));
{$ENDIF}
end
else
begin
try
//we wait till we have a full message here (can be fragmented in several frames)
Result := ReadMessage(VBuffer, wscode);
//first write the data code (text or binary, ping, pong)
FInputBuffer.Write(LongWord(Ord(wscode)));
//we write message size here, vbuffer is written after this. This way we can use ReadStream to get 1 single message (in case multiple messages in FInputBuffer)
if LargeStream then
FInputBuffer.Write(Int64(Result))
else
FInputBuffer.Write(LongWord(Result))
except
Unlock; //always unlock when socket exception
FClosedGracefully := True; //closed (but not gracefully?)
Raise;
{$IFDEF DEBUG_WS}
if Debughook > 0 then
OutputDebugString(PChar(Format('Received (ws, TID:%d, P:%d): %s',
[getcurrentthreadid, Self.Binding.PeerPort, BytesToStringRaw(VBuffer)])));
{$ENDIF}
//first write the data code (text or binary, ping, pong)
FInputBuffer.Write(LongWord(Ord(wscode)));
//we write message size here, vbuffer is written after this. This way we can use ReadStream to get 1 single message (in case multiple messages in FInputBuffer)
if LargeStream then
FInputBuffer.Write(Int64(Result))
else
FInputBuffer.Write(LongWord(Result))
except
Unlock; //always unlock when socket exception
FClosedGracefully := True; //closed (but not gracefully?)
Raise;
end;
end;
finally
Unlock; //normal unlock (no double try finally)
end;
end;
@ -662,7 +699,7 @@ begin
wdcText, wdcBinary:
begin
if lFirstDataCode <> wdcNone then
raise EIdWebSocketHandleError.Create('Invalid frame: specified data code only allowed for the first frame');
raise EIdWebSocketHandleError.Create('Invalid frame: specified data code only allowed for the first frame. Data = ' + BytesToStringRaw(iaReadBuffer));
lFirstDataCode := lDataCode;
FMessageStream.Clear;
@ -671,7 +708,7 @@ begin
wdcContinuation:
begin
if not (lFirstDataCode in [wdcText, wdcBinary]) then
raise EIdWebSocketHandleError.Create('Invalid frame continuation');
raise EIdWebSocketHandleError.Create('Invalid frame continuation. Data = ' + BytesToStringRaw(iaReadBuffer));
TIdStreamHelper.Write(FMessageStream, iaReadBuffer);
end;
wdcClose:
@ -780,8 +817,10 @@ var
if Result then
begin
FWSInputBuffer.Write(temp);
//if debughook > 0 then
// OutputDebugString(PChar('Received: ' + BytesToStringRaw(temp)));
{$IFDEF DEBUG_WS}
if debughook > 0 then
OutputDebugString(PChar('Received: ' + BytesToStringRaw(temp)));
{$ENDIF}
end;
end;
@ -809,8 +848,10 @@ var
begin
InternalReadDataFromSource(temp, True);
FWSInputBuffer.Write(temp);
//if debughook > 0 then
// OutputDebugString(PChar('Received: ' + BytesToStringRaw(temp)));
{$IFDEF DEBUG_WS}
if debughook > 0 then
OutputDebugString(PChar('Received: ' + BytesToStringRaw(temp)));
{$ENDIF}
if FWSInputBuffer.Size < aCount then
Sleep(1);
end;
@ -864,7 +905,7 @@ begin
C_FrameCode_Ping: aDataCode := wdcPing;
C_FrameCode_Pong: aDataCode := wdcPong;
else
raise EIdException.CreateFmt('Unsupported data code: %d', [iCode]);
raise EIdException.CreateFmt('Unsupported data code: %d. Buffer = %s', [iCode, FWSInputBuffer.AsString]);
end;
//Mask: 1 bit
@ -896,9 +937,9 @@ begin
//"All frames sent from client to server must have this bit set to 1"
if IsServerSide and not bHasMask then
raise EIdWebSocketHandleError.Create('No mask supplied: mask is required for clients when sending data to server')
raise EIdWebSocketHandleError.Create('No mask supplied: mask is required for clients when sending data to server. Buffer = ' + FWSInputBuffer.AsString)
else if not IsServerSide and bHasMask then
raise EIdWebSocketHandleError.Create('Mask supplied but mask is not allowed for servers when sending data to clients');
raise EIdWebSocketHandleError.Create('Mask supplied but mask is not allowed for servers when sending data to clients. Buffer = ' + FWSInputBuffer.AsString);
//Masking-key: 0 or 4 bytes
if bHasMask then
@ -1050,8 +1091,8 @@ begin
Inc(ioffset, Result);
until ioffset >= Length(bData);
//if debughook > 0 then
// OutputDebugString(PChar('Written: ' + BytesToStringRaw(bData)));
// if debughook > 0 then
// OutputDebugString(PChar('Written: ' + BytesToStringRaw(bData)));
finally
Unlock;
strmData.Free;