Reputation: 43
I try to make small changes in this ms example I want to do "chat server" from this "echo server" example
So...
I add some blocks in class Server and this work fine but ... just between two telnet connection, when I add more than two clients, I get error "An asynchronous socket operation is already in progress using this SocketAsyncEventArgs instance.";
Can you tell me where is error ? Thanks a lot.
This is a my server class:
namespace AsyncSocketSample
{
/// <summary>
/// Implements the connection logic for the socket server. After accepting a connection, all data read
/// from the client is sent back to the client. The read and echo back to the client pattern is continued
/// until the client disconnects.
/// </summary>
class Server
{
private int m_numConnections; // the maximum number of connections the sample is designed to handle simultaneously
private int m_receiveBufferSize;// buffer size to use for each socket I/O operation
BufferManager m_bufferManager; // represents a large reusable set of buffers for all socket operations
const int opsToPreAlloc = 2; // read, write (don't alloc buffer space for accepts)
Socket listenSocket; // the socket used to listen for incoming connection requests
// pool of reusable SocketAsyncEventArgs objects for write, read and accept socket operations
SocketAsyncEventArgsPool m_readWritePool;
int m_totalBytesRead; // counter of the total # bytes received by the server
int m_numConnectedSockets; // the total number of clients connected to the server
Semaphore m_maxNumberAcceptedClients;
//The ClientInfo structure holds the required information about every
//client connected to the server
struct ClientInfo
{
public Socket socket; //Socket of the client
public AsyncUserToken token; // User Token
//public string strName; //Name by which the user logged into the chat room
}
//The collection of all clients logged into the room (an array of type ClientInfo)
ArrayList clientList = new ArrayList();
/// <summary>
/// Create an uninitialized server instance. To start the server listening for connection requests
/// call the Init method followed by Start method
/// </summary>
/// <param name="numConnections">the maximum number of connections the sample is designed to handle simultaneously</param>
/// <param name="receiveBufferSize">buffer size to use for each socket I/O operation</param>
public Server(int numConnections, int receiveBufferSize)
{
m_totalBytesRead = 0;
m_numConnectedSockets = 0;
m_numConnections = numConnections;
m_receiveBufferSize = receiveBufferSize;
// allocate buffers such that the maximum number of sockets can have one outstanding read and
//write posted to the socket simultaneously
m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToPreAlloc,
receiveBufferSize);
m_readWritePool = new SocketAsyncEventArgsPool(numConnections);
m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);
}
/// <summary>
/// Initializes the server by preallocating reusable buffers and context objects. These objects do not
/// need to be preallocated or reused, by is done this way to illustrate how the API can easily be used
/// to create reusable objects to increase server performance.
/// </summary>
public void Init()
{
// Allocates one large byte buffer which all I/O operations use a piece of. This gaurds
// against memory fragmentation
m_bufferManager.InitBuffer();
// preallocate pool of SocketAsyncEventArgs objects
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>(IO_Completed);
readWriteEventArg.UserToken = new AsyncUserToken();
// assign a byte buffer from the buffer pool to the SocketAsyncEventArg object
m_bufferManager.SetBuffer(readWriteEventArg);
// add SocketAsyncEventArg to the pool
m_readWritePool.Push(readWriteEventArg);
}
}
/// <summary>
/// Starts the server such that it is listening for incoming connection requests.
/// </summary>
/// <param name="localEndPoint">The endpoint which the server will listening for conenction requests on</param>
public void Start(IPEndPoint localEndPoint)
{
// create the socket which listens for incoming connections
listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(localEndPoint);
// start the server with a listen backlog of 100 connections
listenSocket.Listen(100);
// post accepts on the listening socket
StartAccept(null);
//Console.WriteLine("{0} connected sockets with one outstanding receive posted to each....press any key", m_outstandingReadCount);
Console.WriteLine("Press any key to terminate the server process....");
Console.ReadKey();
}
/// <summary>
/// Begins an operation to accept a connection request from the client
/// </summary>
/// <param name="acceptEventArg">The context object to use when issuing the accept operation on the
/// server's listening socket</param>
public void StartAccept(SocketAsyncEventArgs acceptEventArg)
{
if (acceptEventArg == null)
{
acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
}
else
{
// socket must be cleared since the context object is being reused
acceptEventArg.AcceptSocket = null;
}
m_maxNumberAcceptedClients.WaitOne();
bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
if (!willRaiseEvent)
{
ProcessAccept(acceptEventArg);
}
}
/// <summary>
/// This method is the callback method associated with Socket.AcceptAsync operations and is invoked
/// when an accept operation is complete
/// </summary>
void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
{
ProcessAccept(e);
}
private void ProcessAccept(SocketAsyncEventArgs e)
{
Interlocked.Increment(ref m_numConnectedSockets);
Console.WriteLine("Client connection accepted. There are {0} clients connected to the server",
m_numConnectedSockets);
// Get the socket for the accepted client connection and put it into the
//ReadEventArg object user token
SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();
((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket;
// As soon as the client is connected, post a receive to the connection
bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs);
if(!willRaiseEvent){
ProcessReceive(readEventArgs);
}
ClientInfo clientInfo = new ClientInfo();
clientInfo.socket = ((AsyncUserToken)readEventArgs.UserToken).Socket;
clientInfo.token = (AsyncUserToken)readEventArgs.UserToken;
clientList.Add(clientInfo);
// Accept the next connection request
StartAccept(e);
}
/// <summary>
/// This method is called whenever a receive or send opreation is completed on a socket
/// </summary>
/// <param name="e">SocketAsyncEventArg associated with the completed receive operation</param>
void IO_Completed(object sender, SocketAsyncEventArgs e)
{
// determine which type of operation just completed and call the associated handler
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
ProcessSend(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
}
/// <summary>
/// This method is invoked when an asycnhronous receive operation completes. If the
/// remote host closed the connection, then the socket is closed. If data was received then
/// the data is echoed back to the client.
/// </summary>
private void ProcessReceive(SocketAsyncEventArgs e)
{
// check if the remote host closed the connection
AsyncUserToken token = (AsyncUserToken)e.UserToken;
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
//increment the count of the total bytes receive by the server
Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred);
Console.WriteLine("The server has read a total of {0} bytes", m_totalBytesRead);
///////////////////////////////////////////////////////////////////////////////////////////////////
try
{
foreach (ClientInfo clientInfo in clientList)
{
if (clientInfo.token != token)
{
//Data received send to the all client but not for sender
bool willRaiseEvent = clientInfo.token.Socket.SendAsync(e); // token.Socket.SendAsync(e);
if (!willRaiseEvent)
{
ProcessSend(e);
}
}
}
}
catch (Exception x)
{
Console.WriteLine(x.Message.ToString());
Console.ReadKey();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
}
else
{
CloseClientSocket(e);
}
}
/// <summary>
/// This method is invoked when an asynchronous send operation completes. The method issues another receive
/// on the socket to read any additional data sent from the client
/// </summary>
/// <param name="e"></param>
private void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
// done echoing data back to the client
AsyncUserToken token = (AsyncUserToken)e.UserToken;
// read the next block of data send from the client
bool willRaiseEvent = token.Socket.ReceiveAsync(e);
if (!willRaiseEvent)
{
ProcessReceive(e);
}
}
else
{
CloseClientSocket(e);
}
}
private void CloseClientSocket(SocketAsyncEventArgs e)
{
AsyncUserToken token = e.UserToken as AsyncUserToken;
// close the socket associated with the client
try
{
token.Socket.Shutdown(SocketShutdown.Send);
}
// throws if client process has already closed
catch (Exception) { }
token.Socket.Close();
///////////////////////////////////////////
// Connection is terminated, either by force or
// willingly
int nIndex = 0;
foreach (ClientInfo client in clientList)
{
if (client.token == token)
{
clientList.RemoveAt(nIndex);
break;
}
++nIndex;
}
///////////////////////////////////////////
// decrement the counter keeping track of the total number of clients connected to the server
Interlocked.Decrement(ref m_numConnectedSockets);
m_maxNumberAcceptedClients.Release();
Console.WriteLine("A client has been disconnected from the server. There are {0} clients connected to the server", m_numConnectedSockets);
// Free the SocketAsyncEventArg so they can be reused by another client
m_readWritePool.Push(e);
}
}
Upvotes: 1
Views: 4632
Reputation: 1237
As Mark wrote, the obvious problem is in the ProcessReceive method, which attempts to use the same EventArgs to send data to multiple clients. As a minimum-effort solution you can allocate EventArgs each time, but you should know this is not how such apps should be written. For send operations there should be a Queue of packets (in this case, simple byte arrays) used together with a synchronization primitives like ReaderWriterLockSlim or just a ConcurrentQueue. In a send operation callback you check the queue and if it's not empty, you dequeue next packet and send it. Since, it's a duplex scenario and not request/response, receive should not begin as a reaction to sending something, instead receive should work in a loop, in the loop condition you should check whether socket is still available.
The code you have is really suitable for request/response stories. For the problem you are trying to solve, I suggest you to look at something more advanced, like this framework.
Upvotes: 0
Reputation: 1062770
Here's the most likely:
foreach (ClientInfo clientInfo in clientList)
{
if (clientInfo.token != token)
{
//Data received send to the all client but not for sender
bool willRaiseEvent = clientInfo.token.Socket.SendAsync(e); // token.Socket.SendAsync(e);
if (!willRaiseEvent)
{
ProcessSend(e);
}
}
}
You are using the same e
to send async messages to multiple clients. That is doomed. Each operation needs a different args. This API does allow for pooling args to re-use them, but first I would say: get it working with a new arg each time. Then optimize it.
Also note that your ProcessReceive
should probably be starting the next async-receive, otherwise they can only send one message. You need to watch out too, because you currently have send starting a receive; if you send 3 messages to a socket who is just listening, things are going to get very confused. Personally I would have completely separate receive and send handling. Send just sends; receive just receives.
Upvotes: 1