Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 665|回复: 0

问题解决——WSAAsyncSelect模型 不触发 FD_CLOSE

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-9-5 11:30:04 | 显示全部楼层 |阅读模式

    ==================================声明==================================

    本文原创,转载在正文中显要的注明作者和出处,并保证文章的完整性。

    未经作者同意请勿修改(包括本声明),保留法律追究的权利。

    未经作者同意请勿用于出版、印刷或学术引用。

    本文不定期修正完善,为保证内容正确,建议移步原文处阅读。

    本文链接:http://www.cnblogs.com/wlsandwho/p/4228894.html

    =======================================================================

     最近有个同学辞职了,在QQ空间里说要北上找工作,问何为,答曰,“年前辞职,年后上班”。

    这样真的好吗?

    =======================================================================

     最近在写一个小东西,需要用到非阻塞模式的套接字,考虑到用的MFC界面而且信息量不是很大很长很吓人,就选用了WSAAsyncSelect模型。

    =======================================================================

    考虑到1对N的情形,在每次accept后都维护一下map<socket,ClientInfo>的存储结构,自然的,每当客户端正常关闭都要再次更新这个结构。

    =======================================================================

    “每当客户端正常关闭”,会调用shutdown和closesocket,于是需要处理FD_CLOSE。

    =======================================================================

    可怕的是,FD_CLOSE的分支没有触发。

    =======================================================================

    那么问题来了,究竟为何没有触发?

    我看了下监听后的WSAAsyncSelect:

    1 WSAAsyncSelect(m_sockListen,hWnd,m_wMsgServer,FD_ACCEPT|FD_CLOSE)

    又看了下接受连接后的WSAAsyncSelect

    1 WSAAsyncSelect(sockClient, hWnd, m_wMsgServer, FD_READ|FD_WRITE|FD_CLOSE)

    感觉没什么问题啊,好奇怪。

    =======================================================================

    稍微看了下自己的代码后,决定搜一下网上的资料,自然的无果。

    只好对照《Windows网络编程技术》的源码看看有什么忽略的地方。

    贴一下书本的源码先:(要用UNICODE还要稍微改改。)

      1 // Module Name: asyncselect.cpp
      2 //
      3 // Description:
      4 //
      5 //    This sample illustrates how to develop a simple echo server Winsock
      6 //    application using the WSAAsyncSelect() I/O model. This sample is
      7 //    implemented as a console-style application (to reduce the programming
      8 //    complexity of writing a real Windows application) and simply prints
      9 //    messages when connections are established and removed from the server.
     10 //    The application listens for TCP connections on port 5150 and accepts them
     11 //    as they arrive. When this application receives data from a client, it
     12 //    simply echos (this is why we call it an echo server) the data back in
     13 //    it's original form until the client closes the connection.
     14 //
     15 //    Since the WSAAsyncSelect I/O model requires an application to manage
     16 //    window messages when network event occur, this application creates
     17 //    a window for the I/O model only. The window stays hidden during the
     18 //    entire execution of this application.
     19 //
     20 // Compile:
     21 //
     22 //    cl -o asyncselect asyncselect.cpp ws2_32.lib user32.lib gdi32.lib
     23 //
     24 // Command Line Options:
     25 //
     26 //    asyncselect.exe 
     27 //
     28 //    Note: There are no command line options for this sample.
     29 
     30 #include <winsock2.h>
     31 #include <windows.h>
     32 #include <stdio.h>
     33 #include <conio.h>
     34 
     35 #define PORT 5150
     36 #define DATA_BUFSIZE 8192
     37 
     38 typedef struct _SOCKET_INFORMATION {
     39    BOOL RecvPosted;
     40    CHAR Buffer[DATA_BUFSIZE];
     41    WSABUF DataBuf;
     42    SOCKET Socket;
     43    DWORD BytesSEND;
     44    DWORD BytesRECV;
     45    _SOCKET_INFORMATION *Next;
     46 } SOCKET_INFORMATION, * LPSOCKET_INFORMATION;
     47 
     48 #define WM_SOCKET (WM_USER + 1)
     49 
     50 void CreateSocketInformation(SOCKET s);
     51 LPSOCKET_INFORMATION GetSocketInformation(SOCKET s);
     52 void FreeSocketInformation(SOCKET s);
     53 
     54 HWND MakeWorkerWindow(void);
     55 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
     56 
     57 LPSOCKET_INFORMATION SocketInfoList;
     58 
     59 void main(void)
     60 {
     61    MSG msg;
     62    DWORD Ret;
     63    SOCKET Listen;
     64    SOCKADDR_IN InternetAddr;
     65    HWND Window;
     66    WSADATA wsaData;
     67 
     68    if ((Window = MakeWorkerWindow()) == NULL)
     69       return;
     70 
     71    // Prepare echo server
     72 
     73    if ((Ret = WSAStartup(0x0202, &wsaData)) != 0)
     74    {
     75       printf("WSAStartup failed with error %d\n", Ret);
     76       return;
     77    }
     78 
     79    if ((Listen = socket (PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
     80    {
     81       printf("socket() failed with error %d\n", WSAGetLastError());
     82       return;
     83    } 
     84 
     85    WSAAsyncSelect(Listen, Window, WM_SOCKET, FD_ACCEPT|FD_CLOSE);
     86 
     87    InternetAddr.sin_family = AF_INET;
     88    InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
     89    InternetAddr.sin_port = htons(PORT);
     90 
     91    if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
     92    {
     93       printf("bind() failed with error %d\n", WSAGetLastError());
     94       return;
     95    }
     96 
     97    if (listen(Listen, 5))
     98    {
     99       printf("listen() failed with error %d\n", WSAGetLastError());
    100       return;
    101    }
    102              
    103    // Translate and dispatch window messages for the application thread
    104 
    105    while(Ret = GetMessage(&msg, NULL, 0, 0))
    106    {
    107       if (Ret == -1)
    108       {
    109          printf("GetMessage() failed with error %d\n", GetLastError());
    110          return;
    111       }
    112 
    113       TranslateMessage(&msg);
    114       DispatchMessage(&msg);
    115    }
    116 }
    117 
    118 
    119 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    120 {
    121    SOCKET Accept;
    122    LPSOCKET_INFORMATION SocketInfo;
    123    DWORD RecvBytes, SendBytes;
    124    DWORD Flags;
    125 
    126    if (uMsg == WM_SOCKET)
    127    {
    128       if (WSAGETSELECTERROR(lParam))
    129       {
    130          printf("Socket failed with error %d\n", WSAGETSELECTERROR(lParam));
    131          FreeSocketInformation(wParam);
    132       } 
    133       else
    134       {
    135          switch(WSAGETSELECTEVENT(lParam))
    136          {
    137             case FD_ACCEPT:
    138 
    139                if ((Accept = accept(wParam, NULL, NULL)) == INVALID_SOCKET)
    140                {        
    141                   printf("accept() failed with error %d\n", WSAGetLastError());
    142                   break;
    143                }
    144 
    145                // Create a socket information structure to associate with the
    146                // socket for processing I/O.
    147 
    148                CreateSocketInformation(Accept);
    149 
    150                printf("Socket number %d connected\n", Accept);
    151 
    152                WSAAsyncSelect(Accept, hwnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);
    153 
    154                break;
    155 
    156             case FD_READ:
    157 
    158                SocketInfo = GetSocketInformation(wParam);
    159 
    160                // Read data only if the receive buffer is empty.
    161 
    162                if (SocketInfo->BytesRECV != 0)
    163                {
    164                   SocketInfo->RecvPosted = TRUE;
    165                   return 0;
    166                }
    167                else
    168                {
    169                   SocketInfo->DataBuf.buf = SocketInfo->Buffer;
    170                   SocketInfo->DataBuf.len = DATA_BUFSIZE;
    171 
    172                   Flags = 0;
    173                   if (WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes,
    174                      &Flags, NULL, NULL) == SOCKET_ERROR)
    175                   {
    176                      if (WSAGetLastError() != WSAEWOULDBLOCK)
    177                      {
    178                         printf("WSARecv() failed with error %d\n", WSAGetLastError());
    179                         FreeSocketInformation(wParam);
    180                         return 0;
    181                      }
    182                   } 
    183                   else // No error so update the byte count
    184                   {
    185                      SocketInfo->BytesRECV = RecvBytes;
    186                   }
    187                }
    188 
    189                // DO NOT BREAK HERE SINCE WE GOT A SUCCESSFUL RECV. Go ahead
    190                // and begin writing data to the client.
    191 
    192             case FD_WRITE:               
    193 
    194                SocketInfo = GetSocketInformation(wParam);
    195 
    196                if (SocketInfo->BytesRECV > SocketInfo->BytesSEND)
    197                {
    198                   SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->BytesSEND;
    199                   SocketInfo->DataBuf.len = SocketInfo->BytesRECV - SocketInfo->BytesSEND;
    200 
    201                   if (WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &SendBytes, 0,
    202                      NULL, NULL) == SOCKET_ERROR)
    203                   {
    204                      if (WSAGetLastError() != WSAEWOULDBLOCK)
    205                      {
    206                         printf("WSASend() failed with error %d\n", WSAGetLastError());
    207                         FreeSocketInformation(wParam);
    208                         return 0;
    209                      }
    210                   } 
    211                   else // No error so update the byte count
    212                   {
    213                      SocketInfo->BytesSEND += SendBytes;
    214                   }
    215                }
    216 
    217                if (SocketInfo->BytesSEND == SocketInfo->BytesRECV)
    218                {
    219                   SocketInfo->BytesSEND = 0;
    220                   SocketInfo->BytesRECV = 0;
    221 
    222                   // If a RECV occurred during our SENDs then we need to post an FD_READ
    223                   // notification on the socket.
    224 
    225                   if (SocketInfo->RecvPosted == TRUE)
    226                   {
    227                      SocketInfo->RecvPosted = FALSE;
    228                      PostMessage(hwnd, WM_SOCKET, wParam, FD_READ);
    229                   }
    230                }
    231 
    232                break;
    233 
    234             case FD_CLOSE:
    235 
    236                printf("Closing socket %d\n", wParam);
    237                FreeSocketInformation(wParam);
    238 
    239                break;
    240          }
    241       }
    242       return 0;
    243    }
    244 
    245    return DefWindowProc(hwnd, uMsg, wParam, lParam);
    246 }
    247 
    248 
    249 void CreateSocketInformation(SOCKET s)
    250 {
    251    LPSOCKET_INFORMATION SI;
    252 
    253    if ((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR,
    254       sizeof(SOCKET_INFORMATION))) == NULL)
    255    {
    256       printf("GlobalAlloc() failed with error %d\n", GetLastError());
    257       return;
    258    }
    259 
    260    // Prepare SocketInfo structure for use.
    261 
    262    SI->Socket = s;
    263    SI->RecvPosted = FALSE;
    264    SI->BytesSEND = 0;
    265    SI->BytesRECV = 0;
    266 
    267    SI->Next = SocketInfoList;
    268 
    269    SocketInfoList = SI;
    270 }
    271 
    272 LPSOCKET_INFORMATION GetSocketInformation(SOCKET s)
    273 {
    274    SOCKET_INFORMATION *SI = SocketInfoList;
    275 
    276    while(SI)
    277    {
    278       if (SI->Socket == s)
    279          return SI;
    280 
    281       SI = SI->Next;
    282    }
    283 
    284    return NULL;
    285 }
    286 
    287 void FreeSocketInformation(SOCKET s)
    288 {
    289    SOCKET_INFORMATION *SI = SocketInfoList;
    290    SOCKET_INFORMATION *PrevSI = NULL;
    291 
    292    while(SI)
    293    {
    294       if (SI->Socket == s)
    295       {
    296          if (PrevSI)
    297             PrevSI->Next = SI->Next;
    298          else
    299             SocketInfoList = SI->Next;
    300 
    301          closesocket(SI->Socket);
    302          GlobalFree(SI);
    303          return;
    304       }
    305 
    306       PrevSI = SI;
    307       SI = SI->Next;
    308    }
    309 }
    310 
    311 HWND MakeWorkerWindow(void)
    312 {
    313    WNDCLASS wndclass;
    314    CHAR *ProviderClass = "AsyncSelect";
    315    HWND Window;
    316 
    317    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    318    wndclass.lpfnWndProc = (WNDPROC)WindowProc;
    319    wndclass.cbClsExtra = 0;
    320    wndclass.cbWndExtra = 0;
    321    wndclass.hInstance = NULL;
    322    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    323    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    324    wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    325    wndclass.lpszMenuName = NULL;
    326    wndclass.lpszClassName = ProviderClass;
    327 
    328    if (RegisterClass(&wndclass) == 0)
    329    {
    330       printf("RegisterClass() failed with error %d\n", GetLastError());
    331       return NULL;
    332    }
    333 
    334    // Create a window.
    335 
    336    if ((Window = CreateWindow(
    337       ProviderClass,
    338       "",
    339       WS_OVERLAPPEDWINDOW,
    340       CW_USEDEFAULT,
    341       CW_USEDEFAULT,
    342       CW_USEDEFAULT,
    343       CW_USEDEFAULT,
    344       NULL,
    345       NULL,
    346       NULL,
    347       NULL)) == NULL)
    348    {
    349       printf("CreateWindow() failed with error %d\n", GetLastError());
    350       return NULL;
    351    }
    352 
    353    return Window;
    354 }

    注意到代码中实现了FD_WRITE,于是屏蔽了一下该分支代码块的具体内容。这样一来,跟我的代码在逻辑上的差别就是:我没有实现FD_WRITE分支。

    考虑到MSDN的文档太多了,今又是周五,不想细看,就试了下在自己代码中加上了对FD_WRITE_BIT的处理——空代码块。

    1     case FD_WRITE:
    2         {
    3 
    4         }
    5         break;

    居然成功了。

     

    后来又尝试了多次,发现,在WSAAsyncSelect中列出了哪个FD_XXXX,就要实现哪个,对于不想实现的,不要列出。

     (至于为什么,这就要看文档了。)

    ========================这难道就是传说中的,“如果爱 请深爱”?========================

     

     

     

     

     

     

     

    ================================耻辱墙===================================

    http://www.cnblogs.com/wlsandwho/p/4206472.html

     

     

     

    ===============================额外扩展===================================

    那么,FD_WRITE又是什么时候触发呢?

    搜索了一番,找到一个比较靠谱的:http://bbs.csdn.net/topics/200074769

    现摘录12楼cpp_programer的总结:

     

    当一个套接字连接被 建立上时(包括客户端的connect(),connectex()等和服务器端的accept接收到后创建的新套接字),这时会触发 FD_WRITE,以后就可以用send(),WSASend()发送数据了.如果以后发送正常的话,将不会再触发FD_WRITE.

    如果发送数据不正常的话,即用send(),WSASend()等发数据,WSAGetLastError()返回WSAEWOULDBLOCK错误,那么等到系统缓冲可再发送数据时,就会触发FD_WRITE.

     

     

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-19 08:20 , Processed in 0.075346 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表