我是WebSockets的新手,所以我很抱歉,如果这是由于我的错,但是当我在一个while循环(仅测试)中将数据从WebSocket(在chrome中)发送到服务器(C#,TCPListener)时没有正确到达服务器。它的第一个字节通常消失,有时键也会在两个消息之间混合。这导致我的服务器崩溃。当数据不规则地发送或者不是一个接一个地超快速发送时,这种情况永远不会发生。
这是服务器接收器代码:
#region Receiver
public class Receiver
{
private const int DEFAULT_DATA_LIMIT = 256; // Bytes
private const int HEADER_LENGTH = 2;
private const int KEYS_LENGTH = 4;
private const int SHORT_BYTES = 2;
private const int LONG_BYTES = 8;
private const int SHORT_DATA = 126;
private const int LONG_DATA = 127;
private readonly Socket TargetSocket;
private readonly object DataLock = new object();
private readonly object amountOfDataReceivedLock = new object();
private readonly object RecevingLock = new object();
private readonly object CompletingRecieveLock = new object();
private bool receiving;
private int _amoutOfDataReceived;
private byte[] Data;
private int amountOfDataReceived
{
get
{
lock(amountOfDataReceivedLock)
return _amoutOfDataReceived;
}
set
{
lock(amountOfDataReceivedLock)
_amoutOfDataReceived = value;
}
}
public int Capacity
{
get
{
return Data.Length;
}
}
public int FreeSpace
{
get
{
return Capacity - amountOfDataReceived;
}
}
public bool ReceivingData
{
get
{
return receiving;
}
}
public bool ChunkReady
{
get
{
int wholeChunkLength = WholeChunkLength();
if(wholeChunkLength == -1)
return false;
return WholeChunkLength() <= amountOfDataReceived;
}
}
public Receiver(Socket socket, int dataLimit)
{
this.TargetSocket = socket;
Data = new byte[dataLimit];
}
public Receiver(Socket socket) : this(socket, DEFAULT_DATA_LIMIT) { }
private int ChunkDataLength()
{
lock(DataLock)
{
lock(amountOfDataReceivedLock)
{
if(amountOfDataReceived < 1)
return -1;
int lengthInfoIndex = HEADER_LENGTH - 1;
if(amountOfDataReceived < lengthInfoIndex + 1)
return -1;
int rawLength = Data[lengthInfoIndex] -128;
int bytesRequiredToGetLength = 0;
if(rawLength == SHORT_DATA)
bytesRequiredToGetLength = SHORT_BYTES;
else if(rawLength == LONG_DATA)
bytesRequiredToGetLength = LONG_BYTES;
else
return rawLength;
if(amountOfDataReceived < lengthInfoIndex + 1 + bytesRequiredToGetLength)
return -1;
return Utilities.ToInt32(Data, lengthInfoIndex + 1, bytesRequiredToGetLength);
}
}
}
private int WholeChunkLength()
{
int dataLength = ChunkDataLength();
int lengthInfoLength = dataLength < SHORT_DATA ? 0 : dataLength < short.MaxValue ? SHORT_BYTES : LONG_BYTES;
if(dataLength == -1)
return -1;
return HEADER_LENGTH + lengthInfoLength + KEYS_LENGTH + dataLength;
}
private void ReceiveDataInternal(int dataToReceiveLength)
{
if(dataToReceiveLength == 0)
return;
lock(RecevingLock)
{
if(receiving)
return;
receiving = true;
}
if(dataToReceiveLength > FreeSpace)
dataToReceiveLength = FreeSpace;
TargetSocket.BeginReceive(Data, amountOfDataReceived, dataToReceiveLength, SocketFlags.None, result =>
{
OnRecevingComplete(result, dataToReceiveLength);
}, null);
}
private void OnRecevingComplete(System.IAsyncResult result, int receivedDataLength)
{
lock(CompletingRecieveLock) // This is not needed really
{
TargetSocket.EndReceive(result);
this.amountOfDataReceived += receivedDataLength;
receiving = false;
}
}
public void ReceiveData()
{
ReceiveDataInternal(TargetSocket.Available);
}
public byte[] GetChunk()
{
lock(DataLock)
{
lock(amountOfDataReceivedLock)
{
int chunkLength = WholeChunkLength();
if(chunkLength == -1 || chunkLength > amountOfDataReceived)
return null;
// throw new System.InvalidOperationException("Chunk is yet not ready!");
byte[] chunk = new byte[chunkLength];
for(int i = 0; i < chunkLength; i++)
chunk[i] = Data[i];
ArrayUtilities<byte>.ShiftArrayLeft(Data, chunkLength, amountOfDataReceived);
amountOfDataReceived -= chunkLength;
return chunk;
}
}
}
}
#endregion
这是调用Receiver类的函数
private void Update()
{
if(!running)
return;
for(int i = 0; i < clients.TotalClients; i++)
{
if(!clients[i].IsReady)
continue;
var recievier = clients[i].GetReciever();
if(recievier.recievier.FreeSpace > 0 && clients[i].DataAvaliable && !recievier.Receiving)
recievier.ReceiveData();
if(recievier.ChunkReady)
{
var data = recievier.GetChunk();
Utilities.WSFormatter.DecodeMessage(data);
int SI = Utilities.WSFormatter.MessageStartIndex(data);
System.Console.WriteLine(System.Text.Encoding.UTF8.GetString(data, SI, data.Length - SI));
}
}
}
函数本身是通过类似的东西调用的(在新线程上,而不是默认线程):
while (true)
{
Update();
System.Threading.Thread.Sleep(1);
}
这是WebSocket代码:
Engine.Loader.loadEngine(function() {
var client = new Engine.Client("ws:192.168.1.105:8080");
var connected = false;
client.addConnectListener(function()
{
connected = true;
console.log("Connection successful!");
});
client.addRecieveListener(function(data)
{
console.log(data);
});
var gl = new Engine.GameLoop(new Engine.Renderer(), new Engine.Input(), client);
gl.start();
var i = 0;
gl.addEventListener("UPDATE", function()
{
/*The code that works*/
if(connected)
client.sendString("" + i++);
/*The code that causes problems*/
var j = 10;
while(connected && j-- > 0)
client.sendString("" + i++);
});
});
编辑:UPDATE事件由window.requestAnimationFrame
调用 编辑:我之前发布的答案也失败了。事实证明它只有在错误发生之前数据[]被填充时才有效。因此,如果我增加数据容量[],我还需要增加接收数据之间的时间。 编辑:完成整个工作。它有不止一个问题。事实证明,Socket.BeginReceive()方法在内部执行的操作以及我同时调用的DataReceiver.GetChunk()修改了数据。 所以现在我首先在临时缓冲区中接收数据,然后在触发完成事件时将其写入主缓冲区,同时锁定它,这样其他线程就不会乱用它(据我所知,完整的方法没有调用调用BeginRecieve函数的线程。)同样正如@vtortola所说,当完成事件被触发时,所有数据可能都不在缓冲区中,所以我也考虑过了。
这是实际可行的脚本。它是重写的,但有些方法是从旧脚本中复制的(是的,我很懒):
public class DataReceiver
{
private const int DEFAULT_DIRECT_BUFFER_LIMIT = 256;
private const int DEFAULT_DATA_LIMIT = 256; // bytes
private const int HEADER_LENGTH = 2;
private const int KEYS_LENGTH = 4;
private const int SHORT_DATA = 126;
private const int SHORT_BYTES = 2;
private const int LONG_DATA = 127;
private const int LONG_BYTES = 8;
private readonly Socket TargetSocket;
private readonly object dataUpdatingLock = new object();
private bool receivingData;
private byte[] directRecieveBuffer;
private int dataReceived;
private byte[] data;
public int Capacity
{
get
{
return data.Length;
}
}
public int AmountOfDataReceived
{
get
{
return dataReceived;
}
}
public int FreeSpace
{
get
{
return Capacity - AmountOfDataReceived;
}
}
public bool ReceivingData
{
get
{
return receivingData;
}
}
public bool ChunkReady
{
get
{
int chunkLength = WholeChunkLength();
if(chunkLength < 1)
return false;
return chunkLength <= dataReceived;
}
}
private DataReceiver(Socket socket, int bufferLength, int directBufferLength)
{
this.TargetSocket = socket;
this.data = new byte[bufferLength];
this.directRecieveBuffer = new byte[directBufferLength];
}
public DataReceiver(Socket socket, int bufferLength) : this(socket, bufferLength, DEFAULT_DIRECT_BUFFER_LIMIT) { }
public DataReceiver(Socket socket) : this(socket, DEFAULT_DATA_LIMIT, DEFAULT_DIRECT_BUFFER_LIMIT) { }
private void ReceiveDataInternally()
{
receivingData = true;
int expectedDataLength = TargetSocket.Available;
if(expectedDataLength > FreeSpace)
expectedDataLength = FreeSpace;
if(expectedDataLength > directRecieveBuffer.Length)
expectedDataLength = directRecieveBuffer.Length;
TargetSocket.BeginReceive(directRecieveBuffer, 0, expectedDataLength, SocketFlags.None, result =>
{
int receivedDataLength = TargetSocket.EndReceive(result);
lock(dataUpdatingLock)
{
for(int i = 0; i < receivedDataLength; i++)
{
data[dataReceived++] = directRecieveBuffer[i];
directRecieveBuffer[i] = 0;
}
}
receivingData = false;
}, null);
}
public byte[] GetChunk()
{
int chunkLength = WholeChunkLength();
if(chunkLength == -1 || chunkLength > dataReceived)
return null;
byte[] chunk = new byte[chunkLength];
for(int i = 0; i < chunkLength; i++)
chunk[i] = data[i];
lock(dataUpdatingLock)
{
ArrayUtilities<byte>.ShiftArrayLeft(data, chunkLength, dataReceived);
dataReceived -= chunkLength;
}
return chunk;
}
private int ChunkDataLength()
{
if(dataReceived < 1)
return -1;
int lengthInfoIndex = HEADER_LENGTH - 1;
if(dataReceived < HEADER_LENGTH)
return -1;
int rawLength = data[lengthInfoIndex] & 127;
int bytesRequiredToGetLength = 0;
if(rawLength == SHORT_DATA)
bytesRequiredToGetLength = SHORT_BYTES;
else if(rawLength == LONG_DATA)
bytesRequiredToGetLength = LONG_BYTES;
else
return rawLength;
if(dataReceived < HEADER_LENGTH + bytesRequiredToGetLength)
return -1;
return Utilities.ToInt32(data, lengthInfoIndex + 1, bytesRequiredToGetLength);
}
private int WholeChunkLength()
{
int dataLength = ChunkDataLength();
int lengthInfoLength = dataLength < SHORT_DATA ? 0 : dataLength < short.MaxValue ? SHORT_BYTES : LONG_BYTES;
if(dataLength == -1)
return -1;
return HEADER_LENGTH + lengthInfoLength + KEYS_LENGTH + dataLength;
}
public void StartReceving()
{
if(!receivingData)
ReceiveDataInternally();
}
}
答案 0 :(得分:0)
当您致电recievier.ReceiveData();
时,您认为在方法完成时已经读取了数据,但这是不对的。对该方法的调用仅启动读取,该读取将在调用OnRecevingComplete
的匿名委托上异步终止。当应用程序有更多负载只是一个简单的调用时,这将更加明显。如果您使用TcpListener
并使用async/await
而非Socket
以及BeginXXX
和EndXXX
方法,则会更容易。
您似乎也假设一旦读取完成,您的所有数据都在缓冲区中,但可能没有。例如,对于读取6个字节,您可能需要读取6次,或3次,或者可能只是1.您需要在某处继续读取数据while
,直到您完成所有预期的有效负载。< / p>
请参阅此处的示例:How can I read from a socket repeatedly?
public void ContinuousReceive(){
byte[] buffer = new byte[1024];
bool terminationCodeReceived = false;
while(!terminationCodeReceived){
try{
if(server.Receive(buffer)>0){
// We got something
// Parse the received data and check if the termination code
// is received or not
}
}catch (SocketException e){
Console.WriteLine("Oops! Something bad happened:" + e.Message);
}
}
}
作为旁注:
return Utilities.ToInt32(Data, lengthInfoIndex + 1, bytesRequiredToGetLength);
Int32
有4个字节,因此如果将8个字节解析为Int32
,它可能会溢出。另外,请记住长度为ushort
,uint
和ulong
(即:无符号整数),具体取决于标头是否指示&lt; = 125,126或127。
您无法使用多个线程读取,也无法在套接字上使用多个线程进行写入。您可以同时从独立的读写,但不能同时读取相同的操作。这将导致不可预测的错误。你不应该有任何锁,并认为该类不是线程安全的(例如它是NetworkStream
)。多线程不会在网络编程中带来更好的性能,只是没有。相反,专注于使您的代码异步。