我有一個基于微軟提供的例子的C#、.NET資料服務器:
https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socketasynceventargs?view=net-5.0
我已經讓這個資料服務器作業了很多年,有一個簡單的設備監聽器來處理發送的資料。以前的用例是設備向服務器發送資料,服務器發回一個回應,然后設備關閉連接。這一點運行良好。
我正在添加讓設備在收到回應后發送額外資料的功能。這個功能在第一次時運行良好。但是在第一次之后,我得到的后續訊息中,傳輸的位元組數為零,SocketError仍被設定為Success,但是Available顯示了客戶端設備發送的適當的資料量。所以,資料服務器將其視為一個關閉的連接。我不明白為什么服務器沒有收到資料,即使WireShark顯示了傳輸的資料,而且套接字上的可用位元組數顯示它們正在等待被接收。這就像服務器進入了一個糟糕的狀態,它實際上無法接收正在等待的位元組。
下面是資料服務器的代碼:
using System;
using System.Collections;
using System.Collections.Generic;
使用System.Linq.Options; 使用System.Linq.Options
使用System.Net;
using System.Net.Sockets;
namespaceDeviceListener
{
class SocketAsyncUserToken {
{
public Socket Socket。
public DateTime AcceptedAt;
public DateTime LastUpdate;
public bool Closed = false。
public int TotalBytes = 0;
public string DeviceId = null;
public byte[] ReceiveBuffer = new byte[2048] 。
public byte[] SendBuffer = new byte[2048] 。
}
public enum DataServerEventTypes
{
None = 0,
未知。
錯誤。
警告。
資訊。
服務器已啟動。
服務器停止了。
連接已接受。
連接已關閉。
連接拒絕。
收到的資料。
資料發送。
訊息處理
}
public class DataServerEventArgs : EventArgs
{
public DataServerEventTypes EventType;
public string DeviceId;
public string Details;
public Exception 例外。
}
//為套接字服務器實作了連接邏輯。
//接受連接后,所有從客戶端讀取的資料。
//被送回客戶端。讀取并回傳給客戶端的模式/span>。
//繼續進行,直到客戶端斷開連接。
public class SocketAsyncServer
{
private int m_numConnections; //the max number of connections the sample is designed to handle simultaneously
private int m_receiveBufferSize;//用于每個套接字I/O操作的緩沖區大小。
SocketAsyncBufferManager m_bufferManager; //代表一個大型的可重復使用的緩沖區集,用于所有套接字操作。
const int opsToPreAlloc = 2; //讀、寫(不要為接受分配緩沖空間)
Socket listenSocket; //用于監聽進入的連接請求的套接字。
///可重復使用的SocketAsyncEventArgs物件池,用于寫、讀和接受套接字操作。
SocketAsyncEventArgsPool m_readWritePool。
int m_totalBytesRead; //服務器收到的總#位元組的計數器。
int m_numConnectedSockets; //連接到服務器的客戶總數。
Semaphore m_maxNumberAcceptedClients。
string m_AuthenticationId;
List<SocketAsyncEventArgs> m_AcceptedConnections。
public event EventHandler<DataServerEventArgs> EventNotify;
//創建一個未初始化的服務器實體。
//要啟動服務器監聽連接請求。
//呼叫Init方法,然后是Start方法。
//
//<param name="numConnections">樣本被設計為同時處理的最大連接數</param>
// <param name="receiveBufferSize">為每個套接字I/O操作使用的緩沖區大小</param>。
public SocketAsyncServer(intnumConnections, int receiveBufferSize, string authenticationId)。
{
m_totalBytesRead = 0;
m_numConnectedSockets = 0;
m_numConnections = numConnections。
m_receiveBufferSize = receiveBufferSize;
m_AuthenticationId = authenticationId。
//分配緩沖區,使最大數量的套接字可以有一個未完成的讀取和。
//寫同時發布到套接字上
m_bufferManager = new SocketAsyncBufferManager(receiveBufferSize * numConnections * opsToPreAlloc, receiveBufferSize)。
m_readWritePool = new SocketAsyncEventArgsPool(numConnections)。
m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);
m_AcceptedConnections = new List<SocketAsyncEventArgs>()。
}
// <summary>
//當一項作業完成時觸發的事件處理器。
// </summary>/span>
// <param name="e"></param>
public void OnEventNotify(DataServerEventArgs e)
{
EventNotify?.Invoke(this, e) 。
}
///通過預分配可重用的緩沖區和來初始化服務器。
// context物件。 這些物件不需要預先分配
//或重復使用,但這樣做是為了說明API如何//或重復使用。
//很容易被用來創建可重用的物件以提高服務器性能。
//
public void Init()
{
///分配一個大的位元組緩沖區,所有的I/O操作都使用其中的一塊。 這保證了
//防止記憶體碎片化。
m_bufferManager.InitBuffer()。
//預分配SocketAsyncEventArgs物件池
SocketAsyncEventArgs readWriteEventArg;
for (int i = 0; i < m_numConnections; i )
{
//Pre-allocate a set of reusable SocketAsyncEventArgs.
readWriteEventArg = new SocketAsyncEventArgs()。
readWriteEventArg.Completed = new EventHandler<SocketAsyncEventArgs>(SendReceive_Completed)。
readWriteEventArg.UserToken = new SocketAsyncUserToken()。
//從緩沖池中分配一個位元組的緩沖區給SocketAsyncEventArg物件。
m_bufferManager.SetBuffer(readWriteEventArg)。
//將SocketAsyncEventArg添加到池中
m_readWritePool.Push(readWriteEventArg)。
}
}
//啟動服務器,使其監聽。
//進入的連接請求。
//
// <param name="localEndPoint">服務器將監聽的端點。
//用于連接請求的</param>
public bool Start(IPEndPoint localEndPoint)
{
DataServerEventArgs args = new DataServerEventArgs();
args.EventType = DataServerEventTypes.ServerStarted;
args.Details = "Server is starting on port " localEndPoint.Port "。"。
OnEventNotify(args);
//創建監聽傳入連接的socket。
listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp)。
listenSocket.Bind(localEndPoint)。
//啟動服務器,聽積壓的100個連接。
listenSocket.Listen(100)。
///在監聽套接字上發布接受信號。
StartAccept(null)。
return true。
}
public bool Stop()
{
List<SocketAsyncEventArgs> connectionsToClose = new List<SocketAsyncEventArgs>()。
foreach (SocketAsyncEventArgs s in m_AcceptedConnections)
{
if (s.ConnectSocket != null)
{
DataServerEventArgs args = new DataServerEventArgs()。
args.EventType = DataServerEventTypes.ConnectionClosed;
args.Details = "服務器已強制關閉套接字" s.ConnectSocket.Handle.ToInt32() " 由于關閉。
OnEventNotify(args);
connectionsToClose.Add(s)。
}
}
if (connectionToClose.Count > 0)
{
foreach (SocketAsyncEventArgs s in connectionsToClose)
{
CloseClientSocket(s)。
}
}
return true。
}
//開始一個操作,接受來自客戶端的連接請求。
//
// <param name="acceptEventArg">發布時要使用的背景關系物件。
//在服務器的監聽套接字上的接受操作</param>
public void StartAccept(SocketAsyncEventArgs acceptEventArg)
{
if (acceptEventArg == null)
{
acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed = new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed)。
}
else[/span
{
// socket必須被清除,因為背景關系物件被重新使用。
acceptEventArg.AcceptSocket = null;
}
DateTime currentTime = DateTime.Now;
SocketAsyncUserToken token = null;
TimeSpan ts = new TimeSpan(0, 1, 0)。
List<SocketAsyncEventArgs> connectionsToClose = new List<SocketAsyncEventArgs>()。
foreach (SocketAsyncEventArgs s in m_AcceptedConnections)
{
token = (SocketAsyncUserToken)s.UserToken。
if (currentTime - token.LastUpdate > ts)
{
DataServerEventArgs args = new DataServerEventArgs();
args.EventType = DataServerEventTypes.ConnectionClosed;
args.Details = "服務器已強制關閉套接字" token.Socket.Handle.ToInt32() " 由于超時,"。
OnEventNotify(args);
connectionsToClose.Add(s)。
}
}
if (connectionsToClose.Count > 0)
{
foreach (SocketAsyncEventArgs s in connectionsToClose)
{
CloseClientSocket(s)。
}
}
m_maxNumberAcceptedClients.WaitOne()。
bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg)。
if (!willRaiseEvent)
{
ProcessAccept(acceptEventArg)。
}
}
//這個方法是與Socket.AcceptAsync相關的回呼方法。
//操作,并在接受操作完成后被呼叫。
//
void AcceptEventArg_Completed(>sender, SocketAsyncEventArgs e)
{
ProcessAccept(e)。
}
private void ProcessAccept(SocketAsyncEventArgs e)。
{
Interlocked.Increment(ref m_numConnectedSockets)。
DataServerEventArgs args = new DataServerEventArgs();
args.EventType = DataServerEventTypes.ConnectionAccepted。
args.Details = "服務器在套接字上接受了一個連接" e.AcceptSocket.Handle.ToInt32() "。"。
OnEventNotify(args);
args.EventType = DataServerEventTypes.ConnectionAccepted;
args.Details = "有" m_numConnectedSockets " 客戶端連接。"。
OnEventNotify(args);
//獲取已接受的客戶端連接的套接字,并將其放入中。
//ReadEventArg物件的用戶令牌。
SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop()。
SocketAsyncUserToken token = (SocketAsyncUserToken)readEventArgs.UserToken。
token.Socket = e.AcceptSocket;
token.AcceptedAt = DateTime.Now;
token.LastUpdate = token.AcceptedAt;
token.TotalBytes = 0;
token.Closed = false;
token.DeviceId = null;
token.WaitingForControlResponse = false;
m_AcceptedConnections.Add(readEventArgs)。
//一旦客戶端被連接,就向連接發布一個接收。
bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs)。
if (!willRaiseEvent)
{
ProcessReceive(readEventArgs)。
}
//接受下一個連接請求。
StartAccept(e);
}
//每當套接字上的接收或發送操作完成時,就會呼叫這個方法。
//
//<param name="e">與完成的接收操作相關的SocketAsyncEventArg</param>
void SendReceive_Completed(object sender, SocketAsyncEventArgs e)
{
//確定哪種型別的操作剛剛完成并呼叫相關的處理程式。
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive。
ProcessReceive(e)。
break;
case SocketAsyncOperation.Send。
ProcessSend(e)。
break;
default:
throw new ArgumentException("套接字上最后完成的操作不是接收或發送")。
}
}
//此方法在異步接收操作完成時被呼叫。
//如果遠程主機關閉了連接,那么套接字就會被關閉。
//
private void ProcessReceive(SocketAsyncEventArgs e)
{
//檢查遠程主機是否關閉連接。
SocketAsyncUserToken token = (SocketAsyncUserToken)e.UserToken。
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
//增加服務器收到的總位元組數。
Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred) 。
Buffer.BlockCopy(e.Buffer, e.Offset, token.ReceiveBuffer, token.TotalBytes, e.BytesTransferred) 。
byte[] raw = new byte[e.BytesTransferred] 。
Array.Copy(e.Buffer, e.Offset, raw, 0, e.BytesTransferred)。
string raw_bytes = BitConverter.ToString(raw)。
token.TotalBytes = e.BytesTransferred;
token.LastUpdate = DateTime.Now;
DataServerEventArgs args = null;
args = new DataServerEventArgs()。
args.EventType = DataServerEventTypes.DataReceived;
args.Details = "Raw Bytes:
" raw_bytes "
服務器在套接字" token.TotalBytes "位元組數" token.Socket.Handle.ToInt32() "。
OnEventNotify(args);
bool eot = false。
int index = 0;
for (int i = e.Offset; i < e.Offset e.BytesTransferred; i )
{
if (e.Buffer[i] == 4)
{
//跳過eot字符。
index ;
eot = true;
break。
}
index ;
}
//檢查傳輸結束(EOT)的位元組。
if (eot)
{
string messageStr = null;
string messageType = null;
messageStr = System.Text.Encoding.ASCII.GetString(token.ReceiveBuffer, 0, token.TotalBytes) 。
token.DeviceId = messageStr.Substring(0, 3)。
messageType = messageStr.Substring(3, 3) 。
int count = 0;
//生成回應; //生成回應。
count = Message.Response(token);
token.TotalBytes = 0;
//發送回應。
e.SetBuffer(token.SendBuffer, 0, count)。
args = new DataServerEventArgs()。
args.EventType = DataServerEventTypes.DataSent;
args.DeviceId = token.DeviceId;
args.Details = "發送回應的" count " bytes for " token.DeviceId " on socket " token.Socket.Handle.ToInt32() "。
OnEventNotify(args);
bool willRaiseEvent = token.Socket.SendAsync(e)。
if (!willRaiseEvent)
{
ProcessSend(e);
}
}
else(!willRaiseEvent) { ProcessSend(e); }
{
bool willRaiseEvent = token.Socket.ReceiveAsync(e)。
if (!willRaiseEvent)
{
ProcessReceive(e);
}
}
}
else if (token.Socket.Available > 0)
{
//為什么資料沒有被接收到?
var args = new DataServerEventArgs() 。
args.EventType = DataServerEventTypes.Error;
args.Details = "未收到資料,但在套接字"/span> token.Socket.Handle.ToInt32() ",已傳輸。" e.BytesTransferred ", Available: " token.Socket.Available ", SocketError: " e.SocketError "。"。
OnEventNotify(args);
//這將導致堆疊溢位,因為ByteTransferred始終為0。
/*
bool willRaiseEvent = token.Socket.ReceiveAsync(e)。
如果(!willRaiseEvent)
{
ProcessReceive(e);
*/
}
// SocketError.Success and e.BytesTransferred == 0 means client disconnected
else
{
var args = new DataServerEventArgs();
args.EventType = DataServerEventTypes.ConnectionClosed;
args.Details = "Closing socket " token.Socket.Handle.ToInt32() ", " e.SocketError "。
OnEventNotify(args);
CloseClientSocket(e)。
}
}
//此方法在異步發送操作完成時被呼叫。
//該方法在套接字上發出另一個接收,以讀取任何額外的。
//從客戶端發送的資料。
//
//<param name="e"></param>
private void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
SocketAsyncUserToken token = (SocketAsyncUserToken)e.UserToken。
//讀取從客戶端發送的下一個資料塊。
bool willRaiseEvent = token.Socket.ReceiveAsync(e)。
if (!willRaiseEvent)
{
ProcessReceive(e);
}
}
else(!willRaiseEvent) { ProcessReceive(e); }
{
CloseClientSocket(e);
}
}
private void CloseClientSocket(SocketAsyncEventArgs e)
{
SocketAsyncUserToken token = e.UserToken as SocketAsyncUserToken;
//關閉與客戶端相關的套接字。
try
{
if (token.Closed)
{
return;
}
else; }
{
token.Closed = true;
}
token.Socket.Shutdown(SocketShutdown.Send)。
DataServerEventArgs args1 = new DataServerEventArgs();
args1.EventType = DataServerEventTypes.ConnectionClosed;
args1.Details = "服務器已經關閉了套接字" token.Socket.Handle.ToInt32() " 成功。
OnEventNotify(args1);
}
//throws if client process has already closed new DataServerEventArgs();
args2.EventType = DataServerEventTypes.ConnectionClosed;
args2.Details = "套接字" token.Socket.Handle.ToInt32() " 已經關閉了。"。
OnEventNotify(args2);
return;
}
token.Socket.Close()。
//Decrement the counter keeping track of the total number of clients connected to the server.
Interlocked.Decrement(ref m_numConnectedSockets)。
m_maxNumberAcceptedClients.Release()。
DataServerEventArgs args = new DataServerEventArgs();
args.EventType = DataServerEventTypes.ConnectionClosed;
args.Details = "有" m_numConnectedSockets " 客戶端連接。"。
OnEventNotify(args);
m_AcceptedConnections.Remove(e)。
//釋放SocketAsyncEventArg,以便它們可以被另一個客戶端重新使用。
m_readWritePool.Push(e)。
}
}
對于為什么會出現這個問題,有什么想法嗎?只要只有一個來回的傳輸,一切都很正常。當同一連接上有第二次交換時,就會出現問題。
編輯:添加了記錄的輸出來證明這個問題:
。服務器正在啟動在埠15027。
服務器已經接受了一個連接在套接字1068.。
有1個客戶連接。
原始位元組。
7E-39-3901-02-31-03- 30-35-2-03-2-5B-30-39-2F-31-34-2F-32- 31-20-31- 36-3A-33-37-3A-32-38-20-33-5D- 20-44-65-76-69-63-65- 20-73-74- 61-72-74-75-70-2E-20-5B-30-5 D-03-04
服務器總共讀取了55位元組在套接字1068。
收到來自 ~99 on socket 1068. 的通知型別的訊息。
訊息處理與回應。DRX,09/14/21 16:37。 28 3 for ~99 on socket1068.
發送28位元組的回應for ~99 on socket 1068。
關閉插座1068,成功。
服務器已經成功關閉了套接字1068。
有0個客戶連接。
服務器已經接受了一個連接在套接字1092.。
有1個客戶連接。
原始位元組。
7E-39-39-03-04
服務器總共讀取了5位元組在套接字1092。
收到來自 ~99 on socket 1092. 的ControlRequest型別的訊息。
訊息處理與回應。1, D1L for ~99 on socket 1092.
發送7位元組的回應 for ~99 on socket 1092.
原始位元組。
44314C-04
服務器總共讀取了4位元組在套接字1092。
發送0位元組的回應 for ~99 on socket 1092.
關閉插座1092,成功。
服務器已經成功關閉了套接字1092。
有0個客戶連接。
服務器已經接受了一個連接在套接字2140.。
有1個客戶連接。
資料沒有轉移,但是可用的在套接字2140,已轉移。0, Available: 61, SocketError: 成功。
服務器已經接受了一個連接在套接字2244.。
有2個客戶連接。
原始位元組。
7E-39-39-03-04
服務器總共讀取了5位元組在套接字2244。
收到來自 ~99 on socket 2244. 的ControlRequest型別的訊息。
訊息處理與回應。1, D1L for ~99 on socket 2244.
發送7位元組的回應 for ~99 on 插座 2244.
原始位元組。
44314C-04
服務器總共讀取了4位元組在套接字2244。
發送0位元組的回應 for ~99 on socket 2244.
關閉插座2244,成功。
服務器已經成功關閉了套接字2244。
有1個客戶連接。
服務器已經接受了一個連接在套接字2248.。
有2個客戶連接。
資料沒有轉移,但是可用的在套接字2248,已轉移。0, Available: 5, SocketError: 成功。
服務器已經接受了一個連接在套接字2256.。
有3個客戶連接。
原始位元組。
7E-39-3902-05-06-02- 30-2E-30-30-30-35-35-35- 03-1C-02-30-2E-30-30-30-30-30-30- 03-13-02-31-2E-30-30-30- 30-30-30-03-1F-02-30-2E- 30-30-30-30-30-30-03- 44-02-2D-31-30-30-2E-30-30-03-04
服務器總共讀取了61位元組在套接字2256。
收到DataSample型別的訊息 來自 ~99 on socket 2256.
訊息處理與回應。DRX, Acknowledged for ~99 on socket2256.
發送21位元組的回應 for ~99 on socket 2256.
關閉插座2256,成功。
服務器已經成功關閉了套接字2256。
有2個客戶連接。
服務器已經強制關閉了套接字2140,因為超時了。
服務器已經成功關閉了套接字2140。
有1個客戶連接。
服務器已經接受了一個連接在套接字2140.上。
有2個客戶連接。
資料沒有轉移,但是可用的在套接字2140,已轉移。0, Available: 5, SocketError: 成功。
uj5u.com熱心網友回復:
看起來你在做一個零位元組的接收--這在某些情況下對檢測資料何時可用很有用,而不需要為接收保留緩沖區。
int count = 0;
//...
e.SetBuffer(token.SendBuffer, 0, count) 。
使最后一個引數超過0--如果這是發送:確保你發送你打算發送的東西,如果這是用于接收:它需要是積極的,以實際接收資料(而不是僅僅檢測資料的存在)。如果你在連續的發送和接收中使用相同的引數:請確保你在每一次操作之前適當地設定緩沖區的長度。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/311823.html
標籤:
上一篇:如何在Excel中創建復選框
