Implement Socket Client on Free Pascal / Lazarus
Note: This article was translated mainly by ChatGPT. If there is any mistake, please contact me.
Research on the Plan
Delphi was a popular programming tool in the early 21st century. Based on Pascal, it developed a series of object-oriented syntax, forming an Object Pascal ecosystem.
However, Microsoft seemed unwilling to allow another company to dominate the discourse of software development in the Windows world, or possibly due to the profit motives of the Visual series of development tools, Microsoft crushed Borland, the company behind Delphi, in a series of competitions. Consequently, Delphi gradually declined. The Lazarus ecosystem is an open-source alternative to Delphi, inheriting from Delphi but not entirely the same.
Having said all this background, I just want to complain that both Delphi and Free Pascal/Lazarus are really still stuck in the past era’s understanding of desktop application software. Their support for the web is really, really weak, far from reaching the level of many languages today.
This has led to the fact that writing a Socket Client in the Free Pascal/Lazarus ecosystem is truly a daunting task. If there’s a next time, I definitely won’t use Pascal for network clients or servers again…
A rough search showed that for Free Pascal to create a Socket Client, the basic options are the Indy library and the built-in Sockets in FP.
The Indy library requires the introduction of a third-party library, so the built-in Sockets in FP might be better.
Pitfalls
I quickly made a version following the official documentation, but found that it couldn’t even pass compilation. The official documentation is actually wrong…
The Connect() function used in the official documentation can’t be found in the Sockets…
A TCP Socket program given in the online forum seems to work, but it lacks processes like DNS resolution.
So, referring to the C TCP Socket standard program and after a lot of searching, I found the resolve library and the THostResolver class.
Thinking I could be overly clever, I attempted to use the fpWrite/fpRead function instead of the fpSend/fpRecv function, following the C Socket standard program. Then I found that the fpWrite/fpRead function are also broken…
Going to the definition in the Lazarus Sockets library, it turns out that it’s just a wrapper for winsock. I don’t know if someone once wanted to connect Sockets with Pascal’s built-in input and output, but for some reason, they gave up, leaving a half-finished Connect() in the documentation…
Implementation
function ValidateAndParseServerAddress(raw_addr: ansistring; var addr: string; var port: integer): boolean;
var
port_str: string;
stage: integer;
i: longint;
begin
stage := 0;
ValidateAndParseServerAddress := false;
addr := '';
port_str := '';
port := 0;
for i:=1 to length(raw_addr) do
begin
case raw_addr[i] of
':': inc(stage);
else
case stage of
0: addr += raw_addr[i];
1: port_str += raw_addr[i];
else
exit;
end;
end
end;
if stage < 1 then exit;
port := strtoint(port_str);
if (port < 0) or (port > 65535) then exit;
ValidateAndParseServerAddress := true;
end;
function GetHostByName(host_name:String):String;
var
host:THostResolver;
begin
host := THostResolver.Create(nil);
if host.NameLookup(host_name) then
result := host.AddressAsString
else
result := '';
host.Free;
end;
function GetRedpackInfo(server_addr_raw: ansistring; var redpack1, redpack2: longint; var valid1, valid2: boolean): boolean;
var
server_addr: string;
server_port: integer;
host_entry: string;
fp_socket_fd: longint;
saddr: TSockAddr;
buf: TBuffer;
begin
GetRedpackInfo := false;
if not ValidateAndParseServerAddress(server_addr_raw, server_addr, server_port) then
begin
showMessage('Invalid Server Address');
exit;
end;
host_entry := GetHostByName(server_addr);
fp_socket_fd := fpSocket(AF_INET, SOCK_STREAM, 0);
if fp_socket_fd < 0 then
begin
showMessage('Failed to create Socket');
exit;
end;
saddr.sin_family := AF_INET;
saddr.sin_port := htons(server_port);
saddr.sin_addr := StrToNetAddr(host_entry);
if fpConnect(fp_socket_fd, @saddr, sizeof(saddr)) <> 0 then
begin
showMessage('Cannot establish a connection to server');
CloseSocket(fp_socket_fd);
exit;
end;
BuildModbusReadCmd(buf, 1, 0, 0, 3);
fpSend(fp_socket_fd, @buf, 12, 0);
fpRecv(fp_socket_fd, @buf, 255, 0);
if not ParseModbusRedpackReadResp(buf, redpack1, valid1) then
begin
showMessage('Cannot parse Modbus packet');
CloseSocket(fp_socket_fd);
exit;
end;
BuildModbusReadCmd(buf, 3, 1, 0, 3);
fpSend(fp_socket_fd, @buf, 12, 0);
fpRecv(fp_socket_fd, @buf, 255, 0);
if not ParseModbusRedpackReadResp(buf, redpack2, valid2) then
begin
showMessage('Cannot parse Modbus packet');
CloseSocket(fp_socket_fd);
exit;
end;
CloseSocket(fp_socket_fd);
GetRedpackInfo := true;
end;
The ParseModbusRedpackReadResp() function is not related to the main part of the Socket Client, so I will not provide it here.
- Author: ShadowMov's Blog
- Link: https://shadowmov.com/en/posts/implement-socket-client-in-free-pascal/
- License: This work is under a CC BY-SA 4.0. Kindly fulfill the requirements of the aforementioned License when adapting or creating a derivative of this work.