- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
在开发多用户聊天应用程序时,我一直在让多字节字符通过 TServerSocket/TClientSocket 工作。
这是客户端向服务器发送消息的部分:
procedure TChatForm.SendBtnClick(Sender: TObject);
var str : string;
begin
str := MsgLabel.Text;
ClientSocket.Socket.SendText('message' + separator + nickname + separator + str);
MsgLabel.Text := '';
add_text(MsgBox,MsgLabel,nickname+': '+str,'none');
end;
这是服务器解析接收到的数据的方式:
procedure TServerForm.ServerSocketClientRead(Sender: TObject;Socket: TCustomWinSocket);
var
i,hnd : Integer;
recv : string;
arr : TStringArray; // type TStringArray = array of string;
begin
recv := Socket.ReceiveText;
hnd := Socket.Handle; //using this to avoid sending received data back to the client
arr := SplitStr(recv,separator);
//SplitStr is a function i use because TStringList.DelimitedText uses only a char as delimiter
// sending the data to the others users / but the expeditor - async error workaround
for i:=0 to ServerSocket.Socket.ActiveConnections-1 do begin
if ServerSocket.Socket.Connections[i].Handle <> hnd then
ServerSocket.Socket.Connections[i].SendText(recv);
end;
if arr[0] = 'connect' then begin
// adding the connected user to the tlistbox
Contacts.Items.Add(arr[1]);
// adding the connected message in the trichedit
add_text(MsgBox,SendMsg,arr[1]+' has connected !','green');
end else if arr[0] = 'disconnect' then begin
// removing the user from the online user list
Contacts.Items.Delete(Contacts.Items.IndexOf(arr[1]));
// adding the disconnected message in trichedit
add_text(MsgBox,SendMsg,arr[1]+' has disconnected !','red');
end else if arr[0] = 'message' then begin
// finally adding the message that user send in the TRichEdit
add_text(MsgBox,SendMsg,arr[1]+': '+arr[2],'none');
end;
end;
Socket.ReceiveText 的示例:
- when user connects he sends the next message - connect^SEPARATOR^username
- when a user sends a message - message^SEPARATOR^username^SEPARATOR^message_body
结构是ACTION + SEPARATOR + USERNAME + EXTRA_DATA,这是我“保持”在线用户列表更新的方式。我是delphi新手,如果有更简单的方法,请告诉我。
现在的问题是,如果我向用户发送多字节字符并返回,这些多字节字符将以问号“?”的形式接收。-“ś 或 ř”变为“?或?”
此处打印屏幕:
EDIT2:好的,完成所有更改后,感谢您的回答,我在尝试将服务器从客户端接收到的数据发送回其他客户端时遇到了问题。这个问题有两个小问题:
这就是服务器向用户发送“全局”消息的方式。
procedure TServerForm.SendBtnClick(Sender: TObject);
var
i : Integer;
str : String;
begin
str := SendMsg.Text;
with ServerSocket.Socket do
begin
for i := 0 to ActiveConnections-1 do
SendString(Connections[i], TSocketBuffers(Connections[i].Data).OutBuffer, 'global' + separator + str);
end;
add_text(MsgBox,SendMsg,str,'none');
SendMsg.Text := '';
end;
这是服务器将从一个客户端接收到的数据发送回其他事件连接的方式:
procedure TServerForm.ServerSocketClientRead(Sender: TObject;Socket: TCustomWinSocket);
var
Buffers: TSocketBuffers;
i: Integer;
RecvStr : String;
arr : TStringArray;
begin
Buffers := TSocketBuffers(Socket.Data);
if not Buffers.ReadInData(Socket) then Exit;
Buffers.InBuffer.Position := 0;
try
while ReadString(Buffers.InBuffer, RecvStr) do
begin
arr := SplitStr(RecvStr, separator);
with ServerSocket.Socket do
begin
for i := 0 to ActiveConnections-1 do
begin
if Connections[i] <> Socket then
SendString(Connections[i], TSocketBuffers(Connections[i].Data).OutBuffer, arr[0]);
end;
end;
// [ .. some string processing stuff .. ]
end;
finally
CompactBuffer(Buffers.InBuffer);
end;
end;
现在,如果这两种方法都正确,那么问题就出在客户端读取数据的问题,这就是客户端解析数据的方式,原理与 ServerSocketClientRead(Sender: TObject;Socket: TCustomWinSocket );
procedure TChatForm.ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);
var
Buffers: TSocketBuffers;
i: Integer;
RecvStr : String;
arr : TStringArray;
begin
Buffers := TSocketBuffers(Socket.Data);
if not Buffers.ReadInData(Socket) then Exit;
Buffers.InBuffer.Position := 0;
try
while ReadString(Buffers.InBuffer, RecvStr) do begin
ShowMessage(RecvStr); // testing if anything is received
// [[.. some string processing code ..]]
end;
finally
CompactBuffer(Buffers.InBuffer);
end;
end;
尝试将数据从客户端发送到服务器可以完美地工作,正如您在图像(上图)中看到的那样,字符串被解释为应有的样子。问题是尝试在 ServerSocketClientRead 方法中将数据发送回客户端,或者在 ClientSocketRead 方法中。
更新3:所以我在另一台电脑上启动了客户端,问题似乎出在 ClientSocketRead 方法上(如果 ServerSocketClientRead -> SendString 和全局 SendBtn -> SendString 正确);如果发现任何新的详细信息,我会继续更新。
最佳答案
您需要远离 SendText()
和 ReceiveText()
方法,尤其是在使用非阻塞套接字时。它们不处理数据可能必须以多个数据包的形式发送以及数据包可以以较小的片段甚至多个数据包合并在一起到达的情况。这些是 TCP 编程中必须处理的非常常见的情况。
SendText()
只是将字符串按原样传递给 SendBuf()
。如果它无法在一次发送中发送整个字符串,则它不会尝试重新发送剩余的字符。因此您可以(并且可能会)发送不完整的字符串。它确实返回实际发送的字节数,因此您可以循环调用 SendText()
直到没有更多字符要发送。
ReceiveText()
无法知道正在接收的字符串的长度。它只是读取套接字缓冲区中当前的内容并将其作为字符串返回。因此,这也存在读取不完整字符串,甚至一起读取多个(甚至部分)字符串的风险。
发送字符串的最佳方法是直接使用 SendBuf()
和 ReceiveBuf()
。发送字符串时,要么在发送字符串数据之前发送字符串长度(以字节为单位),要么在字符串数据之后发送不出现在字符串本身中的唯一分隔符。然后接收方可以读取长度值,然后读取指定的字节数,或者读取直到遇到分隔符。另外,在处理非 ASCII 字符串数据时,尤其是 D2009+ 的 UnicodeString
字符串类型时,应将字符串数据在传输过程中编码为通用格式,例如 UTF-8。
如果您使用非阻塞套接字,这会变得更加复杂。如果套接字在发送/读取操作期间进入阻塞状态,则该操作将失败并显示 WSAEWOULDBLOCK
错误代码,并且当套接字脱离阻塞状态时,您必须重复该操作。
如果发送操作因 WSAEWOULDBLOCK
失败,则将剩余数据缓冲在某处(如果该缓冲区不为空,则将 future 的出站数据附加到该缓冲区的末尾),直到 OnWrite
code> 事件触发,然后发送缓冲区中的所有内容,删除成功发送的字节,直到缓冲区被清空或套接字再次阻塞(在这种情况下,您必须等待另一个 OnWrite
事件,然后再发送剩余缓冲区数据)。
同样,当读取操作因 WSAEWOULDBLOCK
失败但您仍在等待数据时,您必须等待另一个 OnRead
事件触发,然后才能尝试再次读取,缓冲已接收到的任何中间数据,直到收到您期望的所有数据,然后才能对其进行处理。
例如:
常用代码:
type
TSocketData = class
private
Socket: TCustomSocketSocket;
InBuffer: TMemoryStream;
OutBuffer: TMemoryStream;
function SendRawToSocket(Data: Pointer; DataLen: Integer): Integer;
procedure Compact(Buffer: TMemoryStream);
public
constructor Create(ASocket: TCustomSocketSocket);
destructor Destroy; override;
function BufferInboundData: Boolean;
procedure FlushOutboundData;
procedure BeginReading;
procedure EndReading;
function SendRaw(Data: Pointer; DataLen: Integer): Boolean;
function ReadRaw(Data: Pointer; DataLen: Integer): Boolean;
function SendInteger(Value: Integer): Boolean;
function ReadInteger(var Value: Integer): Boolean;
function SendInt64(Value: Int64): Boolean;
function ReadInt64(var Value: Int64): Boolean;
function SendString(const Str: String): Boolean;
function ReadString(var Str: String): Boolean;
function SendStream(Stream: TStream): Boolean;
function ReadStream(Stream: TStream): Boolean;
end;
constructor TSocketData.Create(ASocket: TCustomWinSocket);
begin
inherited;
Socket := ASocket;
InBuffer := TMemoryStream.Create;
OutBuffer := TMemoryStream.Create;
end;
destructor TSocketData.Destroy;
begin
InBuffer.Free;
OutBuffer.Free;
inherited;
end;
function TSocketData.SendRawToSocket(Data: Pointer; DataLen: Integer): Integer;
var
Bytes: PByte;
Ret: Integer;
begin
Result := 0;
Bytes := PByte(Data);
while DataLen > 0 do
begin
Ret := Socket.SendBuf(Bytes^, DataLen);
if Ret < 1 then
begin
if WSAGetLastError = WSAEWOULDBLOCK then Break;
Result := -1;
Exit;
end;
Inc(Bytes, Ret);
Dec(DataLen, Ret);
Inc(Result, Ret);
end;
end;
function TSocketData.BufferInboundData: Boolean;
var
RecvLen, OldSize: Integer;
begin
Result := False;
RecvLen := Socket.ReceiveLength;
if RecvLen < 1 then Exit;
OldSize := InBuffer.Size;
InBuffer.Size := OldSize + RecvLen;
try
RecvLen := Socket.ReceiveBuf((PByte(InBuffer.Memory)+OldSize)^, RecvLen);
if RecvLen < 1 then RecvLen := 0;
except
RecvLen := 0;
end;
InBuffer.Size := OldSize + RecvLen;
if RecvLen = 0 then Exit;
Result := True;
end;
procedure TSocketData.FlushOutboundData;
var
Ret: Integer;
begin
if OutBuffer.Size = 0 then Exit;
Ret := SendRawToSocket(OutBuffer.Memory, OutBuffer.Size);
if Ret < 1 then Exit;
OutBuffer.Position := Ret;
Compact(OutBuffer);
end;
procedure TSocketData.Compact(Buffer: TMemoryStream);
var
Remaining: Integer;
begin
if Buffer.Position = 0 then Exit;
Remaining := Buffer.Size - Buffer.Position;
if Remaining > 0 then
Move((PByte(Buffer.Memory) + Buffer.Position)^, Buffer.Memory^, Remaining);
Buffer.Size := Remaining;
end;
procedure TSocketData.BeginReading;
begin
InBuffer.Position := 0;
end;
procedure TSocketData.EndReading;
begin
Compact(InBuffer);
end;
function TSocketData.SendRaw(Data: Pointer; DataLen: Integer): Boolean;
var
Bytes: PByte;
Ret: Integer;
begin
Bytes := PByte(Data);
if OutBuffer.Size = 0 then
begin
Ret := SendRawToSocket(Bytes, DataLen);
if Ret = -1 then
begin
Result := False;
Exit;
end;
Inc(Bytes, Ret);
Dec(DataLen, Ret);
end;
if DataLen > 0 then
begin
OutBuffer.Seek(0, soEnd);
OutBuffer.WriteBuffer(Bytes^, DataLen);
end;
Result := True;
end;
function TSocketData.ReadRaw(Data: Pointer; DataLen: Integer): Boolean;
begin
Result := False;
if (InBuffer.Size - InBuffer.Position) < DataLen then Exit;
InBuffer.ReadBuffer(Data^, DataLen);
Result := True;
end;
function TSocketData.SendInteger(Value: Integer): Boolean;
begin
Value := htonl(Value);
Result := SendRaw(@Value, SizeOf(Value));
end;
function TSocketData.ReadInteger(var Value: Integer): Boolean;
begin
Result := ReadRaw(@Value, SizeOf(Value));
if Result then Value := ntohl(Value);
end;
type
TInt64Parts = packed record
case Integer of
0: (
LowPart: LongWord;
HighPart: LongWord);
1: (
QuadPart: Int64);
end;
function hton64(AValue: Int64): Int64;
var
LParts: TInt64Parts;
L: LongWord;
begin
LParts.QuadPart := AValue;
L := htonl(LParts.HighPart);
LParts.HighPart := htonl(LParts.LowPart);
LParts.LowPart := L;
Result := LParts.QuadPart;
end;
function ntoh64(AValue: Int64): Int64;
var
LParts: TInt64Parts;
L: LongWord;
begin
LParts.QuadPart := AValue;
L := ntohl(LParts.HighPart);
LParts.HighPart := ntohl(LParts.LowPart);
LParts.LowPart := L;
Result := LParts.QuadPart;
end;
function TSocketData.SendInt64(Value: Int64): Boolean;
begin
Value := hton64(Value);
Result := SendRaw(@Value, SizeOf(Value));
end;
function TSocketData.ReadInt64(var Value: Int64): Boolean;
begin
Result := ReadRaw(@Value, SizeOf(Value));
if Result then Value := ntoh64(Value);
end;
function TSocketData.SendString(const Str: String): Boolean;
var
S: UTF8String;
Len: Integer;
begin
S := UTF8String(Str);
Len := Length(S);
Result := SendInteger(Len);
if Result and (Len > 0) then
Result := SendRaw(PAnsiChar(S), Len);
end;
function TSocketData.ReadString(var Str: String): Boolean;
var
S: UTF8String;
Len: Integer;
begin
Result := False;
Str := '';
if not ReadInteger(Len) then Exit;
if (InBuffer.Size - InBuffer.Position) < Len then
begin
InBuffer.Seek(-SizeOf(Len), soCurrent);
Exit;
end;
if Len > 0 then
begin
SetLength(S, Len);
ReadRaw(PAnsiChar(S), Len);
Str := String(S);
end;
Result := True;
end;
function TSocketData.SendStream(Stream: TStream): Boolean;
var
Buf: array[0..1023] of Byte;
Len: Int64;
NumToSend: Integer;
begin
Len := Stream.Size - Stream.Position;
Result := SendInt64(Len);
if Result and (Len > 0) then
begin
repeat
if Len > SizeOf(Buf) then
NumToSend := SizeOf(Buf)
else
NumToSend := Integer(Len);
Stream.ReadBuffer(Buf[0], NumToSend);
Dec(Len, NumToSend);
Result := SendRaw(@Buf[0], NumToSend);
until (Len = 0) or (not Result);
end;
end;
function TSocketData.ReadStream(Stream: TStream): Boolean;
var
Len: Int64;
begin
Result := False;
if not ReadInt64(Len) then Exit;
if (InBuffer.Size - InBuffer.Position) < Len then
begin
InBuffer.Seek(-SizeOf(Len), soCurrent);
Exit;
end;
if Len > 0 then
Stream.CopyFrom(InBuffer, Len);
Result := True;
end;
客户端代码:
procedure TChatForm.ClientSocketConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
Socket.Data := TSocketData.Create(Socket);
end;
procedure TChatForm.ClientSocketDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
TSocketData(Socket.Data).Free;
Socket.Data := nil;
end;
procedure TChatForm.ClientSocketWrite(Sender: TObject; Socket: TCustomWinSocket);
begin
TSocketData(Socket.Data).FlushOutboundData;
end;
procedure TChatForm.ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);
var
SocketData: TSocketData;
i: Integer;
RecvStr : String;
arr : TStringArray;
begin
SocketData := TSocketData(Socket.Data);
if not SocketData.BufferInboundData then Exit;
SocketData.BeginReading;
try
while SocketData.ReadString(RecvStr) do begin
ShowMessage(RecvStr); // testing if anything is received
// [[.. some string processing code ..]]
end;
finally
SocketData.EndReading;
end;
end;
procedure TChatForm.SendBtnClick(Sender: TObject);
var
SocketData: TSocketData;
begin
if ClientSocket1.Socket = nil then Exit;
SocketData := TSocketData(ClientSocket1.Socket.Data);
if SocketData = nil then Exit;
str := MsgLabel.Text;
if SocketData.SendString('message' + separator + nickname + separator + str) then
begin
MsgLabel.Text := '';
add_text(MsgBox, MsgLabel, nickname + ': ' + str, 'none');
end;
end;
服务器代码:
procedure TServerForm.ServerSocketClientConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
Socket.Data := TSocketData.Create(Socket);
end;
procedure TServerForm.ServerSocketClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
TSocketData(Socket.Data).Free;
Socket.Data := nil;
end;
procedure TServerForm.ServerSocketClientRead(Sender: TObject;Socket: TCustomWinSocket);
var
SocketData: TSocketData;
i: Integer;
RecvStr : String;
arr : TStringArray;
begin
SocketData := TSocketData(Socket.Data);
if not SocketData.BufferInboundData then Exit;
SocketData.BeginReading;
try
while SocketData.ReadString(RecvStr) do
begin
arr := SplitStr(RecvStr, separator);
with ServerSocket.Socket do
begin
for i := 0 to ActiveConnections-1 do
begin
if Connections[i] <> Socket then
TSocketData(Connections[i].Data).SendString(RecvStr);
end;
end;
if arr[0] = 'connect' then
begin
Contacts.Items.Add(arr[1]);
add_text(MsgBox, SendMsg, arr[1] + ' has connected !', 'green');
end
else if arr[0] = 'disconnect' then
begin
Contacts.Items.Delete(Contacts.Items.IndexOf(arr[1]));
add_text(MsgBox, SendMsg, arr[1] + ' has disconnected !', 'red');
end
else if arr[0] = 'message' then
begin
add_text(MsgBox, SendMsg, arr[1] + ': ' + arr[2], 'none');
end;
end;
finally
SocketData.EndReading;
end;
end;
procedure TServerForm.ServerSocketClientWrite(Sender: TObject; Socket: TCustomWinSocket);
begin
TSocketData(Socket.Data).FlushOutboundData;
end;
procedure TServerForm.SendBtnClick(Sender: TObject);
var
i : Integer;
str : String;
begin
str := SendMsg.Text;
with ServerSocket.Socket do
begin
for i := 0 to ActiveConnections-1 do
TSocketData(Connections[i].Data).SendString('global' + separator + str);
end;
add_text(MsgBox, SendMsg, str, 'none');
SendMsg.Text := '';
end;
关于delphi - TServerSocket Delphi 上的多字节字符支持,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14086012/
我的 processmaker 安装遇到了一些问题。我正在尝试使用本指南 [url]http://wiki.processmaker.com/index.php/ProcessMaker_Ubuntu
我正在使用 ShareKit。发送 SMS 消息使用 MFMessageComposeViewController,用户看到标题“文本”。我想将该标题更改为更能反射(reflect)实际可用内容的内容
我需要在我的一个针对 Gingerbread 的 Android 应用程序中使用操作栏和 fragment 的组合。所以我使用了 v7 支持库中的操作栏和 v4 支持库中的 fragment ,并使用
我明白为什么浏览器 vendor 不想帮助我阻止他们的 UI 线程。但是,我不明白为什么会有: Web Workers 中没有 sleep (2) 没有同步 WebSockets API 有一个syn
最近我的组织正在考虑使用 Docker。我们组使用的是cloudera CDH 5.1.2。 1) cloudera 是否与 Docker 容器兼容?2) docker 和cloudera 组合是否存
我正在尝试通过编译在 Mac 上安装 rsync 3.2.3。但是,我想安装所有功能。为此,它需要一些库,此处 ( https://download.samba.org/pub/rsync/INSTA
我一直在使用 PyDev 成功运行 nose 测试,并想试试 nose2。 所以我安装了它 pip install nose2 复制/粘贴来自 http://nose2.info/ 的示例代码进入名为
我想知道 LLVM 中是否有任何函数/方法可以在 LLVM IR 中添加 Open-MP 构造。 llvm-3.0 是否仍然支持 OpenMP 指令? 最佳答案 OpenMP 是一种高级语言扩展。因此
我对 CUDA 编程非常陌生。我正在浏览 SDK 附带的示例。我能够编译代码,但是当我运行它时,出现以下错误: "clock.cu(177) : CUDA Runtime API error 38:
RStudio 是用于 R 开发的出色 IDE。我想知道是否有任何方法可以很好地支持 HiDPI 分辨率? 我目前有 13 英寸显示器和 3200x1800 分辨率,甚至很难阅读 RStudio 选项
我正在寻找一种有助于为 Django 项目提供 RDF 支持的工具。 到目前为止,我发现了两个: django-rdf - 最后一次修改是在 4 年前,所以它看起来像是一个死项目。 djubby -
我刚刚尝试了一些 JS 核心原则,发现引擎评估链接的关系运算符而不会引发错误。相反,他们以我自己无法理解的方式进行评估。 console.log(1 4 > 3 > 2 > 1); //false,
我知道 etexteditor 和 vim/emacs。 是否有任何其他 Windows 编辑器支持类似 textmate 的片段(例如,您编写触发词,按 Tab,它更改为某些内容,再次按 Tab,它
我正在尝试找出验证给定集群的网络策略配置的最佳方法。 According to the documentation Network policies are implemented by the ne
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
Z3 会支持 AUFBV 吗? 对于以下脚本: (set-logic AUFBV) (declare-fun x () (_ BitVec 16)) (declare-const t (Array (
使用分部类编写 NUnit 测试的优缺点是什么? 我要开始了: 亲:可以测试私有(private)方法 缺点:TDD 不再可能了 还有什么? 最佳答案 缺点:要么您必须测试与您发布的版本不同的构建,要
它很容易(对于 90% 的 aop 特性)在没有任何语言本身支持的情况下做到这一点,就像大多数动态语言如 python 和 ruby 一样。然而,Dojo在 1.3.2 上直接支持它.最新版本发生
我在我的 android 应用程序中使用亚洲字符,我已经了解到某些字符无法显示,因为系统字体不支持它们。我查询了一个包含亚洲字符的数据库,并且经常检索到无法显示的标志。这些情况对我的应用程序来说通常不
你好,我想实现一个控件,我想在用户键入@字符时启用该控件,直到未填充运行文本中的空格为止,它应该显示用户列表,@符号后键入的文本应该显示基于键盘字符的建议,就像我们在上面看到的那样Twitter 或
我是一名优秀的程序员,十分优秀!