Winsock, ConnectEx, shutdown, SO_UPDATE_CONNECT_CONTEXT and WSANOTCONN

| 13 Comments
I'm updating one of the pieces of sample code that we put on CodeProject a couple of years ago. A client wants a version of the COM object socket server that has been built with the latest version of our server framework and which supports outbound connections as well as inbound. The work has been going reasonably well until this afternoon when I found I had a problem shutting down the socket connections that I'd opened.

The latest version of our framework supports ConnectEx for asynchronous outbound socket connection requests. Rather than have your code wait around for a connect to complete you can have it complete asynchronously and have the framework tell you when it has completed, or failed. This works well and was a good fit for the COM object. You call Connect() on the object and when the connection completes it calls you back.

Late this afternoon the code had got to the point where I could create a noddy little proxy server in VB. This created outbound connections in response to incoming connections and routed the data from one to another. To test the code I took our echo server sample and its test harness and stuck the VB proxy in-between. The test harness connects, pumps loads of data to the echo server, reads it all back and checks that what was sent is the same as what was received. The test ran well but there was a problem with the proxy not being able to pass a half-close on to the echo server. When the test harness has finished sending all of its test data it shuts down the send side of its socket connection and I wanted the proxy to pass this half-close on to the echo server and it didn't.

It took me a while to work out what was going on. First I tinkered with the way the proxy handled the half-close. I assumed that I was doing something wrong and simply not calling shutdown() correctly, or calling it on the wrong socket, or something. Eventually I traced down from VB into the C++ and watched the socket shutdown code do its stuff. Later I actually paid attention to what it was doing and realised that the shutdown() call was actually failing, the low-level socket server framework error callback hadn't yet been routed up to VB so I wasn't seeing the message. When I finally did see the message it didn't make sense. The error I was getting from shutdown() was WSANOTCONN (10057) which was obviously wrong because the socket was clearly connected as data was flowing through it.

I spent some time assuming that somehow I'd screwed up somewhere and the socket I was passing to shutdown() had been trashed or changed or something. Eventually I was happy that it everything was as it should be and that for some reason shutdown() seemed to think that the socket was already closed.

I looked at the ConnectEx docs again and actually read them this time.

Halfway down the docs there's this paragraph:

When the ConnectEx function successfully completes, socket handle s can be passed to the following functions only:

   ReadFile 
   WriteFile 
   send or WSASend 
   recv or WSARecv 
   TransmitFile 
   closesocket

Notice that shutdown() isn't present in the above list. I've had issues with the documentation for these Winsock "Ex" functions before. When I was originally writing about AcceptEx over on CodeProject the docs on MSDN didn't include WSASend and WSARecv in the AcceptEx version of that list, they do now... However, since shutdown isn't on the list and doesn't appear to work I don't think that I can use the 'documentation bug' excuse for its lack of appearance in this instance.

The docs go on to say that:

When the ConnectEx function returns, the socket s is in the default state for a connected socket. The socket s does not enable previously set properties or options until SO_UPDATE_CONNECT_CONTEXT is set on the socket. Use the setsockopt function to set the SO_UPDATE_CONNECT_CONTEXT option.

For example:

err = setsockopt( s, 
  SOL_SOCKET, 
  SO_UPDATE_CONNECT_CONTEXT, 
  NULL, 
  0 );

Which is similar to how AcceptEx() works... It doesn't say anywhere that you must do this and most things seem to work if you don't... I decided to try adding some code to make this call once the socket connection completed. Once I did shutdown started to work as expected again...

I think it would be useful if the MSDN docs on the Microsoft Winsock extension functions was updated so that it was a little more precise about exactly what you need to do and which bits are optional and what the consequences of the optionality are.

13 Comments

Hi, Len Holgate..
I am Korean. I beginner programmer.
Sorry..My English is poor.
I saw your graceful class (A reusable, high performance, socket server class).
I saw the calss in Korean's programmer's web site.
I wish use your Excellent the class.
But, I don't know how to use the class.
Please send me sample source that show how to use the class.
My E-Mail is sandmanq@nate.com
I am waiting your message.
Bye Sir~

JinSung Choi

There are articles (in English I'm afraid) on the CodeProject web site and also on my company web site. These explain how the server framework works and provide several different example servers. Good luck.

Len

Hi Len,

I have some problem with using ConnectEx(). The function return an error code 10022. The following sample was found on-line on game forum. It simulates the problem that I have in my code. I don't know why the code gave the error on my XP Pro SP2.

Would you please comment what is wrong?

Thank you.

Val


//=======================================================
#include
#define WIN32_LEAN_AND_MEAN
#include
#include


int main() {
// App
int iRet;


// Startup WinSock
WSADATA wsaData;

printf("WSAStartup(...):\n");

iRet = WSAStartup(
MAKEWORD(2,2),
&wsaData
);
printf("iRet: %d\n", iRet);
printf("MAKEWORD(2,2): %x\n", MAKEWORD(2,2));
printf("wsaData.wVersion: %x\n", wsaData.wVersion);
printf("wsaData.wHighVersion: %x\n", wsaData.wHighVersion);
printf("wsaData.szDescription: %s\n", wsaData.szDescription);
printf("wsaData.szSystemStatus: %s\n", wsaData.szSystemStatus);


// Socket
SOCKET s;

printf("\n\nWSASocket(...):\n");

s = WSASocket(
AF_INET,
SOCK_STREAM,
IPPROTO_TCP,
NULL,
0,
WS_OVERLAPPED
);
printf("s: %d\n", s);

setsockopt( s,
SOL_SOCKET,
SO_UPDATE_CONNECT_CONTEXT,
NULL,
0 );

// Load ConnectEx
GUID GuidConnectEx = WSAID_CONNECTEX;
LPFN_CONNECTEX lpfnConnectEx = NULL;
DWORD dwBytes = 0;

printf("\n\nWSAIoctl(...) for ConnectEx:\n");

iRet = WSAIoctl(
s,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidConnectEx,
sizeof(GuidConnectEx),
&lpfnConnectEx,
sizeof(lpfnConnectEx),
&dwBytes,
NULL,
NULL
);
printf("iRet: %d\n", iRet);
printf("sizeof(GuidConnectEx): %d\n", sizeof(GuidConnectEx));
printf("lpfnConnectEx: %x\n", lpfnConnectEx);
printf("sizeof(lpfnConnectEx): %d\n", sizeof(lpfnConnectEx));
printf("dwBytes: %d\n", dwBytes);
printf("WSAGetLastError(): %d\n", WSAGetLastError());


// Try ConnectEx
SOCKADDR_IN addr;
BOOL bRet;
OVERLAPPED overlapped;

printf("\n\nlpfnConnectEx(...):\n");

memset(&addr, 0, sizeof(addr));

addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(88);

memset(&overlapped, 0, sizeof(overlapped));

dwBytes = 0;
bRet = lpfnConnectEx(
s,
(LPSOCKADDR)&addr,
sizeof(addr),
NULL,
0,
NULL,
&overlapped
);
printf("bRet: %d\n", bRet);
printf("sizeof(addr): %d\n", sizeof(addr));
printf("WSAGetLastError(): %d\n", WSAGetLastError());




// Wait
char str[255];
gets(str);

return 0;
}

Hi Len,

Thank you very much for your comments and notes relating to the error identifying.

Is it possible to bind up a particular local address? If yes, for what needs?

Thanks

Val

Yeah, just set the address to one of the local ones before binding rather than leaving it empty.

Hi Len,

How do I reuse socket using ConnectEx and DisconnectEx?

Thanks

Val

By reading the documentation?

Hi Len,

If I had a glossary of Microsoft terms which used for describing of API functions or other programming docs, many questions are not arisen.

Anyway, thanks for your attention and quick response.

Val

Len, I did not see the response to Val, but I am also having a problem with ConnectEx and am using it similarly to her example... could you tell me what might be causing a 10022/Invalid argument return from WSAGetLastError() in the example above?
Thanks,
Hal

Hal, no idea where the original response went. The socket needs to be bound to a local address before you can call connectex, this is explained in the docs on msdn for connectex...

I have implemented my own Async IO with COmpl port server and client. But I experience strange behavior with ConnectEx it simply freezes - never returns. Any idea ?
thanks

Bug in your code?

Sorry I cant be more help, but you havent given me a great deal to go on. I've never had any problems like that.

Hi Len,

Thanks for this one. As you can imagine, I arrived here using a search for an obscure condition. Not the one on this page, but I'll grab the solution anyways :D

I guess this underscores the importance of a well maintained library. So many things to forget.

If a search yields lenholgate.com, it's an automatic clickthrough!

Leave a comment