如何使TServerSocket多线程?

时间:2017-12-28 04:54:53

标签: multithreading sockets delphi

我正在测试如何获取我的Android智能手机的定期屏幕截图以及此服务器从我的设备接收到大量屏幕截图,发生这些图片没有出现在TImage中,我认为是因为TServerSocket(接收图像的部分)不在线程中。是的,我正确发送这些屏幕截图:

Java(Android):

    bitmap = Bitmap.createBitmap(mWidth + rowPadding / pixelStride, mHeight, Bitmap.Config.ARGB_8888);
    bitmap.copyPixelsFromBuffer(buffer);

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
    byte[] array = bos.toByteArray();

    DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());
    dos.writeInt(array.length);
    dos.write(array, 0, array.length);

    dos.flush();

这是我的Delphi代码,必须收到定期截图:

var
  Form1: TForm1;

   stSize: integer;   
   Stream: TMemoryStream; 
   Receiving: boolean; 
   png: TPngImage; 

   FSize: Integer;
   writing: Boolean;

implementation

{$R *.dfm}

procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
var
Item: TListItem;  
begin
    Item := ListView1.Items.Add;   
    Item.Caption := IntTostr(socket.Handle); 
    Item.SubItems.Add(Socket.RemoteAddress); 
    Item.SubItems.Add(socket.RemoteHost);  
    Item.Data := Socket.Data; 
end;

procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
var
Item: TListItem;
begin  
    Item:= ListView1.FindCaption(0, inttostr(socket.Handle), false, true, false);
    if item <> nil then
    Item.Delete;
end;

procedure TForm1.ServerSocket1ClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);
begin
    showmessage('socket erro'); 
    ErrorCode := 0;  
end;

procedure TForm1.Activate1Click(Sender: TObject);
begin
    ServerSocket1.Active := true;  
end;

procedure TForm1.Deactive1Click(Sender: TObject);
begin
    ServerSocket1.Active := false;  
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
    Stream:= TMemoryStream.Create;
    writing:= False;
end;

procedure TForm1.SendMyReqst1Click(Sender: TObject);
begin
    if ListView1.Selected = nil then exit;    
    ServerSocket1.Socket.Connections[ListView1.ItemIndex].SendText('screencapture' + #13#10); 
end;

procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
  BytesReceived: Longint;
  CopyBuffer: Pointer;
  ChunkSize: Integer;
  TempSize: Integer;
const
  MaxChunkSize: Longint = 8192;
begin

  If FSize=0 then
  begin
    begin
      Socket.ReceiveBuf(TempSize,SizeOf(TempSize));
      TempSize := ntohl(TempSize);
      Stream.SetSize(TempSize);
      FSize:= TempSize
    End;
  End;

  If (FSize>0) and not(writing) then
  begin
    GetMem(CopyBuffer, MaxChunkSize);
    writing:= True;
    While Socket.ReceiveLength>0 do
    Begin
      ChunkSize:= Socket.ReceiveLength;
      If ChunkSize > MaxChunkSize then ChunkSize:= MaxChunkSize;
      BytesReceived:= Socket.ReceiveBuf(CopyBuffer^,ChunkSize);
      Stream.Write(CopyBuffer^, BytesReceived);
      Dec(FSize,BytesReceived);
    End;
  end;

  If FSize=0 then begin

  Stream.Position := 0;
  png:=TPngImage.Create;
  png.LoadFromStream(Stream);
  img1.Picture.Assign(png);
  img1.Refresh;
  Stream.SetSize(0);
  png.Free;
  FSize:= 0;
end;

FreeMem(CopyBuffer, MaxChunkSize);
Writing:= False;
end;

end.

enter image description here

上面的Delphi代码工作正常,但只能获得1个屏幕截图,而不是很大的变化。

更新

this是我在Android上获取定期截图的代码库。

PS :看到他使用无限循环。

1 个答案:

答案 0 :(得分:2)

您显示的Delphi代码无法正确考虑TCP或多个客户端的流媒体特性:

  • 它没有正确阅读FSize。获取所有4个字节可能需要多次读取。

  • 它不会使用FSize来限制为PNG流读取的字节数。您需要准确读取FSize指定的字节数,不多也不少。只要客户端仍在发送字节,即使它们属于后续消息,它也在读取。它需要在到达流的末尾时停止读取,然后重置以显示下一条消息。

  • 它不会处理多个客户端同时发送屏幕截图的可能性。它与多个客户共享变量,从而允许它们破坏彼此的消息。

简而言之,无论多线程如何,代码都完全被破坏了。如果您在非阻塞模式下使用服务器,那么BTW不是一个因素(代码可能是,因为这是服务器的默认模式,并且代码没有使用任何服务器&#39;与线程相关的事件)。

代码不需要多线程才能正常工作。需要重写它才能正常运行。

尝试更像这样的事情:

type
  TInt32Bytes = record
    case Integer of
      0: (Bytes: array[0..SizeOf(Int32)-1] of Byte);
      1: (Value: Int32);
  end;

  TSocketState = (ReadingSize, ReadingStream);

  TSocketData = class
  public
    Stream: TMemoryStream;
    Png: TPngImage;
    State: TSocketState;
    Size: TInt32Bytes;
    Offset: Integer;
    constructor Create;
    destructor Destroy; override;
  end;

constructor TSocketData.Create;
begin
  Stream := TMemoryStream.Create;
  Png := TPngImage.Create;
end;

destructor TSocketData.Destroy;
begin
  Stream.Free;
  Png.Free;
end;

procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
var
  Item: TListItem;
begin
  Socket.Data := TSocketData.Create;
  Item := ListView1.Items.Add;
  Item.Data := Socket;
  Item.Caption := IntToStr(Socket.Handle);
  Item.SubItems.Add(Socket.RemoteAddress);
  Item.SubItems.Add(Socket.RemoteHost);
end;

procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
var
  Item: TListItem;
begin
  Item := ListView1.FindData(0, Socket, true, false);
  if Item <> nil then Item.Delete;
  TSocketData(Socket.Data).Free;
end;

procedure TForm1.ServerSocket1ClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);
begin
  ErrorCode := 0;
  Socket.Close;
end;

procedure TForm1.Activate1Click(Sender: TObject);
begin
  ServerSocket1.Active := true;
end;

procedure TForm1.Deactive1Click(Sender: TObject);
begin
  ServerSocket1.Active := false;
end;

procedure TForm1.SendMyReqst1Click(Sender: TObject);
var
  Index: Integer;
begin
  Index := ListView1.ItemIndex;
  if Index = -1 then Exit;
  ServerSocket1.Socket.Connections[Index].SendText('screencapture' + #13#10);
end;

procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
  BytesReceived: Integer;
  BufferPtr: PByte;
  SD: TSocketData;
  Item: TListItem;
begin
  SD := TSocketData(Socket.Data);

  if SD.State = ReadingSize then
  begin
    while SD.Offset < SizeOf(Int32) do
    begin
      BytesReceived := Socket.ReceiveBuf(SD.Size.Bytes[SD.Offset], SizeOf(Int32) - SD.Offset);
      if BytesReceived <= 0 then Exit;
      Inc(SD.Offset, BytesReceived);
    end;
    SD.Size.Value := ntohl(SD.Size.Value);
    SD.State := ReadingStream;
    SD.Offset := 0;
    SD.Stream.Size := SD.Size.Value;
  end;

  if SD.State = ReadingStream then
  begin
    if SD.Offset < SD.Size.Value then
    begin
      BufferPtr := PByte(SD.Stream.Memory);
      Inc(BufferPtr, SD.Offset);
      repeat
        BytesReceive := Socket.ReceiveBuf(BufferPtr^, SD.Size.Value - SD.Offset);
        if BytesReceived <= 0 then Exit;
        Inc(BufferPtr, BytesReceived);
        Inc(SD.Offset, BytesReceived);
      until SD.Offset = SD.Size.Value;
    end;
    try
      SD.Stream.Position := 0;
      SD.Png.LoadFromStream(SD.Stream);
    except
      SD.Png.Assign(nil);
    end;
    Item := ListView1.Selected;
    if (Item <> nil) and (Item.Data = Socket) then
      img1.Picture.Assign(SD.Png);
    SD.State := ReadingSize;
    SD.Offset := 0;
  end;
end;

procedure TForm1.ListView1SelectItem(Sender: TObject; Item: TListItem; Selected: Boolean);
begin
  if (Item <> nil) and Selected then
    img1.Picture.Assign(TSocketData(TCustomWinSocket(Item.Data).Data).Png);
end;