通过套接字发送文件:SendText() 和 SendStream() 未正确发送数据
Send file over socket: SendText() and SendStream() not sending data correctly
我尝试将 .jpg 文件从 ClientSocket
发送到 ServerSocket
,但显然在 SendText
和 [=32 附近遇到了问题=]SendStream
起作用,因为执行 SendText
后得到的结果总是 0 .但是存在其他奇怪的事情,当我在发送文件大小之前放置 ShowMessage()
时,SendText
工作(并且收到大小)但是 SendStream
失败,结果为 -1。
如何解决?
这是我最后一次尝试>
发件人:
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()
和 ReceiveBuf()
,并注意它们的 return 值,以便您知道何时需要再次调用它们来处理更多数据。
试试像这样的东西:
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.
我尝试将 .jpg 文件从 ClientSocket
发送到 ServerSocket
,但显然在 SendText
和 [=32 附近遇到了问题=]SendStream
起作用,因为执行 SendText
后得到的结果总是 0 .但是存在其他奇怪的事情,当我在发送文件大小之前放置 ShowMessage()
时,SendText
工作(并且收到大小)但是 SendStream
失败,结果为 -1。
如何解决?
这是我最后一次尝试>
发件人:
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()
和 ReceiveBuf()
,并注意它们的 return 值,以便您知道何时需要再次调用它们来处理更多数据。
试试像这样的东西:
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.