The following source was built using Visual Studio 6.0 SP5 and Visual Studio .Net. You need to have a version of the Microsoft Platform SDK installed
Overview
When a server has to deal with lots of short lived client connections it's advisable to use the Microsoft extension function for WinSock, AcceptEx(), to accept connections. Creating a socket is a relatively "expensive" operation and by using AcceptEx() you can create the socket before the connection occurs rather than as it occurs, thus speeding the establishment of the connection. What's more, AcceptEx() can perform an initial data read at the same time as doing the connection establishment which means you can accept a connection and retrieve data with a single call.
In this article we develop a socket server class that uses AcceptEx() and the related Microsoft extension functions. The resulting server class is similar to the one we developed in the previous article in that it does all of the hard work for you and provides a simple way to develop powerful and scalable socket servers.
Documentation bug, or undocumented behavior?
The documentation for AcceptEx() states:
"When this operation is successfully completed, sAcceptHandle can be passed, but to the following functions only:
ReadFile
WriteFile
send
recv
TransmitFile
closesocket"
Notice that WSARecv and WSASend are conspicuous by their absence, and so it DisconnectEx. This article assumes that this is due to a documentation bug and that AcceptEx is intended to operate with these functions. Either way, we're into undocumented behaviour, so if that's important to you then you may not wish to do things this way. What we've found is that it works on the platforms that we need it to work on.
Using Microsoft extension functions with WinSock
The Windows Sockets 2 specification defines an extension mechanism that allows Windows Sockets service providers to expose advanced transport functionality to application programmers. Microsoft provides several of these extension functions but by using them you are limiting your software to running on a Windows Sockets provider that supports these functions. Generally this isn't a problem...
Several of the extension functions have been available since WinSock 1.1 and are exported from MSWsock.dll, however it's not advisable to link directly to this dll as this ties you to the Microsoft WinSock provider. A provider neutral way of accessing these extension functions is to load them dynamically via WSAIoctl using the SIO_GET_EXTENSION_FUNCTION_POINTER op code. This should, theoretically, allow you to access these functions from any provider that supports them...
With WindowsXP Microsoft has added several new WinSock extension functions and these are only available via the WSAIoctl route so, in the interest of consistency and portability we'll access all of the extension functions using a simple wrapper class, CMSWinSock, which wraps the required calls to WSAIoctl.
AcceptEx() can reuse sockets that have been prepared for reuse in the appropriate way. WindowsXP provides DisconnectEX() as a way to prepare a socket handle for reuse. Prior to WindowsXP the only function that could prepare a socket for reuse was TransmitFile(). Fortunately, TransmitFile() could be used to prepare a socket for reuse without actually having to transmit a file... To make the code easier to understand CMSWinSock provides a function to disconnect a socket for reuse. DisconnectSocketForReuse() will call DisconnectEx() if it's available and otherwise call TransmitFile() with the appropriate arguments to simply reuse the socket.
Accepting connections with AcceptEx()
Our previous servers have used a blocking loop on WSAAccept() to accept connections. When a connection occurs, a socket is created and returned from the call to WSAAccept(), our accepting thread then loops around to call WSAAccept() again for the next connection. Not only is creating a socket a time consuming operation but the design means that all connection establishment must go through a single piece of code in a single thread. AcceptEx() uses a different model, you create your sockets first, then "post" accept requests onto the listening socket and when these requests complete you receieve IO completion packets on the associated IO Completion Port. I've no idea how this works under the hood, but at the very least, the use of an IO Completion Port for notification allows us to multi thread the work that we need to do in relation to establishing a connection.
So, how many sockets do we need to create in advance and how do we know when we need to create more? The number of sockets that you need to create in advance will depend on the number of connections that your server has to handle and as such is a configurable parameter. You don't want to create too many sockets as this wastes server resources, but if you create too few then your server will run slower, or refuse connections... We keep a track of the number of sockets that we have created and as accepts complete we move the sockets from a "pending accept" list onto an "active" list. In this way we can monitor when we need to create more sockets and issue more calls to AcceptEx(). However, if we are expecting a client to immediately send data after it connects then the accept doesn't complete until at least one byte arrives on the connection. A malicious client could thus attempt a denial of service attack on our server by opening connections and not sending any data. This would eventually use up all of the accepts we have posted and cause our server to start to use the listen backlog queue. Eventually the server will fill the listen backlog queue and begin to reject connections attempts. To avoid this situation we can register for notification when a connection attempt occurs and there are no outstanding accepts available. When this happens the backlog queue will have queued the connection request and we can post more calls to AcceptEx() so that the connection will be accepted. We use WSAEventSelect() to register for FD_ACCEPT events - these are reported by an event being set. We can then structure our accept loop something like this:
WSAEventSelect(m_listeningSocket, m_acceptEvent.GetEvent(), FD_ACCEPT);
do
{
for (size_t i = 0; i < numAcceptsToPost; ++i)
{
Accept();
}
m_postMoreAcceptsEvent.Reset();
m_acceptEvent.Reset();
HANDLE handlesToWaitFor[2];
handlesToWaitFor[0] = m_postMoreAcceptsEvent.GetEvent();
handlesToWaitFor[1] = m_acceptEvent.GetEvent();
waitResult = ::WaitForMultipleObjects(2, handlesToWaitFor, false, INFINITE);
if (waitResult != WAIT_OBJECT_0 &&
waitResult != WAIT_OBJECT_0 + 1)
{
OnError(_T("CSocketServerEx::Run() - WaitForMultipleObjects: ")
+ GetLastErrorMessage(::GetLastError()));
}
if (waitResult == WAIT_OBJECT_0 + 1)
{
Output(_T("Accept..."));
}
}
while (waitResult == WAIT_OBJECT_0 || waitResult == WAIT_OBJECT_0 + 1);
We've only moved the denial of service attack from causing our server to refuse connections to causing our server to run out of resources by accepting an infinite number of malicious connections. To address this problem we need to be able to determine if a socket that is pending an accept completion has had the connection established and is now waiting for data to arrive, and if it is, how long it's been waiting... For this we use getsockopt() with the SO_CONNECT_TIME option (available from Windows NT 4.0) . This returns -1 if the socket is not connected or the number of seconds that it has been connected. If, when we are informed that we need to post more accepts, we post the accepts and then check all of the pending accepts to see how long they have been connected and waiting for data then we can forcibly disconnect sockets that are "taking too long" (a configurable parameter) to send data after connecting...
We now have a server that will post a configurable number of accepts when it first starts listening and, in normal operation, will post more accepts as connections complete. If we get to a point where we have no accepts pending and a connection occurs then we are informed so that we can post more accepts and check to see if any connected sockets have been waiting for data for longer than our configurable timeout.
Accepting and reading data
When calling AcceptEx() you must always pass a buffer to store the local and remote addresses of the resulting connection. For servers that receive data before they send data, such as web servers, for example, you can include space in this buffer for the first batch of data that is read from the connection. As we pointed out above, the accept doesn't complete until at least one byte arrives. The code could look something like this:
void CSocketServerEx::Accept()
{
Socket *pSocket = AllocateSocket();
{
CCriticalSection::Owner lock(m_listManipulationSection);
m_pendingList.PushNode(pSocket);
}
CIOBuffer *pBuffer = Allocate();
pBuffer->SetOperation(IO_Accept_Completed);
pBuffer->SetUserPtr(pSocket);
const size_t sizeOfAddress = GetAddressSize() + 16;
const size_t sizeOfAddresses = 2 * sizeOfAddress;
DWORD bytesReceived = 0;
if (!CMSWinSock::AcceptEx(
m_listeningSocket,
pSocket->m_socket,
reinterpret_cast<void*>(const_cast<BYTE*>(pBuffer->GetBuffer())),
pBuffer->GetSize() - sizeOfAddresses,
sizeOfAddress,
sizeOfAddress,
&bytesReceived,
pBuffer->GetAsOverlapped()))
{
const DWORD lastError = ::WSAGetLastError();
if (ERROR_IO_PENDING != lastError)
{
Output(_T("CSocketServerEx::Accept() - AcceptEx: ")
+ GetLastErrorMessage(lastError));
pSocket->Close();
pSocket->Release();
pBuffer->Release();
}
}
else
{
m_iocp.PostStatus((ULONG_PTR)m_listeningSocket, bytesReceived,
pBuffer->GetAsOverlapped());
}
}
Note that we call up to our derived class to provide details of the size of the sockaddr that we need to make space for, though a default implementation simply returns sizeof(SOCKADDR_IN). When the accept completes the socket retrieved from the completion key by our worker thread is the listening socket, since that's the device that's associated with the IO Completion Port and generating the completion packet for the accept. We require the accepted socket as well, so we store that in the IO buffer's user data slot. It's a bit crufty in the worker thread as for all other completion packets the completion key is a pointer to a Socket, but for accepts it's not - a special case that may get refactored away if I get the time... Note that although the expected code path is for AcceptEx() to return false and for WSAGetLastError() to return ERROR_IO_PENDING, we handle the case where the accept completes synchronously by posting to the completion port ourselves, and we do so with the same semantics as the asynchronously generated packet (ie listening socket as completion key). I've never actually been able to get my test harness to generate this situation...
Accepting without reading data
For server's that don't receieve data before sending we can still use AcceptEx(), we just specify a data buffer size of 0 and no read occurs, the accept completes as soon as the connection is established.
Accept completion
When an accept completes and, if appropriate, data arrives, an IO completion packet is posted and our worker threads complete the accept by setting the socket options on accepted socket to match those of the listening socket (normally WSAAccept() would do this for us, but AcceptEx() makes us do it ourselves using setsockopt() and SO_UPDATE_ACCEPT_CONTEXT). We then move the socket from our pending list to our active list, extract the local and remote addresses from the data buffer that we passed to AcceptEx() and notify the derived class of a new connection and, if appropriate, new data.
The derived class interface
The derived class is almost as straight forward as the one in the previous article except that you can override the creation of the accepted socket and you can override the amount of space you need to reserve for the local and remote addresses (in case you're using a protocol other than TCP/IP).
The example...
The example server is another simple echo server, I know what I said about echo servers, but in this case it's an ok example :). The server contains two instances of the socket server class and listens on 5001 and 5002. On 5001 it performs an accept that requires data to arrive before it will complete and on 5002 it performs an accept that returns straight after the connection is established. Note that the server shows how you can package multiple socket servers in the same executable (perhaps one day I'll optimise the class so that all servers are handled by a single pool of IO threads...).
To test the server, telnet to localhost 5001/2 and type some data. If you telnet to 5001 a few times and dont type any data then you should be able to see the FD_ACCEPT notification and connection timeout checking in operation. As always, the ServerShutdown program lets you pause, resume and shutdown the server.
Revision history
- 3rd Jun 2002 - Initial revision.
- 26th June 2002 - Removed call to
ReuseAddress() during the creation of the listening socket as it not required - Thanks to Alun Jones for pointing this out to me.
- 28th June 2002 - Adjusted how we handle socket closure. We now issue async disconnects.
- 30th June 2002 - Removed the requirement for users to subclass the socket server's worker thread class. All of the work can now be done by simply subclassing the socket server class.
- 15th July 2002 - Socket closure notifications now occur when the server shuts down whilst there are active connections.
SocketServer can now be set to ensure read and write packet sequences
- 19th July 2002 - Merged with latest Socket Server code - still need to do the refactoring job to remove the duplication. Tweaked the
AcceptEx repost logic so that the server runs 'smoother'. Updated the article to indicate the undocumented nature of the example code.
|
|
 |
 | [Message Deleted] it.ragester | 22:45 2 Apr '09 |
|
|
 |
 | Question on WinAPI ConnectEx Schiener | 2:29 12 Dec '08 |
|
 |
Hi Len,
I'm programming an IOCP-Peer, which meens that the peer acts as the server as well as the client at the same time. I have reimplemented lots of your excellent code. Everything works well. Now I try to use AcceptEx which does'nt make any problems. But what I can't get running is to replace ::connect(...) with ::ConnectEx(...).
My function looks like this:
void XFSocket::Connect(LPCTSTR lpszIPAddress, UINT nPort) { XFSocketAddress sa(lpszIPAddress, nPort); if ((m_hSocket = ::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) XFThrowWin32Exception(_T("XFSocket::Connect() - ::WSASocket()"));
int nReuseAddr = 1; if (::setsockopt(m_hSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&nReuseAddr, sizeof(nReuseAddr)) == SOCKET_ERROR) XFThrowWin32Exception(_T("XFSocket::Connect() - ::setsockopt()"));
int nSendBufferSize = 0;
if (::setsockopt(m_hSocket, SOL_SOCKET, SO_SNDBUF, (char*)&nSendBufferSize, sizeof(nSendBufferSize)) == SOCKET_ERROR) XFThrowWin32Exception(_T("XFSocket::Connect() - ::setsockopt()")); if (::bind(m_hSocket, (LPSOCKADDR)&sa, sizeof(struct sockaddr)) == SOCKET_ERROR) XFThrowWin32Exception(_T("XFSocket::Connect() - ::bind()"));
m_pEndPoint->m_pIOWorkerCP->AssociateDevice(reinterpret_cast<HANDLE>(m_hSocket), (ULONG_PTR)this);
XFBuffer* pBuffer = m_pEndPoint->m_pBufferPool->AllocateBuffer(this); pBuffer->SetIOActivity(XFIOActivity::ConnectCompleted); if (!XFWinSockEx::ConnectEx(m_hSocket, (LPSOCKADDR)&sa, sizeof(sa), NULL, 0, NULL, pBuffer)) { DWORD dwLastError = ::WSAGetLastError();
if (dwLastError != ERROR_IO_PENDING) { ProcessError(dwLastError);
Release(); pBuffer->Release(); } } else { m_pEndPoint->m_pIOWorkerCP->PostQueuedCompletionStatus((ULONG_PTR)this, 0, pBuffer); } }
In my IOWorkerThread I correctly get called:
void XFSocket::OnConnectCompleted(XFBuffer* pBuffer, DWORD dwBytesTransferred, DWORD dwLastError) { if (::setsockopt(m_hSocket, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0) == SOCKET_ERROR) XFMessageService::CreateWin32Error(_T("XFSocket::OnConnectCompleted() - ::setsockopt()"));
ResolveIPAddressAndPort();
ReceiveBuffer();
m_pEndPoint->OnConnectSocket(this); }
But dwLastError is always WSACONNREFUSED. OnConnectCompleted is called immediatly and it seems, that it is a firewall problem or so.
Do you have any idea?
With kind regards Ernst Schiener
|
|
|
|
 |
|
 |
Does a normal, sync connect work to the same address? If so then it's not a firewall or external issue.
However, your problem is that you're binding the socket to the wrong address. You bind it to the local address not the remote address. In my code I tend to bind to the address type's wildcard address (ie connect via any interface). I assume you could be more specific and bind to a particular interface's local address to ensure that you connect via that interface.
Len
|
|
|
|
 |
|
 |
A normal ::Connect() using the same local address works fine:
void XFSocket::Connect(LPCTSTR lpszIPAddress, UINT nPort) { SOCKADDR_IN saClient;
saClient.sin_family = AF_INET; saClient.sin_addr.s_addr = inet_addr(lpszIPAddress); saClient.sin_port = ::htons(nPort);
if (::connect(m_hSocket, (LPSOCKADDR)&saClient, sizeof(struct sockaddr)) == SOCKET_ERROR) XFMessageService::CreateWin32Error(_T("XFSocket::Connect() - ::connect"));
ResolveIPAddressAndPort(); }
The server-part is listening e.g. on "127.0.0.1" port 1000.
I can establish any numer of connected socket using this IPAddress. I do not realy understand what you mean with to connect via an interface.
|
|
|
|
 |
|
 |
A normal ::Connect() using the same local address works fine:
void XFSocket::Connect(LPCTSTR lpszIPAddress, UINT nPort) { SOCKADDR_IN saClient;
saClient.sin_family = AF_INET; saClient.sin_addr.s_addr = inet_addr(lpszIPAddress); saClient.sin_port = ::htons(nPort);
if (::connect(m_hSocket, (LPSOCKADDR)&saClient, sizeof(struct sockaddr)) == SOCKET_ERROR) XFMessageService::CreateWin32Error(_T("XFSocket::Connect() - ::connect"));
ResolveIPAddressAndPort(); }
The server-part is listening e.g. on "127.0.0.1" port 1000.
I can establish any numer of connected socket using this IPAddress. I do not realy understand what you mean with to connect via an interface.
|
|
|
|
 |
|
 |
As I said, you're binding the socket to the wrong address. Use INADDR_ANY as the address that you bind to and it will work. If you have multiple network cards in the machine and you want to bind to a specific LOCAL address then you should replace INADDR_ANY with the ip address of the local network card that you want the LOCAL end of the connection to be bound to.
|
|
|
|
 |
|
 |
I had meet the same problem !but i had resolved it ,now it work!
msdn give a detail:
If the TransmitFile function is called on a previously connected socket with both TF_DISCONNECT and TF_REUSE_SOCKET flags, the specified socket is returned to a state in which it is not connected, but still bound. In such cases, the handle of the socket can be passed to the ConnectEx function in its s parameter, but the socket cannot be reused in an AcceptEx function call. Similarly, the accepted socket reused using the TransmitFile function cannot be used in a call to ConnectEx. Note that in the case of a reused socket, ConnectEx is subject to the behavior of the underlying transport. For example, a TCP socket may be subject to the TCP TIME_WAIT state, causing the ConnectEx call to be delayed
it's all!
modified on Sunday, April 19, 2009 10:36 PM
|
|
|
|
 |
 | Wrong usage AcceptEx Alezis | 7:47 5 Apr '08 |
|
 |
sorry for my bad english. When AcceptEx function completed successfully and a value of TRUE is returned, AcceptEx also post to the completion port message. Need :
if (!CMSWinSock::AcceptEx( m_listeningSocket, pSocket->m_socket, reinterpret_cast(const_cast(pBuffer->GetBuffer())), bufferSize, sizeOfAddress, sizeOfAddress, &bytesReceived, pBuffer)) { const DWORD lastError = ::WSAGetLastError();
if (ERROR_IO_PENDING != lastError) { Output(_T("CSocketServerEx::Accept() - AcceptEx: ") + GetLastErrorMessage(lastError));
pSocket->Release(); pBuffer->Release(); } }
/* // this code waste else { // Accept completed synchronously. We need to marshal the data recieved over to the // worker thread ourselves...
m_iocp.PostStatus((ULONG_PTR)m_listeningSocket, bytesReceived, pBuffer); } */
|
|
|
|
 |
|
|
 |
 | why doesnt popNode? minkun | 0:22 15 Oct '07 |
|
 |
hi.. in CheckPendingConnections(), may be some clients timeouted is CancelAccept()ed. i understand that.
my first question is that these clients CancelAccept()ed is in m_pendingList yet. why it doesnt these clients do re-AcceptEx()? ...or socket->popNode()?
second question could you more explain with these codes?
in CSocketServerEx::Run()...
acceptsToPost = acceptsToPost + 10; <-- why add 10 to acceptsPost?
if (m_acceptsPending < acceptsToPost) <-- what mean this code? { acceptsToPost = m_acceptsToPost; }
anyone help me.. i'm poor english man...
|
|
|
|
 |
|
 |
As far as I remember, cancelling the accept (which is what happens if the accept has timedout) causes the socket to be closed which causes the pending AcceptEx to complete with an error which cleans up the socket from the list.
The + 10 is 'post 10 more accepts' and it's triggered when the post more accepts event is set. The code then loops, posts 10 more accepts and then adjusts the counts again so that the next time around it doesnt post more...
Try stepping through the code in the debugger.
|
|
|
|
 |
 | Dialog or MFC BeerFizz | 13:32 13 Nov '06 |
|
 |
Len,
You wouldn't happen to have a version of this code which runs as a dialog application? or under MFC?
Thanks Phil
|
|
|
|
 |
|
 |
No, and there's a reason for that... Personally I always separate the GUI from the server logic, the two just don't really belong together, ever (even if they appear to at the start of the project...) It's generally best to use some form of IPC between the GUI program and the server program; you can use a socket connection, as that's easy and means you can put the GUI on another machine if you need to or you can run the server without a user logged on to the machine....
|
|
|
|
 |
|
 |
Ok, fair enough, I can understand that.
Its not so much that I want a dialog, I just expressed myself poorly.
I am using VC++2003. I compiled and executed the simpleprotocol2 and it worked well.
I then made the modifications I needed to the ThreadPoolWorkerThread such that it communicated with a PHP web client. I incorporated some retry and soft fail strategies and this all worked very well.
I restricted the business logic pool to one (1) and wrote a test client that I could invoke several copies of. That worked very nicely, queuing up multiple client requests and servicing them in turn.
So, now came the purpose of all of this, making your code the base for an intermediary server between my PHP client and yet another 'server' for which I have a MFC API.
The API is in source form, and contains several MFC constructs such as CStrings. When I attempt to compile this, I get the following error:
"error C2061:syntax error : identifier 'CString'.
I have played around with this extensively. I have tried to include the correct headers for CString and that generates a whole new set of problems.
I created a new MFC based console app and brought your code over, but there are many compilation issues there as well.
In summary, I don't want to add a GUI to your code, but I do want to incorporate large pieces of MFC code.
any ideas?
|
|
|
|
 |
|
 |
Just had a go, only get 2 errors with VC6 when I include afx.h but they're about duplicated new/delete and I dont have time to track them down...
Put all the MFC interface code in a DLL, dont expose MFC from the dll interface, link the DLL to the server.
|
|
|
|
 |
|
 |
Well, it would be fantastic if the code you have generously provided would compile and run under VC2003 and VC2005.
Any chance?
|
|
|
|
 |
|
|
 |
|
 |
The trick to linking with MFC is to adjust the order of library linkage: See: here[^] for details.
You dont need to include MFC headers in any file except the ones where you want to use MFC, so, most likely in the server main and the derived server class.
|
|
|
|
 |
 | Localhost plus... BeerFizz | 10:07 26 Oct '06 |
|
 |
Hi Len,
thanks for the code, very nice package and it makes it easier for a novice like me.
Couple of questions:
If I change the server IP address to 127.0.0.1 using:
long hostAddr = inet_addr("127.0.0.1") and replacing INADDR_ANY with hostAddr,
Then I get an exception.
Second Question:
I am writing a simple server which can only do one thing at a time and it takes about 5 seconds to do that. I will have potentially multiple clients (a small number) attempting to access the server. I would think I would want to use your protocol server 2.
When the server is processing the request from one of the clients and another client comes along, I would like the server to tell the second (or third/forth) client that it is busy and to try again in 6 seconds or so.
My question is this: How can the thread(s) which are talking to the additional clients know that the first thread/server is busy?
Thanks Phil
|
|
|
|
 |
|
 |
Phil,
The localhost issue is due to a bug...
CSocket::InternetAddress::InternetAddress( const unsigned long address, const unsigned short port) { sin_family = AF_INET; sin_port = htons(port); sin_addr.s_addr = htonl(address); }
should be:
CSocket::InternetAddress::InternetAddress( const unsigned long address, const unsigned short port) { sin_family = AF_INET; sin_port = htons(port); sin_addr.s_addr = address; }
The second question is a little more complex...
Personally I'd prefer to hold the connection open on the server and allow the client to wait (with the connection open) if it wants to or close the connection if it prefers. So, in your example, if the server is busy then it can send a message to the client saying "I'm busy" and the client can decide what to do. This gives you more control on the client and is better for resource usage and whatever as it doesnt mean that most connections will be opened and closed really quickly on a busy server...
The second question is how to tell if the server is busy and how to have the connection that is waiting retry...
This could become a complex design until you realise that the concept of the server being 'busy' for however much time is left of a '5 second delay' is no different to the server running a little slowly or the network being congested. So, I'd say skip the whole 'inform the client that the server is busy' and simply allow all connections and push a work item in the queue and have a single thread processing the work queue; so yes, the thread pool simple protocol server is a good starting point... The client can still deal with timeouts or whatever if it wants (as there may be 100,000 requests ahead of it in the queue!) but there's no special case for "currently busy"...
|
|
|
|
 |
|
 |
Thanks Len,
I appreciate your help...
Phil
|
|
|
|
 |
|
|
 |
 | Timeout and disconnect problems Zelius | 6:51 10 Oct '06 |
|
 |
This is intresting article! But still can't understand details about disconnecting timeouted clients. Is it possible to disconnect timeouted clients for reuse or they may be disconnected only by closesocket? Best regards, Konstantin Knyazev
|
|
|
|
 |
|
 |
I dont know if you can disconnect and reuse a socket that's in the middle of an accept but which hasn't had any data arrive yet... The code here uses closesocket, MSDN doesn't shed any light on the subject, I suggest you experiment!
|
|
|
|
 |
 | std::map problems Angus Comber | 5:35 1 Oct '06 |
|
 |
Hello
I am using your socket classes - the SimpleProtocolServer example code. But I want to add to the functionality so I declare a CAgents class inside CSocketServer like this:
class CAgents { public: CAgents() { m_Socket = 0; m_dwDeviceID = 0; }; // default constructor - init values to sensible defaults
CAgents(const Socket* pSocket, const DWORD DeviceID, const std::string& strLogin) { m_Socket = pSocket; m_dwDeviceID = DeviceID; m_strLogin = strLogin; } CAgents(const CAgents& rhs) :m_Socket(rhs.GetSocket()), m_dwDeviceID(rhs.GetDeviceID()), m_strLogin(rhs.GetLogin()) {}; // assign rhs = eg
const Socket* m_Socket; // ptr to socket for client DWORD m_dwDeviceID; // (DeviceID) extn std::string m_strLogin; // logon
DWORD GetDeviceID() const { return m_dwDeviceID; }
std::string GetLogin() const { return m_strLogin; }
const Socket* GetSocket() const { return m_Socket; }
CAgents& operator=(const CAgents& rhs) { m_Socket = rhs.GetSocket(); m_dwDeviceID = rhs.GetDeviceID(); m_strLogin = rhs.GetLogin(); return *this; }
};
I can create CAgents no problem at all. Eg: CAgents myagent; // default constructor - just initialises everything to basic values myagent.m_Socket = 0; myagent.m_dwDeviceID = 8; myagent.m_strLogin = "Sarah"; CAgents heragent = myagent; // operator= CAgents thisagent(0, 3, "Angus");
But when I try to use std::map I get compilation problems.
For example, // std::map<Key,Val>: std::map<int, CAgents> m_AgentsList; m_AgentsList[8] = myagent; // compile error line SocketServer.cpp(410) : error C2678: binary '[' : no operator defined which takes a left-hand operand of type 'const class std::map<int,class CSocketServer::CAgents,struct std::less<int>,class std::allocator<class CSocketServer::CAgents> >' (or there is no acceptable conversion)
I don't get these compilation errors if I setup a test program using CAgents and std::map but NOT using CSocketServer.
What is it about the SocketServer class or classes I am using that gives me this headache? How can I resolve?
Angus
|
|
|
|
 |
|
|
Last Updated 19 Jul 2002 |
Advertise |
Privacy |
Terms of Use |
Copyright ©
CodeProject, 1999-2010