sockets - 通过套接字发送文件:SendText() 和 SendStream() 未正确发送数据
问题描述
ClientSocket
我尝试从to发送一个 .jpg 文件,但显然在 of和函数ServerSocket
周围有麻烦,因为在执行后获得的结果例如总是0。但是存在其他奇怪的事情,那就是当我在发送文件大小之前放置一个,工作(并且接收到大小)但结果为-1失败。SendText
SendStream
SendText
ShowMessage()
SendText
SendStream
怎么解决?
这是我最后一次尝试>
发件人:
uses
System.Win.ScktComp, Vcl.Imaging.jpeg;
type
TForm1 = class(TForm)
ClientSocket1: TClientSocket;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
P: TPicture;
J: TJpegImage;
MS: TMemoryStream;
Sent: Boolean;
begin
ClientSocket1.Host := '192.168.0.10';
ClientSocket1.Port := 1234;
ClientSocket1.Active := True;
try
MS := TMemoryStream.Create;
MS.Position := 0;
P := TPicture.Create;
P.Bitmap.LoadFromFile('sent.bmp');
J := TJpegImage.Create;
J.Assign(P.Bitmap);
J.CompressionQuality := 100;
J.SaveToStream(MS);
ShowMessage(IntToStr(Round(MS.Size / 1024)));
ClientSocket1.Socket.SendText(IntToStr(MS.Size) + #0);
Sent := ClientSocket1.Socket.SendStream(MS);
ShowMessage(BoolToStr(Sent));
finally
MS.Free;
P.Free;
J.Free;
end;
end;
end.
接收者:
uses
System.Win.ScktComp, Vcl.Imaging.jpeg;
type
TForm1 = class(TForm)
Button1: TButton;
ServerSocket1: TServerSocket;
procedure ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
ServerSocket1.Port := 1234;
ServerSocket1.Active := True;
end;
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
s: string;
Stream: TMemoryStream;
Receiving: Boolean;
stSize: Integer;
jpg: TJpegImage;
begin
if Socket.ReceiveLength > 0 then
begin
s := Socket.ReceiveText;
if not Receiving then
begin
if Pos(#0, s) > 0 then
begin
stSize := strToInt(Copy(s, 1, Pos(#0, s) - 1));
ShowMessage(IntToStr(Round(stSize / 1024)));
end
else
;
Stream := TMemoryStream.Create;
Receiving := true;
Delete(s, 1, Pos(#0, s));
end;
try
Stream.Write(AnsiString(s)[1], length(s));
if Stream.Size = stSize then
begin
Stream.Position := 0;
Receiving := false;
jpg := TJpegImage.Create;
jpg.LoadFromStream(Stream);
jpg.SaveToFile('received.jpg');
Stream.Free;
end;
except
Stream.Free;
end;
end;
end;
end.
解决方案
您的发送者代码没有处理SendText()
和SendStream()
发送部分数据的可能性,尤其是在非阻塞模式下。SendStream()
在退出之前可能会或可能不会释放TStream
,您无法知道其中一种方式。SendText()
在 D2009+ 中无法正确处理 Unicode 字符串。
您的接收器代码没有考虑到ReceiveText()
可能不会也可能不会在一次读取中接收所有数据。它可以并且可能需要多个OnClientRead
事件来接收所有数据。或者ReceiveText()
可能会接收部分图像数据并错误地尝试将这些字节转换为字符串字符。OnClientRead
此外,如果数据在单次读取中不完整,则不会在事件之间缓存未处理的字节。
所以,根本不要使用SendText()
/SendStream()
或ReceiveText()
!您没有正确使用它们,尤其是在非阻塞模式下。始终使用SendBuf()
andReceiveBuf()
代替,并注意它们的返回值,以便知道何时需要再次调用它们来处理更多数据。
尝试更多类似的东西:
unit Unit1;
interface
uses
..., System.Win.ScktComp;
type
TForm1 = class(TForm)
ClientSocket1: TClientSocket;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
Vcl.Imaging.jpeg, System.Math;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
ClientSocket1.Host := '192.168.0.10';
ClientSocket1.Port := 1234;
ClientSocket1.Active := True;
end;
procedure TForm1.ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
var
B: TBitmap;
J: TJpegImage;
MS: TMemoryStream;
Sent: Boolean;
function htonll(Value: UInt64): UInt64;
var
UL: Windows.ULARGE_INTEGER;
L: UInt32;
begin
UL.QuadPart := Value;
L := htonl(UL.HighPart);
LParts.HighPart := htonl(UL.LowPart);
LParts.LowPart := L;
Result := UL.QuadPart;
end;
function DoSend(Buf: Pointer; BufLen: Integer): Boolean;
var
P: PByte;
BytesSent: Integer;
begin
Result := False;
P := PByte(Buf);
while BufLen > 0 do
begin
BytesSent := Socket.SendBuf(P^, BufLen);
if BytesSent = -1 then
begin
if WSAGetLastError = WSAEWOULDBLOCK then
begin
// TODO: use Winsock.select() or TClientSocket.OnWrite to detect when
// the socket can accept more bytes again...
Continue;
end;
Exit;
end;
Inc(P, BytesSent);
Dec(BufLen, BytesSent);
end;
Result := True;
end;
function DoSendStream(Stream: TStream): Boolean;
const
MaxChunkSize: UInt64 = 1024;
var
Size, TempSize: UInt64;
Buf: array[0..1023] of Byte;
ChunkSize: Integer;
begin
Result := False;
Size := Strm.Size - Strm.Position;
TempSize := htonll(Size);
if not DoSend(@TempSize, SizeOf(TempSize)) then Exit;
while Size > 0 do
begin
ChunkSize := Integer(Min(Size, MaxChunkSize));
Stream.ReadBuffer(buf[0], ChunkSize);
if not DoSend(@buf[0], ChunkSize) then Exit;
Dec(Size, ChunkSize);
end;
Result := True;
end;
begin
// NOTE: the DoSend...() functions above are written to operate in a blocking
// manner, even if the socket is set to non-blocking mode! If you truly want
// to operate in a non-blocking manner, you need to handle the case where
// SendBuf() reports a WSAEWOULDBLOCK error by stopping the sending immediately,
// cache any unsent bytes, exit and let code flow return to the main message loop,
// and wait for the TClientSocket.OnWrite event to fire before sending the cached
// and subsequent bytes. Repeat every time WSAEWOULDBLOCK is reported...
try
MS := TMemoryStream.Create;
try
J := TJpegImage.Create;
try
B := TBitmap.Create;
try
B.LoadFromFile('sent.bmp');
J.Assign(B);
finally
B.Free;
end;
J.CompressionQuality := 100;
J.SaveToStream(MS);
finally
J.Free;
end;
MS.Position := 0;
//ShowMessage(IntToStr(Round(MS.Size / 1024)));
Sent := DoSendStream(MS);
finally
MS.Free;
end;
finally
Socket.Close;
end;
ShowMessage(BoolToStr(Sent));
end;
end.
unit Unit1;
interface
uses
... System.Win.ScktComp;
type
TForm1 = class(TForm)
Button1: TButton;
ServerSocket1: TServerSocket;
procedure ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
procedure ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
procedure ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
Vcl.Imaging.jpeg, System.Math;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
ServerSocket1.Port := 1234;
ServerSocket1.Active := True;
end;
type
SocketState = (ReadingSize, ReadingData);
TSocketHelper = class
public
Buffer: array[0..1023] of Byte;
BufSize: Integer;
ExpectedSize: UInt64;
Stream: TMemoryStream;
State: SocketState;
constructor Create;
destructor Destroy; override;
end;
constructor TSocketHelper.Create;
begin
BufSize := 0;
ExpectedSize := 0;
Stream := TMemoryStream.Create;
State := ReadingSize;
end;
destructor TSocketHelper.Destroy;
begin
Stream.Free;
inherited;
end;
procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
Socket.Data := TSocketHelper.Create;
end;
procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
TSocketHelper(Socket.Data).Free;
end;
procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
SH: TSocketHelper;
jpg: TJpegImage;
function ntohll(Value: UInt64): UInt64;
var
UL: Windows.ULARGE_INTEGER;
L: UInt32;
begin
UL.QuadPart := Value;
L := ntohl(UL.HighPart);
LParts.HighPart := ntohl(UL.LowPart);
LParts.LowPart := L;
Result := UL.QuadPart;
end;
begin
SH := TSocketHelper(Socket.Data);
repeat
case SH.State of
ReadingSize: begin
while SH.BufSize < SizeOf(UInt64) do
begin
BytesReceived := Socket.ReceiveBuf(SH.Buffer[SH.BufSize], SizeOf(UInt64) - SH.BufSize);
if BytesReceived <= 0 then Exit;
Inc(SH.BufSize, BytesReceived);
end;
SH.ExpectedSize := ntohll(PUInt64(@SH.Buffer)^);
SH.Data.Clear;
SH.State := ReadingData;
//ShowMessage(IntToStr(Round(SH.ExpectedSize / 1024)));
Continue;
end;
ReadingData: begin
while SH.ExpectedSize > 0 do
begin
BytesReceived := Socket.ReceiveBuf(SH.Buffer[0], Integer(Min(SH.ExpectedSize, SizeOf(SH.Buffer))));
if BytesReceived <= 0 then Exit;
Dec(SH.ExpectedSize, BytesReceived);
SH.Data.WriteBuffer(SH.Buffer[0], BytesReceived);
end;
try
jpg := TJpegImage.Create;
try
SH.Data.Position := 0;
jpg.LoadFromStream(SH.Data);
jpg.SaveToFile('received.jpg');
finally
jpg.Free;
end;
finally
SH.Data.Clear;
SH.BufSize := 0;
SH.State := ReadingSize;
end;
Continue;
end;
end;
until False;
end;
end.
推荐阅读
- azure-pipelines - 如何在 Azure DevOps Services 管道 yaml 中使用变量指定触发器
- opencv - 使用 PixelLib 分离从图像中检测到的对象
- apache - 主文档根的域指向新启用的子域文档根
- c# - 我可以说事件和委托之间的关系采用复合模式吗?
- python - NDI(网络设备接口)与 Python
- laravel - 如何通过中间表定义 Laravel Eloquent 关系
- python - 根据 ParentModel 中的 Total 验证 InlineFormSet 的总和
- java - 有人可以指出代码“满足给定总和条件的子序列数”的错误,这会导致堆栈溢出错误
- activemq - 即使 usePrefetchExtension 为 true,消费者也无法接收消息
- ios - 更改 googleMaps 当前位置图标/标记的颜色