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.