将C# Socket XML接口请求代码迁移至Delphi时无响应求助
C# Socket代码迁移至Delphi Indy
TIdTCPClient无响应问题排查 我正尝试将C# Socket代码迁移至Delphi,使用Indy的TIdTCPClient组件,但始终无法获取响应,直到超时。以下是原C#代码及我编写的Delphi代码,请求协助排查问题。
原C#代码
public static string SendXML(String xmlMessage, int cashbackAmount, bool jumpOut = false) { connection: try { string messageToSend = xmlMessage; String responseStringData = ""; int headerOffset = 2; var length = messageToSend.Length; byte[] sendbytes = new byte[length + 2]; byte[] responsebytes = new byte[1024 * 10]; int remainder = 0; var bytesRec = 0; if (length < 65535) { sendbytes[0] = Convert.ToByte(Math.DivRem(length, 256, out remainder)); sendbytes[1] = Convert.ToByte(remainder); } else { headerOffset = 6; sendbytes[0] = 0xFF; sendbytes[1] = 0xFF; byte[] intBytes = BitConverter.GetBytes(length); Array.Reverse(intBytes); byte[] result = intBytes; Array.Copy(result, 0, sendbytes, 2, 4); } try { IPHostEntry host = Dns.GetHostEntry("localhost"); IPAddress ipAddress = host.AddressList[0]; IPEndPoint remoteEP = new IPEndPoint(ipAddress, 23001); Socket s = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); s.Connect(remoteEP); s.ReceiveTimeout = TimeSpan.FromSeconds(30).Milliseconds; s.SendTimeout = TimeSpan.FromSeconds(30).Milliseconds; byte[] msg = Encoding.ASCII.GetBytes(messageToSend); Array.Copy(msg, 0, sendbytes, headerOffset, msg.Length); int bytesSent = s.Send(sendbytes); bool isEvent = true; bool didContainEvent = false; bool wasCashbackEvent = false; bytesRec = 0; while (isEvent) { bytesRec = s.Receive(responsebytes); responseStringData = Encoding.ASCII.GetString(responsebytes, 0, bytesRec); responseStringData = responseStringData.Substring(2, responseStringData.Length - 2); responseStringData = responseStringData.Replace("\0?", ""); Console.WriteLine("Data Received = {0}", responseStringData); Console.WriteLine(); Console.WriteLine(); isEvent = responseStringData.Contains("Esp:Event") | responseStringData.Contains("Esp:Callback"); if (isEvent) { if (jumpOut) { return responseStringData; } didContainEvent = true; if (!responseStringData.Contains("Esp:Callback")) continue; wasCashbackEvent = true; string cashbackMessage = $"<?xml version=\"1.0\" encoding=\"UTF-8\"?><Esp:Interface Version=\"1.0\" xmlns:Esp=\"http://www.mosaicsoftware.com/Postilion/eSocket.POS/\"><Esp:Callback TerminalId=\"TR000002\" EventId=\"DATA_REQUIRED\" ResponseData=\"{cashbackAmount}\" /></Esp:Interface>"; MessageBox.Show(responseStringData); SendXML(cashbackMessage, 0, jumpOut: true); byte[] cashMsg = Encoding.ASCII.GetBytes(cashbackMessage); Array.Copy(cashMsg, 0, sendbytes, headerOffset, cashMsg.Length); s.Send(sendbytes); } return responseStringData; } } catch (Exception) { return responseStringData; } return responseStringData; } catch (Exception) { goto connection; } }
我的Delphi代码
function TfrmEFT000.SendXML(xXMLMessage :String; xCashbackAmount :Integer = 0; wJumpOut :Boolean = False): string; label Connection; var wResponseStringData :String; wHeaderOffset, wLength, wRemainder, wByteInt :Word; wBytesRec, wBytesSent :Integer; wSendBytes, wResponseBytes, wMsg, wResult :TIdBytes; wIPAddress :TIPAddress; wHost : PHostEnt; ipAddr : array of TIPAddress; wIsEvent, wDidContainEvent, wWasCashbackEvent :Boolean; begin try wHeaderOffset := 2; wLength := Length(xXMLMessage); SetLength(wSendBytes, wLength+2); SetLength(wResponseBytes, 1024 * 10); wRemainder := 0; wBytesRec := 0; DivMod(wLength, 264, wByteInt, wRemainder); if (wLength < 65535) then begin wSendBytes[0] := Byte(wByteInt); wSendBytes[1] := Byte(wRemainder); end else begin wHeaderOffset := 6; wSendBytes[0] := $FF; wSendBytes[1] := $FF; // wIntBytes := BitConverter.GetBytes(wLength); // Array.Reverse(wIntBytes); // byte[] result = wIntBytes; // Array.Copy(result, 0, sendbytes, 2, 4); end; TCPClient.IOHandler.WriteBufferOpen; try wMsg := IndyTextEncoding_ASCII.GetBytes(xXMLMessage); Assert(Length(wMsg)<=Length(wSendBytes)); Move(wMsg[0], wSendBytes[2], Length(wMsg)); TCPClient.IOHandler.Write(wSendBytes,Length(wSendBytes),2); TCPClient.IOHandler.WriteBufferClose; wIsEvent := True; wDidContainEvent := False; wWasCashbackEvent := False; while wIsEvent do begin Connection: TCPClient.Socket.CheckForDataOnSource(255); wResponseStringData := TCPClient.IOHandler.ReadLnWait(30000,IndyTextEncoding_ASCII); if (not TCPClient.Socket.InputBufferIsEmpty) then wResponseStringData := TCPClient.Socket.InputBufferAsString(IndyTextEncoding_ASCII); if wResponseStringData = '' then begin goto Connection; end; wResponseStringData := IndyTextEncoding_ASCII.GetString(wResponseBytes,0,wBytesRec); wResponseStringData := wResponseStringData.Substring(2, wResponseStringData.Length - 2); wResponseStringData := wResponseStringData.Replace('\0?', ''); LogMessage(lmtInformation, ldIn, 'Data Received = {0}'); LogMessage(lmtInformation, ldIn, wResponseStringData); LogMessage(lmtInformation, ldIn, ''); LogMessage(lmtInformation, ldIn, ''); end; except on E:Exception do begin LogMessage(lmtError, ldIn, E.Message); ShowMessage(E.Message); Exit; end; end; except on E:Exception do begin TCPClient.IOHandler.WriteBufferCancel; LogMessage(lmtError, ldIn, E.Message); ShowMessage(E.Message); //goto Connection; end; end; end;
问题排查与修正方案
核心问题点
- 长度计算错误:C#中用256作为除数拆分长度高位/低位,Delphi误用264,导致头部长度标识错误,服务端无法解析消息。
- 长消息头部未实现:长度≥65535时,Delphi未写入4字节大端格式的长度值,导致消息不完整。
- 发送偏移错误:写入消息内容时,固定用偏移2,未根据
wHeaderOffset动态调整,长消息会覆盖头部数据。 - 接收逻辑完全错误:原协议是二进制字节流,却用
ReadLnWait按行读取;未实际读取字节到wResponseBytes,wBytesRec始终为0,导致后续字符串处理无效;Delphi字符串索引从1开始,Substring用法错误;转义字符串\0?无法匹配ASCII空字符。 - 连接逻辑缺失:未处理
TIdTCPClient的连接流程,若组件未提前连接,发送会失败。 - 递归调用风险:原C#递归调用
SendXML会新建Socket,但Delphi若用全局TIdTCPClient,递归会导致连接冲突,应复用当前连接。
修正后的Delphi代码
function TfrmEFT000.SendXML(xXMLMessage: String; xCashbackAmount: Integer = 0; wJumpOut: Boolean = False): string; label Connection; var wResponseStringData: String; wHeaderOffset, wLength: Integer; wRemainder: Word; wBytesRec, wBytesSent: Integer; wSendBytes, wResponseBytes, wMsg: TIdBytes; wIsEvent, wDidContainEvent, wWasCashbackEvent: Boolean; wCashbackMessage: String; wIntBytes: array[0..3] of Byte; begin Result := ''; Connection: try wHeaderOffset := 2; wLength := Length(xXMLMessage); // 初始化发送缓冲区:短消息长度+2,长消息长度+6 if wLength < 65535 then SetLength(wSendBytes, wLength + 2) else SetLength(wSendBytes, wLength + 6); // 构建消息头部 if wLength < 65535 then begin // 拆分长度为高位(256)和低位 wSendBytes[0] := Byte(wLength div 256); wSendBytes[1] := Byte(wLength mod 256); end else begin wHeaderOffset := 6; wSendBytes[0] := $FF; wSendBytes[1] := $FF; // 将长度转为大端4字节 wIntBytes[3] := Byte(wLength and $FF); wIntBytes[2] := Byte((wLength shr 8) and $FF); wIntBytes[1] := Byte((wLength shr 16) and $FF); wIntBytes[0] := Byte((wLength shr 24) and $FF); Move(wIntBytes[0], wSendBytes[2], 4); end; // 写入XML消息内容 wMsg := IndyTextEncoding_ASCII.GetBytes(xXMLMessage); Move(wMsg[0], wSendBytes[wHeaderOffset], Length(wMsg)); // 确保连接到服务端 if not TCPClient.Connected then begin TCPClient.Host := 'localhost'; TCPClient.Port := 23001; TCPClient.ReadTimeout := 30000; TCPClient.Connect; end; // 发送消息 TCPClient.IOHandler.Write(wSendBytes); // 接收响应 wIsEvent := True; wDidContainEvent := False; wWasCashbackEvent := False; SetLength(wResponseBytes, 1024 * 10); while wIsEvent do begin // 读取响应字节 wBytesRec := TCPClient.IOHandler.ReadBytes(wResponseBytes, -1, False); if wBytesRec <= 0 then Break; // 转换为字符串并处理头部 wResponseStringData := IndyTextEncoding_ASCII.GetString(wResponseBytes, 0, wBytesRec); // Delphi字符串索引从1开始,跳过前2字节 if Length(wResponseStringData) > 2 then wResponseStringData := Copy(wResponseStringData, 3, Length(wResponseStringData) - 2) else wResponseStringData := ''; // 替换ASCII空字符+问号 wResponseStringData := StringReplace(wResponseStringData, #0'?', '', [rfReplaceAll]); LogMessage(lmtInformation, ldIn, 'Data Received = ' + wResponseStringData); LogMessage(lmtInformation, ldIn, ''); // 判断是否为事件/回调 wIsEvent := (Pos('Esp:Event', wResponseStringData) > 0) or (Pos('Esp:Callback', wResponseStringData) > 0); if wIsEvent then begin if wJumpOut then begin Result := wResponseStringData; Exit; end; wDidContainEvent := True; // 处理回调消息 if Pos('Esp:Callback', wResponseStringData) > 0 then begin wWasCashbackEvent := True; wCashbackMessage := '<?xml version="1.0" encoding="UTF-8"?><Esp:Interface Version="1.0" xmlns:Esp="http://www.mosaicsoftware.com/Postilion/eSocket.POS/"><Esp:Callback TerminalId="TR000002" EventId="DATA_REQUIRED" ResponseData="' + IntToStr(xCashbackAmount) + '" /></Esp:Interface>'; // 复用当前连接发送回调响应 SendXML(wCashbackMessage, 0, True); end else Continue; end; // 返回非事件响应 Result := wResponseStringData; Break; end; except on E: Exception do begin LogMessage(lmtError, ldIn, E.Message); ShowMessage(E.Message); // 断开连接,重试一次 if TCPClient.Connected then TCPClient.Disconnect; goto Connection; end; end; end;
内容的提问来源于stack exchange,提问作者Ntsito Maelane
相关产品推荐
相关产品推荐

