I need send data from Client to Server in a determinated sequence, where the Server can receive these data also on same sequence sent by Client. On code below exists a problem that a data (that is a byte of control, 1) is received like a data of next data.
Ex:
On Client i have the following piece that send 1 (Connection._Input)
if SendInt(Sock, Ord(Connection._Input)) <= 0 then
Exit;
This byte sent above, on Server the correct is be received on Check variable, but instead is received on dwC.
See:
How can solve it?
Here is the complete code:
Client:
program _Client;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
WinSock,
SysUtils;
type
Connection = (Desktop, _Input);
const
SendBuf: array [0 .. 9] of AnsiChar = ('A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', #0);
function SendInt(S: TSocket; I: Integer): Integer;
begin
Result := send(S, I, SizeOf(I), 0);
end;
function ConnectServer: TSocket;
var
Wsa: WSAData;
Client: sockaddr_in;
S: TSocket;
Rslt: Integer;
begin
S := INVALID_SOCKET;
try
Rslt := WSAStartup(MakeWord(2, 2), Wsa);
if Rslt = NO_ERROR then
begin
S := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if S <> INVALID_SOCKET then
begin
Client.sin_family := AF_INET;
Client.sin_addr.s_addr := inet_addr('192.168.15.6');
Client.sin_port := htons(5099);
if connect(S, Client, SizeOf(Client)) <> SOCKET_ERROR then
Writeln('Connected successfully!');
end;
end;
except
Writeln(SysErrorMessage(WSAGetLastError));
end;
Result := S;
end;
function DesktopThread(P: Pointer): DWORD; stdcall;
var
Sock: TSocket;
dwC, dwD, dwE, dwF, dwG: DWORD;
A, B: Integer;
begin
Result := 0;
Sock := ConnectServer;
if send(Sock, SendBuf, SizeOf(SendBuf), 0) <= 0 then
Exit;
if SendInt(Sock, Ord(Connection.Desktop)) <= 0 then
Exit;
dwC := 111;
dwD := 222;
dwE := 333;
dwF := 444;
dwG := 555;
repeat
if recv(Sock, A, SizeOf(A), 0) <= 0 then
Exit;
if recv(Sock, B, SizeOf(B), 0) <= 0 then
Exit;
if SendInt(Sock, Ord(Connection._Input)) <= 0 then
Exit;
if SendInt(Sock, dwC) <= 0 then
Exit;
if SendInt(Sock, dwD) <= 0 then
Exit;
if SendInt(Sock, dwE) <= 0 then
Exit;
if SendInt(Sock, dwF) <= 0 then
Exit;
if SendInt(Sock, dwG) <= 0 then
Exit;
// Writeln(Format('%s', [SysErrorMessage(WSAGetLastError)]));
Writeln(Format('dwC: %d, dwD: %d, dwE: %d, dwF: %d, dwG: %d',
[dwC, dwD, dwE, dwF, dwG]));
until True;
end;
var
ThrId: Cardinal;
begin
try
CreateThread(nil, 0, #DesktopThread, nil, 0, ThrId);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Server:
program _Server;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Windows,
WinSock,
SysUtils;
type
Connection = (Desktop, Input, _End);
const
Buffer: array [0 .. 9] of AnsiChar = ('A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', #0);
function SendInt(S: TSocket; I: Integer): Integer;
begin
Result := send(S, I, SizeOf(I), 0);
end;
function ClientThread(P: Pointer): DWORD; stdcall;
var
Buf: array [0 .. SizeOf(Buffer) - 1] of AnsiChar;
Sock: TSocket;
Check: BOOL;
A, B: Integer;
_connection: Connection;
dwC, dwD, dwE, dwF, dwG: DWORD;
begin
Result := 0;
Sock := TSocket(P);
if recv(Sock, Buf, SizeOf(Buffer), 0) <= 0 then
begin
closesocket(Sock);
Result := 0;
Exit;
end;
if not CompareMem(#Buf, #Buffer, SizeOf(Buffer)) then
begin
closesocket(Sock);
Result := 0;
Exit;
end;
if recv(Sock, _connection, SizeOf(_connection), 0) <= 0 then
begin
closesocket(Sock);
Result := 0;
Exit;
end;
if _connection = Connection.Desktop then
begin
A := 666;
B := 777;
repeat
if SendInt(Sock, A) <= 0 then
Exit;
if SendInt(Sock, B) <= 0 then
Exit;
if recv(Sock, Check, SizeOf(Check), 0) <= 0 then
Exit;
Writeln(BoolToStr(Check));
{ if not Check then
continue; }
if recv(Sock, dwC, SizeOf(dwC), 0) <= 0 then
Exit;
if recv(Sock, dwD, SizeOf(dwD), 0) <= 0 then
Exit;
if recv(Sock, dwE, SizeOf(dwE), 0) <= 0 then
Exit;
if recv(Sock, dwF, SizeOf(dwF), 0) <= 0 then
Exit;
if recv(Sock, dwG, SizeOf(dwG), 0) <= 0 then
Exit;
// Writeln(Format('%s', [SysErrorMessage(WSAGetLastError)]));
Writeln(Format('dwC: %d, dwD: %d, dwE: %d, dwF: %d, dwG: %d',
[dwC, dwD, dwE, dwF, dwG]));
until True;
end;
end;
function StartServer(Port: Integer): Boolean;
var
_wsdata: WSAData;
serverSocket, S: TSocket;
_addrIn, _addr: sockaddr_in;
addrSize: Integer;
tid: Cardinal;
begin
Result := False;
if WSAStartup(MakeWord(2, 2), _wsdata) <> 0 then
Exit;
serverSocket := socket(AF_INET, SOCK_STREAM, 0);
if serverSocket = INVALID_SOCKET then
Exit;
_addrIn.sin_family := AF_INET;
_addrIn.sin_addr.S_addr := INADDR_ANY;
_addrIn.sin_port := htons(Port);
if bind(serverSocket, _addrIn, SizeOf(_addrIn)) = SOCKET_ERROR then
Exit;
if listen(serverSocket, SOMAXCONN) = SOCKET_ERROR then
Exit;
addrSize := SizeOf(_addrIn);
getsockname(serverSocket, _addrIn, addrSize);
Writeln(Format('Listening on port %d.' + #13, [ntohs(_addrIn.sin_port)]));
while True do
begin
S := accept(serverSocket, #_addr, #addrSize);
CreateThread(nil, 0, #ClientThread, Pointer(S), 0, tid);
end;
Result := True;
end;
begin
try
if not StartServer(5099) then
Writeln(SysErrorMessage(WSAGetLastError));
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
You have a misalignment of data size in client vs server.
type
Connection = (Desktop, _Input);
Default size of an enumeration in Delphi is byte. This is in itself OK, but you handle these differently in the client and the server.
You send from the client using your SendInt() function which converts to integer.
On the server side you receive it as a SizeOf(_connection) which is only a byte. Because of the byte order, the 1 remains in the buffer and is later read into dwC.
You can correct the error either by setting Minimum enum size in project options to doubleword or by sending as byte.
Edit after comment
Indeed you also have another error, or maybe misunderstanding.
From the client you send
SendInt(Sock, Ord(Connection._Input))
which is received by the server as
var
Check: BOOL;
....
recv(Sock, Check, SizeOf(Check), 0) ,
Then you write it out as
Writeln(BoolToStr(Check));
and the console shows '-1'. But that is not an error, it is documented:
System.SysUtils.BoolToStr
Value of B Value of UseBoolStrs Value of returned string
true false '-1'
Perhaps you want to show it as the enum value instead.
Related
I have the following code, where I can draw several rectangles and make a hole to each.
How can I send the RectList object over a socket (TServerSocket) and recover (receive in a TClientSocket) this object directly to a variable of same type (var RectList: TList<TRect>)?
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure FormPaint(Sender: TObject);
private
{ Private declarations }
Drawing: Boolean;
RectList: TList<TRect>;
Rectangle: TRect;
FormRegion, HoleRegion: HRGN;
function ClientToWindow(const P: TPoint): TPoint;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function TForm1.ClientToWindow(const P: TPoint): TPoint;
begin
Result := ClientToScreen(P);
Dec(Result.X, Left);
Dec(Result.Y, Top);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
RectList := TList<TRect>.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
RectList.Free;
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
Rectangle.Left := X;
Rectangle.Top := Y;
Drawing := True;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if Drawing then
begin
Rectangle.Right := X;
Rectangle.Bottom := Y;
Invalidate;
end;
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
I: Integer;
begin
Drawing := false;
Rectangle.Right := X;
Rectangle.Bottom := Y;
Invalidate;
if RectList.Count < StrToInt(ComboBox1.Text) then
begin
Rectangle.NormalizeRect;
if not Rectangle.IsEmpty then
RectList.Add(Rectangle)
else
SetWindowRgn(Handle, 0, True);
end
else
begin
FormRegion := CreateRectRgn(0, 0, Width, Height);
for I := 0 to Pred(RectList.Count) do
begin
HoleRegion := CreateRectRgn(ClientToWindow(RectList.Items[I].TopLeft).X, ClientToWindow(RectList.Items[I].TopLeft).Y, ClientToWindow(RectList.Items[I].BottomRight).X, ClientToWindow(RectList.Items[I].BottomRight).Y);
CombineRgn(FormRegion, FormRegion, HoleRegion, RGN_DIFF);
end;
SetWindowRgn(Handle, FormRegion, True);
RectList.Clear;
end;
end;
procedure TForm1.FormPaint(Sender: TObject);
var
R: TRect;
begin
Canvas.Brush.Style := bsClear;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Color := clRed;
Canvas.Rectangle(Rectangle);
for R in RectList do
Canvas.Rectangle(R);
end;
end.
I made some code to show you how to do it.
In your code, I added a TClientSocketon the form and assigned a few events. Also added a TButton to send the RectList to the other side (server side) thru the TClientSocket.
I designed a new simple server application having a TServerSocket set to listen for client connection and accepting commands from the client. I implemented two commands: rectangle and clear. Obviously clear command is implemented to clear the display on the rectangle list. The rectangle command is used to sent a rectangle (Left, top, right and bottom as coma delimited integers).
Since client and server must understand each other, I designed a very simple communication protocol. Data is exchanged between client and server using ASCII lines. A line is any character collection terminated by a CRLF pair. TCP port 2500 (Almost any other would do) is used.
For example, the command
rectangle 10,20,30,40
will sent a rectangle from client to server (The line above is terminated by CRLF).
If the server receive a valid command, it act on it and then send
OK
The line above is terminated by CRLF. In case of an error, an error message is sent back to the client.
When a client establish a connection, the first thing the server does is to send a welcome banner. That is a line terminated by CRLF.
The client wait to receive the banner before sending any command. Then it send the clear command, wait for OK, then send a rectangle command with first item in RectList and wait for OK, then loop sending all rectangle commands and waiting for OK acknowledge until all RectList has been sent. The the client close the connection.
I'm not completely correct when I say wait for. Actually the socket is event driven. That means everything is done thry events. For example, when a line comes in - sent by the other side - the socket triggers an OnRead event. In the corresponding event handler, you receive the line that is already received.
I used this line oriented protocol because it is really simple, easy to debug and cross platform. Actually, if looks much like the SMTP protocol which is used to send an email! Sending binary data is surely faster but has a lot of difficulties. Binary data format is compiler and platform specific. This result in difficulties. Binary data is diffcult to read for a human and so it is difficult to debug.
Below you'll find your enhanced source code and DFM (This is the client), then the server source code and DFM.
Client source code:
unit SktSocketClientDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
System.Generics.Collections,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
System.Win.ScktComp;
type
TSktSocketClientMainForm = class(TForm)
ComboBox1 : TComboBox;
SocketSendButton : TButton;
ClientSocket1 : TClientSocket;
Memo1 : TMemo;
procedure ClientSocket1Connect(
Sender : TObject;
Socket : TCustomWinSocket);
procedure ClientSocket1Connecting(
Sender : TObject;
Socket : TCustomWinSocket);
procedure ClientSocket1Read(
Sender : TObject;
Socket : TCustomWinSocket);
procedure FormCreate(Sender : TObject);
procedure FormDestroy(Sender : TObject);
procedure FormMouseDown(
Sender : TObject;
Button : TMouseButton;
Shift : TShiftState;
X, Y : Integer);
procedure FormMouseMove(
Sender : TObject;
Shift : TShiftState;
X, Y : Integer);
procedure FormMouseUp(
Sender : TObject;
Button : TMouseButton;
Shift : TShiftState;
X, Y : Integer);
procedure FormPaint(Sender : TObject);
procedure SocketSendButtonClick(Sender : TObject);
private
Drawing : Boolean;
RectList : TList<TRect>;
Rectangle : TRect;
FormRegion, HoleRegion : HRGN;
FBanner : string;
FSendIndex : Integer;
function ClientToWindow(const P : TPoint) : TPoint;
end;
var
SktSocketClientMainForm : TSktSocketClientMainForm;
implementation
{$R *.dfm}
function TSktSocketClientMainForm.ClientToWindow(const P : TPoint) : TPoint;
begin
Result := ClientToScreen(P);
Dec(Result.X, Left);
Dec(Result.Y, Top);
end;
procedure TSktSocketClientMainForm.FormCreate(Sender : TObject);
begin
RectList := TList<TRect>.Create;
end;
procedure TSktSocketClientMainForm.FormDestroy(Sender : TObject);
begin
RectList.Free;
end;
procedure TSktSocketClientMainForm.FormMouseDown(
Sender : TObject;
Button : TMouseButton;
Shift : TShiftState;
X, Y : Integer);
begin
Rectangle.Left := X;
Rectangle.Top := Y;
Drawing := True;
end;
procedure TSktSocketClientMainForm.FormMouseMove(
Sender : TObject;
Shift : TShiftState;
X, Y : Integer);
begin
if Drawing then begin
Rectangle.Right := X;
Rectangle.Bottom := Y;
Invalidate;
end;
end;
procedure TSktSocketClientMainForm.FormMouseUp(
Sender : TObject;
Button : TMouseButton;
Shift : TShiftState;
X, Y : Integer);
var
I : Integer;
begin
Drawing := false;
Rectangle.Right := X;
Rectangle.Bottom := Y;
Invalidate;
if RectList.Count < StrToInt(ComboBox1.Text) then begin
Rectangle.NormalizeRect;
if not Rectangle.IsEmpty then
RectList.Add(Rectangle)
else
SetWindowRgn(Handle, 0, True);
end
else begin
FormRegion := CreateRectRgn(0, 0, Width, Height);
for I := 0 to Pred(RectList.Count) do
begin
HoleRegion :=
CreateRectRgn(ClientToWindow(RectList.Items[I].TopLeft).X,
ClientToWindow(RectList.Items[I].TopLeft).Y,
ClientToWindow(RectList.Items[I].BottomRight).X,
ClientToWindow(RectList.Items[I].BottomRight).Y);
CombineRgn(FormRegion, FormRegion, HoleRegion, RGN_DIFF);
end;
SetWindowRgn(Handle, FormRegion, True);
RectList.Clear;
end;
end;
procedure TSktSocketClientMainForm.FormPaint(Sender : TObject);
var
R : TRect;
begin
Canvas.Brush.Style := bsClear;
Canvas.Pen.Style := psSolid;
Canvas.Pen.Color := clRed;
Canvas.Rectangle(Rectangle);
for R in RectList do
Canvas.Rectangle(R);
end;
procedure TSktSocketClientMainForm.SocketSendButtonClick(Sender : TObject);
begin
FBanner := '';
FSendIndex := 0;
ClientSocket1.Port := 2500; // Must be the same as server side
ClientSocket1.Address := '127.0.0.1';
ClientSocket1.Active := True;
end;
procedure TSktSocketClientMainForm.ClientSocket1Connect(
Sender : TObject;
Socket :
TCustomWinSocket);
begin
Memo1.Lines.Add('Connected');
end;
procedure TSktSocketClientMainForm.ClientSocket1Connecting(
Sender : TObject;
Socket : TCustomWinSocket);
begin
Memo1.Lines.Add('Connecting...');
end;
procedure TSktSocketClientMainForm.ClientSocket1Read(
Sender : TObject;
Socket : TCustomWinSocket);
var
Line : string;
CmdLine : string;
R : TRect;
begin
Line := Trim(string(Socket.ReceiveText));
Memo1.Lines.Add('Rcvd: "' + Line + '"');
if FBanner = '' then begin
FBanner := Line;
Socket.SendText('Clear' + #13#10);
Exit;
end;
if Line <> 'OK' then begin
Memo1.Lines.Add('Expected "OK", received "' + Line + '"');
Socket.Close;
Exit;
end;
if FSendIndex >= RectList.Count then begin
// We have sent everything in RectList
Memo1.Lines.Add('Send completed OK');
Socket.Close;
Exit;
end;
// Send next item in RectList
R := RectList[FSendIndex];
CmdLine := Format('Rectangle %d,%d,%d,%d' + #13#10,
[R.Left, R.Top, R.Right, R.Bottom]);
Inc(FSendIndex);
Socket.SendText(AnsiString(CmdLine));
end;
end.
Client DFM:
object SktSocketClientMainForm: TSktSocketClientMainForm
Left = 0
Top = 0
Caption = 'SktSocketClientMainForm'
ClientHeight = 299
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
OnDestroy = FormDestroy
OnMouseDown = FormMouseDown
OnMouseMove = FormMouseMove
OnMouseUp = FormMouseUp
OnPaint = FormPaint
DesignSize = (
635
299)
PixelsPerInch = 96
TextHeight = 13
object ComboBox1: TComboBox
Left = 24
Top = 12
Width = 145
Height = 21
Style = csDropDownList
ItemIndex = 4
TabOrder = 0
Text = '5'
Items.Strings = (
'1'
'2'
'3'
'4'
'5'
'6'
'7'
'8'
'9')
end
object SocketSendButton: TButton
Left = 188
Top = 8
Width = 75
Height = 25
Caption = 'Send'
TabOrder = 1
OnClick = SocketSendButtonClick
end
object Memo1: TMemo
Left = 8
Top = 192
Width = 621
Height = 101
Anchors = [akLeft, akTop, akRight, akBottom]
Lines.Strings = (
'Memo1')
TabOrder = 2
end
object ClientSocket1: TClientSocket
Active = False
ClientType = ctNonBlocking
Port = 0
OnConnecting = ClientSocket1Connecting
OnConnect = ClientSocket1Connect
OnRead = ClientSocket1Read
Left = 44
Top = 148
end
end
Server source code:
unit SktSocketServerDemoMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
System.Generics.Collections,
Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Win.ScktComp,
Vcl.ExtCtrls;
type
TCmdProc = procedure (Socket : TCustomWinSocket;
const Params : String) of object;
TCmdItem = record
Cmd : String;
Proc : TCmdProc;
constructor Create(const ACmd : String; AProc : TCmdProc);
end;
TServerMainForm = class(TForm)
ServerSocket1 : TServerSocket;
Memo1 : TMemo;
ServerStartButton : TButton;
PaintBox1 : TPaintBox;
ServerStopButton : TButton;
procedure PaintBox1Paint(Sender : TObject);
procedure ServerSocket1ClientConnect(
Sender : TObject;
Socket : TCustomWinSocket);
procedure ServerSocket1ClientDisconnect(
Sender : TObject;
Socket :
TCustomWinSocket);
procedure ServerSocket1ClientRead(
Sender : TObject;
Socket : TCustomWinSocket);
procedure ServerSocket1Listen(
Sender : TObject;
Socket : TCustomWinSocket);
procedure ServerStartButtonClick(Sender : TObject);
procedure ServerStopButtonClick(Sender : TObject);
private
RectList : TList<TRect>;
CmdList : TList<TCmdItem>;
procedure ProcessCmd(
Socket : TCustomWinSocket;
const CmdLine : string);
procedure CmdNoop(
Socket : TCustomWinSocket;
const Params : string);
procedure CmdClear(
Socket : TCustomWinSocket;
const Params : string);
procedure CmdRectangle(
Socket : TCustomWinSocket;
const Params : string);
public
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
end;
var
ServerMainForm: TServerMainForm;
implementation
{$R *.dfm}
function SkipOverWhiteSpaces(const CmdLine : String; Index : Integer) : Integer;
var
I : Integer;
begin
I := Index;
while (I <= Length(CmdLine)) and
CharInSet(CmdLine[I], [' ', #13, #10, #9]) do
Inc(I);
Result := I;
end;
function SkipToNextWhiteSpace(const CmdLine : String; Index : Integer) : Integer;
var
I : Integer;
begin
I := Index;
while (I <= Length(CmdLine)) and
(not CharInSet(CmdLine[I], [' ', #13, #10, #9])) do
Inc(I);
Result := I;
end;
function SkipToNextDelimiter(
const CmdLine : String;
Index : Integer;
Delimiters : array of const) : Integer;
var
I : Integer;
nArg : Integer;
V : TVarRec;
begin
I := Index;
while I <= Length(CmdLine) do begin
nArg := 0;
while nArg <= High(Delimiters) do begin
V := Delimiters[nArg];
case (V.VType and varTypeMask) of
vtWideChar:
begin
if CmdLine[I] = V.VWideChar then begin
Result := I;
Exit;
end;
end;
end;
Inc(nArg);
end;
Inc(I);
end;
Result := I;
end;
function GetInteger(
const CmdLine : String;
Index : Integer;
out Value : Integer) : Integer;
var
I : Integer;
begin
Value := 0;
I := SkipOverWhiteSpaces(CmdLine, Index);
while (I <= Length(CmdLine)) and
CharInSet(CmdLine[I], ['0'..'9']) do begin
Value := Value * 10 + Ord(CmdLine[I]) - Ord('0');
Inc(I);
end;
Result := I;
end;
procedure TServerMainForm.CmdClear(Socket: TCustomWinSocket; const Params: String);
begin
RectList.Clear;
PaintBox1.Invalidate;
Socket.SendText('OK' + #13#10);
end;
procedure TServerMainForm.CmdNoop(Socket: TCustomWinSocket; const Params: String);
begin
Socket.SendText('OK' + #13#10);
end;
procedure TServerMainForm.CmdRectangle(Socket: TCustomWinSocket; const Params: String);
var
Param : array [0..3] of Integer;
I, J, K : Integer;
begin
// Clear all parameters
for K := Low(Param) to High(Param) do
Param[K] := 0;
// Parse all parameters
J := 1;
K := Low(Param);
while K <= High(Param) do begin
I := GetInteger(Params, J, Param[K]);
J := SkipOverWhiteSpaces(Params, I);
if J > Length(Params) then
break;
if K = High(Param) then // Check if we got all
break;
if Params[J] <> ',' then // Check for coma delimiter
break;
Inc(J); // Skip over coma
Inc(K);
end;
if K <> High(Param) then begin
Socket.SendText('Rectangle requires 4 parameters.'#13#10);
Exit;
end;
RectList.Add(TRect.Create(Param[0], Param[1], Param[2], Param[3]));
PaintBox1.Invalidate;
Socket.SendText('OK'#13#10);
end;
constructor TServerMainForm.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
RectList := TList<TRect>.Create;
RectList.Add(TRect.Create(10, 10, 50, 50));
RectList.Add(TRect.Create(20, 30, 80, 100));
CmdList := TList<TCmdItem>.Create;
CmdList.Add(TCmdItem.Create('', CmdNoop));
CmdList.Add(TCmdItem.Create('Clear', CmdClear));
CmdList.Add(TCmdItem.Create('Rectangle', CmdRectangle));
end;
destructor TServerMainForm.Destroy;
begin
FreeAndNil(CmdList);
FreeAndNil(RectList);
inherited Destroy;
end;
procedure TServerMainForm.PaintBox1Paint(Sender: TObject);
var
R: TRect;
ACanvas : TCanvas;
begin
ACanvas := (Sender as TPaintBox).Canvas;
ACanvas.Brush.Style := bsClear;
ACanvas.Pen.Style := psSolid;
ACanvas.Pen.Color := clRed;
for R in RectList do
ACanvas.Rectangle(R);
end;
procedure TServerMainForm.ServerSocket1ClientConnect(
Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo1.Lines.Add('Client connected');
Socket.SendText('Welcome to myServer' + #13#10);
end;
procedure TServerMainForm.ServerSocket1ClientRead(Sender: TObject; Socket:
TCustomWinSocket);
var
CmdLine : String;
begin
CmdLine := String(Socket.ReceiveText);
Memo1.Lines.Add('Rcvd: "' + CmdLine + '"');
ProcessCmd(Socket, CmdLine);
end;
procedure TServerMainForm.ProcessCmd(
Socket : TCustomWinSocket;
const CmdLine : String);
var
Cmd : String;
Params : String;
I, J : Integer;
begin
I := SkipOverWhiteSpaces(CmdLine, 1);
J := SkipToNextWhiteSpace(CmdLine, I);
// Split command and parameters
Cmd := UpperCase(Copy(CmdLine, I, J - I));
Params := Copy(CmdLine, J, MAXINT);
Memo1.Lines.Add(Format('Cmd="%s" Params="%s"', [Cmd, Params]));
for I := 0 to CmdList.Count - 1 do begin
if CmdList[I].Cmd = Cmd then begin
CmdList[I].Proc(Socket, Params);
Exit;
end;
end;
Socket.SendText('Unknown command' + #13#10);
end;
procedure TServerMainForm.ServerSocket1ClientDisconnect(Sender: TObject; Socket:
TCustomWinSocket);
begin
Memo1.Lines.Add('Client disconnected');
end;
procedure TServerMainForm.ServerSocket1Listen(Sender: TObject; Socket: TCustomWinSocket);
begin
Memo1.Lines.Add('Waiting for client connection');
end;
procedure TServerMainForm.ServerStartButtonClick(Sender: TObject);
begin
ServerSocket1.Port := 2500; // Almost any (free) port is OK
ServerSocket1.Open; // Start listening for clients
end;
procedure TServerMainForm.ServerStopButtonClick(Sender: TObject);
begin
ServerSocket1.Close;
Memo1.Lines.Add('Server stopped');
end;
{ TCmdItem }
constructor TCmdItem.Create(const ACmd: String; AProc: TCmdProc);
begin
Cmd := UpperCase(ACmd);
Proc := AProc;
end;
end.
Server DFM:
object ServerMainForm: TServerMainForm
Left = 0
Top = 0
Caption = 'ServerMainForm'
ClientHeight = 498
ClientWidth = 635
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
DesignSize = (
635
498)
PixelsPerInch = 96
TextHeight = 13
object PaintBox1: TPaintBox
Left = 8
Top = 48
Width = 617
Height = 273
Anchors = [akLeft, akTop, akRight, akBottom]
OnPaint = PaintBox1Paint
end
object Memo1: TMemo
Left = 8
Top = 329
Width = 617
Height = 161
Anchors = [akLeft, akTop, akRight, akBottom]
Lines.Strings = (
'Memo1')
TabOrder = 0
end
object ServerStartButton: TButton
Left = 12
Top = 8
Width = 75
Height = 25
Caption = 'Server Start'
TabOrder = 1
OnClick = ServerStartButtonClick
end
object ServerStopButton: TButton
Left = 93
Top = 8
Width = 75
Height = 25
Caption = 'Server Stop'
TabOrder = 2
OnClick = ServerStopButtonClick
end
object ServerSocket1: TServerSocket
Active = False
Port = 0
ServerType = stNonBlocking
OnListen = ServerSocket1Listen
OnClientConnect = ServerSocket1ClientConnect
OnClientDisconnect = ServerSocket1ClientDisconnect
OnClientRead = ServerSocket1ClientRead
Left = 64
Top = 196
end
end
I keep getting an error that says:
line 25: type error near num_values ; current type unsigned; expected
type unsigned.
It is already the type that is supposed to be, and I think it is same in bit length and declared alright, what am I doing wrong here?
The code is about implementation of fifo queue structure.
<Queue.vhd>
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity Queue is
port (
-- clock
clk: in std_logic;
-- input
push: in std_logic;
push_data: in std_logic_vector(31 downto 0);
pop: in std_logic;
-- output
pop_data: out std_logic_vector(31 downto 0);
num_values: out unsigned(31 downto 0)
);
end entity;
architecture Behavioral of Queue is
type items is array(0 to 31) of std_logic_vector(31 downto 0);
signal manager : items := (others => (others => '0'));
signal push_idx, pop_idx : integer := 0;
begin
process(clk) is
variable howmany : integer := 0;
begin
if rising_edge(clk) then
if (push = '1') then
manager(push_idx) <= push_data;
push_idx <= push_idx + 1;
howmany := howmany + 1;
end if;
if (pop = '1') then
if (howmany /= 0) then
pop_data <= manager(pop_idx);
pop_idx <= pop_idx + 1;
howmany := howmany - 1;
end if;
else
pop_data <= std_logic_vector(to_unsigned(0,pop_data'length));
end if;
if (push_idx = 31) then
push_idx <= 0;
end if;
if (pop_idx = 31) then
pop_idx <= 0;
end if;
num_values <= to_unsigned(howmany,32);
end if;
end process;
end architecture;
<QueueTb.vhd>
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
entity QueueTb is
end entity;
architecture sim of QueueTb is
constant ClockFrequency : integer := 100e6;
constant ClockPeriod : time := 1000ms / ClockFrequency;
signal clk : std_logic := '0';
signal push, pop : std_logic := '0';
signal num_values : unsigned(31 downto 0);
signal push_data, pop_data : std_logic_vector(31 downto 0) := (others =>'0');
begin
UUT : entity work.Queue(Behavioral)
port map(
clk => clk,
push => push,
pop => pop,
num_values => num_values, <=== this is where error occurs!
push_data => push_data,
pop_data => pop_data);
clk <= not clk after ClockPeriod / 2;
process is
begin
wait for 10 ns;
push <= '1';
push_data <= conv_std_logic_vector(123,32);
wait for 10 ns;
push_data <= conv_std_logic_vector(456,32);
wait for 10 ns;
push <= '0';
push_data <= conv_std_logic_vector(0,32);
pop <= '1';
wait for 10 ns;
pop <= '0';
push <= '1';
push_data <= conv_std_logic_vector(789,32);
wait for 10 ns;
push <= '0';
pop <= '1';
wait for 80 ns;
end process;
end architecture;
What am I doing wrong here??
In the Queue entity you are using ieee.numeric_std and in the QueueTB you are using ieee.std_logic_arith . Both define different unsigned types.
Delete std_logic_arith from the testbench as it is not a standard VHDL library and use numeric_std instead.
Actually i have this code below that is working fine to send webcam images over socket (ClientSocket > ServerSocket), before send, i'm saving the image to file . Then i want know if exists some solution to insert the capture inside of MemoryStream without need save to file before.
Any suggestion will welcome.
Thanks in advance.
Camera.pas
unit Camera;
interface
uses
Windows, Messages, SysUtils, Graphics, Controls, Dialogs, ExtCtrls,
Jpeg;
type
TCamera = class(TObject)
private
Parent: TPanel;
VideoHwnd: HWND;
procedure Resize(Sender: TObject);
public
constructor Create(Owner: TPanel);
destructor Destroy; override;
function TakePicture(FileName: string): boolean;
procedure SetSize;
procedure SetSource;
end;
implementation
const
WM_CAP_START = WM_USER;
WM_CAP_STOP = WM_CAP_START + 68;
WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10;
WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11;
WM_CAP_SAVEDIB = WM_CAP_START + 25;
WM_CAP_DLG_VIDEOFORMAT = WM_CAP_START + 41;
WM_CAP_DLG_VIDEOSOURCE = WM_CAP_START + 42;
WM_CAP_SET_PREVIEW = WM_CAP_START + 50;
WM_CAP_SET_PREVIEWRATE = WM_CAP_START + 52;
WM_CAP_SET_SCALE = WM_CAP_START + 53;
WM_CAP_GRAB_FRAME = WM_CAP_START + 60;
WM_CAP_SEQUENCE = WM_CAP_START + 62;
WM_CAP_FILE_SET_CAPTURE_FILEA = WM_CAP_START + 20;
function capCreateCaptureWindowA(lpszWindowName: PCHAR; dwStyle: longint; x: integer; y: integer; nWidth: integer; nHeight: integer; ParentWin: HWND; nId: integer): HWND; stdcall; external 'AVICAP32.DLL';
constructor TCamera.Create(Owner: TPanel);
begin
try
VideoHwnd := capCreateCaptureWindowA('Camera', WS_CHILD or WS_VISIBLE, 0, 0, Owner.Width, Owner.Height, Owner.Handle, 0);
if (SendMessage(VideoHwnd, WM_CAP_DRIVER_CONNECT, 0, 0) <> 0) then
begin
SendMessage(VideoHwnd, WM_CAP_SET_PREVIEW, -1, 0);
SendMessage(VideoHwnd, WM_CAP_SET_PREVIEWRATE, 100, 0);
SendMessage(VideoHwnd, WM_CAP_SET_SCALE, -1, 0);
Parent := Owner;
Owner.OnResize := Resize;
end;
except
exit;
end;
end;
destructor TCamera.Destroy;
begin
if (VideoHwnd <> 0) then
begin
SendMessage(VideoHwnd, WM_CAP_DRIVER_DISCONNECT, 0, 0);
SetParent(VideoHwnd, 0);
SendMessage(VideoHwnd, WM_CLOSE, 0, 0);
end;
inherited;
end;
procedure TCamera.Resize(Sender: TObject);
begin
inherited;
if (VideoHwnd <> 0) then
begin
SetWindowPos(VideoHwnd, HWND_BOTTOM, 0, 0, Parent.Width, Parent.Height, SWP_NOMOVE or SWP_NOACTIVATE);
end;
end;
procedure TCamera.SetSize;
begin
SendMessage(VideoHwnd, WM_CAP_DLG_VIDEOFORMAT, 0, 0);
end;
procedure TCamera.SetSource;
begin
SendMessage(VideoHwnd, WM_CAP_DLG_VIDEOSOURCE, 0, 0);
end;
function StringToPAnsiChar(stringVar: string): PAnsiChar;
var
AnsString: AnsiString;
begin
Result := '';
try
if stringVar <> '' then
begin
AnsString := AnsiString(stringVar);
Result := PAnsiChar(PAnsiString(AnsString));
end;
except
end;
end;
function TCamera.TakePicture(FileName: string): boolean;
var
p: TPicture;
j: TJpegImage;
Q, k: integer;
begin
if (SendMessage(VideoHwnd, WM_CAP_GRAB_FRAME, 0, 0) <> 0) and (SendMessage(VideoHwnd, WM_CAP_SAVEDIB, wparam(0), lparam(StringToPAnsiChar(FileName))) <> 0) then
begin
SendMessage(VideoHwnd, WM_CAP_SET_PREVIEW, -1, 0);
p := TPicture.Create;
p.Bitmap.LoadFromFile(FileName);
j := TJpegImage.Create;
j.Assign(p.Bitmap);
val('100', Q, k);
j.CompressionQuality := Q;
j.SaveToFile(FileName);
p.Free;
j.Free;
result := true;
end
else
result := false;
end;
end.
Form1.pas
uses
Camera;
// ...
procedure TForm1.ClientSocketCamConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
Camera := TCamera.Create(Form1.Panel1);
end;
procedure TForm1.ClientSocketCamDisconnected(Sender: TObject; Socket: TCustomWinSocket);
begin
Camera.Destroy;
end;
procedure TForm1.ClientSocketCamError(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Integer);
begin
ErrorCode := 0;
end;
procedure TForm1.ClientSocketCamRead(Sender: TObject; Socket: TCustomWinSocket);
var
s, FileName: string;
Stream: TMemoryStream;
begin
s := Socket.ReceiveText;
FileName := ExtractFilePath(Application.ExeName) + 'webcam.jpg';
if s = 'camoff' then
begin
Camera.Destroy;
Socket.SendText('endcam');
end;
if s = 'cam' then
begin
try
Camera.TakePicture(FileName);
Sleep(200);
Stream := TMemoryStream.Create;
if FileExists(FileName)
then
begin
Stream.LoadFromFile(FileName);
Stream.Position := 0;
Socket.SendText(inttostr(Stream.Size) + #0);
Socket.SendStream(Stream);
end;
finally
Stream.Free;
Exit;
end;
end;
end;
I'm using Delphi Rest Client component to upload file to Google Drive.
I use a resumable download. After the first request, the response code should come 308 upload is not complete, and afterwards it is necessary to send parts of the files.
System.HTTPPClient is trying to do something, because by default 308 = permanent redirect and eventually gives an Access Violation. How can this situation be solved?
This is upload file
UploadStream := TMemoryStream.Create;
fFile := TFileStream.Create(fileName,fmOpenRead);
GetMem(buffer, onePartion);
fFileSize := fFile.Size;
alreadyWrite := 0;
try
while(fFile.Position < fFile.Size) do
begin
FillChar(buffer[0], onePartion, #0);
UploadStream.Clear;
liReaded := fFile.Read(buffer[0], onePartion);
UploadStream.Write(buffer[0], liReaded);
UploadStream.Position := 0;
addAuthParam;
fRESTRequest.Params.Add;
fRESTRequest.Params[1].Kind:=TRESTRequestParameterKind.pkHTTPHEADER;
fRESTRequest.Params[1].name:='Content-Range';
fRESTRequest.Params[1].Options:=[poDoNotEncode];
fRESTRequest.Params[1].Value:='bytes ' + IntToStr(alreadyWrite) + '-' + IntToStr(alreadyWrite+liReaded-1) + '/' + IntToStr(fFileSize);
fRESTRequest.ClearBody;
fRESTRequest.AddBody(UploadStream,ctAPPLICATION_ZIP);
try
fRESTRequest.Execute;
except
end;
alreadyWrite := alreadyWrite + liReaded;
end;
This System.Net.HTTPClient
if LRequest.FCancelled then
Exit;
LExecResult := DoExecuteRequest(LRequest, LResponse, AContentStream);
if LRequest.FCancelled then
Exit;
case LExecResult of
TExecutionResult.Success:
begin
if not SameText(LRequest.FMethodString, sHTTPMethodHead) then
LResponse.DoReadData(LResponse.FStream);
if LRequest.FCancelled then
Exit;
Status := LResponse.GetStatusCode;
case Status of
200:
begin
Break;
end;
401:
begin
State.Status := InternalState.ServerAuthRequired;
end;
407:
begin
State.Status := InternalState.ProxyAuthRequired;
end;
else
begin
case Status of
301..304, 307:
if FHandleRedirects and (LRequest.FMethodString <> sHTTPMethodHead) then
begin
Inc(State.Redirections);
if State.Redirections > FMaxRedirects then
raise ENetHTTPRequestException.CreateResFmt(#SNetHttpMaxRedirections, [FMaxRedirects]);
end
else
Break;
else
end;
State.Status := InternalState.Other;
if DoProcessStatus(LRequest, LResponse) then
Break;
end;
end;
end;
Status = 308, then do function DoProcessStatus(LRequest, LResponse)
function TWinHTTPClient.DoProcessStatus(const ARequest: IHTTPRequest; const AResponse: IHTTPResponse): Boolean;
var
LRequest: TWinHTTPRequest;
LResponse: TWinHTTPResponse;
LURI: TURI;
begin
LRequest := ARequest as TWinHTTPRequest;
LResponse := AResponse as TWinHTTPResponse;
// If the result is true then the while ends
Result := True;
if (AResponse.StatusCode >= 300) and (AResponse.StatusCode < 400) then //Redirect
begin
LURI := TURI.Create(TURI.PathRelativeToAbs(LResponse.GetHeaderValue('Location'), LRequest.FURL));
if SameText(LRequest.GetMethodString, 'POST')
and ( (AResponse.StatusCode >= 301) and (AResponse.StatusCode <= 303) ) then
begin
LRequest.FMethodString := 'GET'; // Change from POST to GET
LRequest.FSourceStream := nil; // Donot send any data
end;
LRequest.UpdateRequest(LURI);
LResponse.UpdateHandles(LRequest.FWConnect, LRequest.FWRequest);
Result := False;
end;
end;
LURI := TURI.Create(TURI.PathRelativeToAbs(LResponse.GetHeaderValue('Location'), LRequest.FURL));
On this line - AV. I think because Response doesn't have header with this name, and he mustn't have this header.
I'm using delphi (RAD Studio 10) to make a little app client and server. The server only send text.
The next functions returns correctly the number of bytes sent. For example I send 'hello' (client side):
procedure TTCPConnection.Write(S: String);
var buff : string;
begin
buff = S+EOL;
WriteBuffer(buff, Length(buff));
end;
Where: S = 'hello' and EOL = #13#10, so buff = 'hello'+EOL.
procedure TTCPConnection.WriteBuffer(var Buffer; const Len: Cardinal);
var
a : Int16;
begin
a := send(FSocket, Buffer, Len, 0);
if (a = SOCKET_ERROR) and FConnected then
begin
HandleError;
Disconnect;
end;
end;
in the previous function: send(FSocket, Buffer, Len, 0) return 7, number of bytes sent ('hello+EOL').
Server side:
function TTCPConnection.ReadLn(Delim: String = EOL): String;
const BUFFER_SIZE = 255;
var
Buff, Buf: string;
I, L: Cardinal;
begin
Result := '';
I := 1;
L := 1;
SetLength(Buff, BUFFER_SIZE);
Buf := AnsiString(Buff);
while Connected and (L <= Cardinal(Length(Delim))) do
begin
if recv(FSocket, Buffer, Len, 0) < 1 then exit;
Buf := Buff[I];
if Buff[I] = Delim[L] then
...
...
end;
(In the previous code I include Buf var for debugger purposes only). When I debug obtain this result:
Buff = {'h', #0, 'e', #0, 'l', #0, 'l'} (7 bytes) and the next time this line is executed recv(FSocket, Buffer, Len, 0) the program does nothing, I guess this is because recv function has nothing to receive (7 bytes were sent).
I need help to make all bytes are received correctly. I do not know why they are in that order (h-#0-e-#0-l-#0-l).
Starting with RAD Studio 2009, string consists of WideChar characters. Char is an alias for WideChar and is 16 bit. The Length() function returns number of characters, not bytes.
You may want to consider using TEncoding to convert your string to a TBytes (array of byte) for sending and vice versa for receiving. I don't know your TTCPConnection class, so it may be you need to tweek the code, but here for the sending part using TEncoding.UTF8:
procedure TTCPConnection.Write(S: String);
var buff : TBytes;
begin
buff := TEncoding.UTF8.GetBytes(S+EOL); // Convert to bytes using UTF8 encoding
WriteBuffer(buff, Length(buff));
end;
And here is my receiver code from a testproject in XE5 where I used the TTcpServer component
procedure TForm3.TcpServer1Accept(Sender: TObject;
ClientSocket: TCustomIpClient);
var
a: array[0..255] of byte;
b: TBytes;
i, n: integer;
s: string;
begin
n := ClientSocket.ReceiveBuf(a,255,0);
SetLength(b, n);
for i := Low(b) to High(b) do b[i] := a[i]; // copy from the receive buffer
s := TEncoding.UTF8.GetString(b); // convert UTF8 bytes to string
Memo1.Lines.Add(format('%4d %s',[n,s]));
end;
Finally a few useful links to the documentation:
TEncoding
UTF-8 Conversion Routines