WinSock异步编程

文章目录

  • WinSock异步编程
    • 简介
    • WSAAsyncSelect
    • Finger协议
      • Finger服务器程序
      • Finger客户端程序
简介
  • 同步

    每个函数在下一条语句执行以前必须完成

  • 异步

    Windows的消息是异步的,不按照事先定义的顺序发生的。

    当程序开始一个任务时,可以告诉Windows在任务完成时发送一条消息,收到消息时根据任务的完成结果再决定下一步做什么,处理完这条消息,控制权又返回给Windows,系统继续执行其他的任务。

同步模型中,当执行一些需要花费很长时间才能完成的功能时,程序会被阻塞,无法进行其他的操作,只能等待这个功能完成。

异步WinSock则不同,在执行一个费时的网络操作时,程序用WSAAsyncSelect向Windows系统注册一条消息,指明感兴趣的网络事件,告诉系统任务完成时用这条消息来通知程序。这样,正在进行的网络操作如果不能立即完成,会返回错误码,告诉系统正在处理而不会阻塞程序,程序还可以进行其他的各种操作。网络操作完成时,无论成功还是失败,应用程序的窗口过程会收到之前注册的消息,消息中有操作完成的结果,程序根据消息中的参数判断发生了什么网络事件,决定下一步的工作。

WSAAsyncSelect

该函数提供一个异步I/O模型,使用它应用程序不会在调用某一个套接口函数时阻塞,函数会立即返回给调用者。

当要求的操作完成时,应用程序会收到消息,程序根据消息中指明的事件来决定做什么样的处理。

int WSAAPI WSAAsyncSelect(SOCKET s, HWND hWnd,u_int wMsg,long IEvent);

成功返回0,表明应用程序的事件注册成功;

失败返回SOCKET_ERROR,应用程序可以调用WSAGetLastError()得到具体的错误码。

  • s 要求事件通知的套接口描述符
  • hWnd 窗口句柄,网络事件发生时向这个窗口发消息
  • wMsg 网络事件发生时应用程序会收到的消息
  • IEvent 事件组合

WSAAsyncSelect要求WinSock监测套接口s上的事件,当检查到lEvent参数中规定的网络事件时,向窗口hWnd发送wMsg消息。

会自动把套接口设置为非阻塞模式,要再设为阻塞模式,应用程序必须再调用WSAAsyncSelect ,并且将IEvent 参数设置为0,清除与套接口相关的事件,然后调用ioctlsocket 设置为阻塞模式

程序感兴趣的事件必须一次设置完成, 后面的设置会覆盖前面的

套接口网络事件

事件 说明
FD_ACCEPT 接收进入的连接请求通知
FD_CLOSE 套接口关闭通知
FD_CONNECT 连接建立完成通知
FD_READ 套接口缓冲区收到数据,可以读取数据通知
FD_WRITE 套接口缓冲区有空间发送应用程序的数据,可以发送数据通知

当套接口上指定的发生网络事件的应用程序收到wMsg时,wParam是发生网络事件的套接口句柄,IParam 的低16字节表明发生了什么事件,高16位是错误码

WinSock定义了两个宏来提取网络事件和错误码

#define WSAGETSELECTEVENT(lParam) LOWORD(lParam)
#define WSAGETSELECTERROR(lParam) HIWORD(lParam)

调用WSAAsyncSelect的返回值与网络事件错误之间的区别

  • 调用WSAAsyncSelect函数失败时,可以用WSAGetLastError()得到具体的错误码
  • 收到网络事件通知时,只能用WSAGETSELECTERROR提取错误码,不能用WSAGetLastError函数
  • 调用函数错误与网络事件错误是不同的,函数错误是指函数是否已经成功启动了用户要求的操作,而网络事件是指事件是否有错误。
  • 如果函数调用失败,一定不会收到网络事件;成功时,收到的网络事件可能是成功,也可能是失败。

套接口上的事件具有继承性,套接口上的事件具有继承性,调用accept接受新的连接创建的套接口与侦听套接口有同样的事件属性。

如果侦听套接口上调用WSAAsyncSelect设置了FD_ACCEPT、FD_READ、FD_WRITE和FD_CLOSE,那么在侦听套接口上接受的任何套接口上都有这些事件,并且在与侦听套接口相同的消息上接收这些网络事件通知。

如果应用程序想在accept的套接口上有不同的消息和事件,需要重新调用WSAAsyncSelect设置事件通知条件

WinSock向应用程序的窗口发送了一个网络事件后,不会再发送同样的事件通知,除非应用程序调用了相关函数,重新启用了事件通知。如:服务器连续调用两次send向客户端发送数据,第一次200字节,会收到FD_READ通知,如果客户端没有调用recv接收这200字节数据,那么后面再发送100字节数据到达客户端时,就不会再有FD_READ通知。直到客户端调用recv后,才会重新启用FD_READ事件。

套接口事件及通知条件

事件 重新启用函数 通知条件
FD_ACCEPT accept 1. 只适用于面向连接套接口 2. 调用WSAAsyncSelect时,队列中有连接请求 3. 接收到新的连接请求,并且还没有发送FD_ACCEPT 4. 调用accept后,队列中仍然有连接请求
FD_CLOSE 1. 只适用于面向连接的套接口,调用closesocket后不会再收到FD_CLOSE 2. 调用WSAAsyncSelect时,连接已经被关闭 3. 对方关闭了连接(发送FIN 或者 RST)
FD_CONNECT 1. 调用WSAAsyncSelect时,已经建立了连接 2. 调用connect函数后,返回WSAEWOULDBLOCK, 在连接完成时,会收到FD_CONNECT
FD_READ recv,recvfrom 1. 调用WSAAsyncSelect时,接收缓冲区中有数据 2. 接收到新的数据,且没有发送FD_READ 3. 调用recv,recvfrom后缓冲区内仍然有数据或者设置SO_OOBINLINE 为TRUE, 而有OOB数据 ( OOB带外数据 )
FD_WRITE send,sendto 1. 调用WSAAsyncSelect时,send或sendto 可以成功 2. 调用connect或者accept后,连接建立成功 3. 调用send或者sendto失败,错误码为WSAEWOULDBLOCK,而当发送缓冲区又有空间时 4. 无连接套接口调用bind成功后,总是可写的
FD_OOB 1. 调用setsockopt设置SO_OOBINLINE为FALSE,条件满足时才会收到FD_OOB 2. 调用WSAAsyncSelect时,接收缓冲区中有OOB数据 3. 收到OOB数据,并且还没有发送FD_OOB 4. 调用recv或者 recvfrom后,缓冲区中还有OOB数据
  • FD_ACCEPT

    只有listen套接口会收到FD_ACCEPT事件。

    当有新的客户与服务器建立连接时,服务器应用程序会收到FD_ACCEPT事件。

    收到FD_ACCEPT事件后,要调用accept函数接收新的连接

  • FD_CONNECT

    客户端的应用程序才会收到FD_CONNECT。

    应用程序调用connect函数与服务器建立连接,连接过程经过三次握手,建立连接不会立即成功。

    无论成功与否,connect 会先返回给调用程序,错误码为WSAEWOULDBLOCK,连接完成后客户端应用程序会收到FD_CONNECT事件通知。

  • FD_READ

    当有新的数据到达,并且还没有发送FD_READ时,会向应用程序发送FD_READ事件通知。

    收到FD_READ应用程序调用recv接收数据,应用程序不需要一次读完所有的数据。

    在Windows中是事件驱动的,当调用recv时,如果数据没有读完,Windows还会发送FD_READ通知。

    对于一个FD_READ事件,应用程序如果调用了多次recv,每次都没读完数据,那么每个recv都会导致系统发送一个FD_READ通知。

    为了避免这种情况,应用程序可以在recv前用WSAAsyncSelect取消FD_READ事件。

    收到FD_READ通知不一定就能接收到数据,通知有可能是前面recv时导致发送的,但数据已接收完。

    WinSock不合理之处:当调用recv恰好接收完数据,即使缓冲区中已经没有数据了,还是会发送FD_READ

  • FD_WRITE

    收到FD_WRITE意味着套接口是可写的,可以调用send或sendto发送数据。

    的,可以调用send或sendto发送数据。当第一次调用connect连接成功或调用accept接受一个连接时,应用程序都会收到FD_WRITE。

    连接建立成功后,就可以发送数据,如果应用程序发送的数据比较多,TCP/IP协议不能及时把数据发送给对方,应用程序的数据会暂时保存在套接口发送缓冲区中。

    当缓冲区满时,再调用send就会失败,错误为WSAEWOULDBLOCK。

    协议把数据发送出去,发送缓冲区又有空间时,会向应用程序发送FD_WRITE通知。

  • FD_OOB

    当套接口被配置为单独接收紧急数据,即紧急数据不与正常数据一起接收,并收到了对方send(MSG_OOB)发送的紧急数据时,应用程序会收到FD_OOB事件通知。

    收到FD_OOB事件通知,应用程序需要调用recv(MSG_OOB)接收紧急数据。

    • 接收缓冲区中只有OOB数据,调用recv,标志为0,则会失败,错误码为WSAEWOULDBLOCK。调用recv(MSG_OOB)会收到OOB数据。

    • 发送OOB数据send(soc,data,len,MSG_OOB),无论len是多长,接收到的OOB只有一个字节,是data的最后一个字节。

      当len大于1时,接收数据的应用程序会先收到FD_OOB事件,调用recv(MSG_OOB)接收data最后一个字节的OOB数据。之后会收到FD_READ,调用recv接收OOB前面的正常数据

    • 发送n次OOB时,只要send之间的间隔较长,数据没有在发送时组合到一个分组中,而接收方一直没有接收OOB,最后一起接收时,会一次收到n个字节的OOB。

      WinSock把OOB放在了一个单独的接收缓冲区中

    • 是否单独接收紧急数据是用setsockopt(SO_OOBINLINE)设置的,默认是单独接收OOB

      调用setsockopt(SO_OOBINLINE),选项值为TRUE时,表示把OOB当作正常数据接收,这样即使收到了OOB,应用程序也不会收到FD_OOB事件,而是FD_READ通知。

    • FD_CLOSE

      只适用于面向连接套接口,对方调用shutdown或closesocket关闭连接时会收到FD_CLOSE通知。

      建议收到FD_CLOSE,先调用recv把数据接收完再关闭套接口

      FD_CLOSE消息的错误码 --> 表示对方是否是正常关闭还是放弃连接

      • 0 正常关闭
      • WSAECONNRESET 连接被重设,对方放弃连接
Finger协议

Finger协议的主要功能是查询某一主机上的用户信息。

主机返回用户容易阅读的状态报告,包括用户名、终端位置、任务名称、空闲时间等。

Finger协议的知名端口号是79,协议的格式没有特别的要求,大部分情况下客户端只需要发送一个"命令行"。根据"命令行"和服务器的不同,客户端收到的信息会有所变化。

服务器一发送完数据就关闭连接。

  • 如果客户端发送的是空行,即只有<CRLF>,服务器把当前使用系统的所有用户都发送给客户端。

  • 如果客户端规定了用户名,如"Alice",那么服务器应该只把这个用户的情况报告给客户端。

  • 如果用户名与服务器上的多个登录用户匹配,这些匹配的用户信息应该都发送给客户端。

  • 当查询的用户没有登录时,服务器报告用户名、最后的注销时间。

    用户也可以留下一条短消息,服务器在应答中包含这条消息。

常用的操作系统,如Linux、Windows都带有Finger程序,基本格式为:finger [user]@host。其中user规定了想要查询的用户,它是可选的,没有这个参数,则显示服务器上所有用户的信息。而host指定了要查询的服务器。

Finger服务器程序
  1. 服务器程序固定在知名端口79上侦听
  2. 预先定义用户信息 --> 用于测试
  3. 目前程序只有开始菜单,没有结束或者停止菜单
  4. 开始Finger服务器,需要单击File菜单中的Start

FingerSrv.c

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>
#include <stdio.h>
#include <tchar.h>
#include "resource1.h"
#pragma comment(lib, "ws2_32.lib") /* WinSock使用的库函数 */
#define WM_SOCKET_NOTIFY   (WM_USER + 11) /* 自定义socket消息 */
#define FINGER_DEF_PORT    79 /* 侦听的端口 */
#define ID_EDIT_LOG        1  /* 日志控件的标识 */
/* 定义控件的风格 */
#define EDIT_STYLE  (WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | \ES_MULTILINE | WS_HSCROLL | WS_VSCROLL)
#define FINGER_LINE_END  "\r\n" /* 行结束符 */
#define FINGER_MAX_BUF    1024  /* 最大的接收缓冲区 */
#define FINGER_MAX_SOC       8  /* 最多可以接受的客户数 */
#define TABLE_SIZE(a) (sizeof(a) / sizeof(a[0]))
/* 定义用户信息 */
struct UserInfo
{const char* szUser;  // 用户名 const char* szFullName; // 全称 const char* szMessage;  // 留下的消息
};
struct UserInfo FingerUser[] =
{{ "Alice",  "Alice Joe",   "Welcome! I am on vacation." },{ "Smith",  "Smith David", "Learn to fly like a bird." },{ "Rubin",  "Jeff Rubin",  "How are you!" }
};
/* 定义全局变量 */
static HWND hWndLog;       /* 输出日志信息的窗口句柄 */
static SOCKET hLstnSoc;    /* 侦听socket句柄 */
static SOCKET hClntSoc[FINGER_MAX_SOC]; /* 客户端的socket句柄 */
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);  // 窗口处理函数
static SOCKET FingerListenSoc(HWND hWnd, unsigned short port);
static void FingerOnSocketNotify(WPARAM wParam, LPARAM lParam);
static void LogPrintf(const TCHAR* szFormat, ...);int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)
{TCHAR szClassName[] = TEXT("FingerSrv");HWND        hWnd;MSG         msg;WNDCLASS    wndclass;WSADATA     wsaData;int i;WSAStartup(WINSOCK_VERSION, &wsaData); /* 初始化 */memset(hClntSoc, INVALID_SOCKET, FINGER_MAX_SOC * sizeof(SOCKET));/* 注册窗口类 */wndclass.style = CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc = WndProc;  /* 窗口过程处理函数 */wndclass.cbClsExtra = 0;wndclass.cbWndExtra = 0;wndclass.hInstance = hInstance;wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);wndclass.lpszMenuName = (LPCSTR)IDR_FINGER_SRV; /* 菜单 */wndclass.lpszClassName = szClassName; /* 窗口类名 */if (!RegisterClass(&wndclass)){MessageBox(NULL, TEXT("TRequires Windows NT!"), szClassName, 0);return 0;}// 在内存中创建窗口 hWnd = CreateWindow(szClassName,          /* 与注册的类名相同 */TEXT("Finger Server"),/* 窗口标题 */WS_OVERLAPPEDWINDOW,  /* 窗口风格 */CW_USEDEFAULT,        /* 初始x坐标 */CW_USEDEFAULT,        /* 初始y坐标 */CW_USEDEFAULT,        /* 初始宽度 */CW_USEDEFAULT,        /* 初始高度 */NULL,                 /* 父窗口句柄 */NULL,                 /* 菜单句柄 */hInstance,            /* 程序实例句柄 */NULL);                /* 程序参数 */ShowWindow(hWnd, iCmdShow); /* 显示窗口 */UpdateWindow(hWnd);         /* 更新窗口 *//* 消息循环 */while (GetMessage(&msg, NULL, 0, 0)){TranslateMessage(&msg);DispatchMessage(&msg);}/* 关闭socket */for (i = 0; i < FINGER_MAX_SOC; i++){if (hClntSoc[i] != INVALID_SOCKET)closesocket(hClntSoc[i]);}closesocket(hLstnSoc);WSACleanup();return msg.wParam;
}static LRESULT CALLBACK WndProc(HWND hWnd, UINT message,WPARAM wParam, LPARAM lParam)
{int cxClient, cyClient;int wmId, wmEvent;switch (message){case WM_CREATE:MessageBox(hWnd, "Start", "infor", 0);hWndLog = CreateWindow(TEXT("edit"), NULL, EDIT_STYLE,0, 0, 0, 0, hWnd, (HMENU)ID_EDIT_LOG,((LPCREATESTRUCT)lParam)->hInstance, NULL);return 0;case WM_SIZE:cxClient = LOWORD(lParam);cyClient = HIWORD(lParam);MoveWindow(hWndLog, 0, 0, cxClient, cyClient, FALSE);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;case WM_COMMAND:wmId = LOWORD(wParam);wmEvent = HIWORD(wParam);switch (wmId){case IDM_START:MessageBox(hWnd, "IDM_START", "infor", 0);hLstnSoc = FingerListenSoc(hWnd, FINGER_DEF_PORT);if (hLstnSoc == INVALID_SOCKET)MessageBox(hWnd, TEXT("Listen error"), TEXT("Finger"), 0);return 0;case IDM_EXIT:DestroyWindow(hWnd);return 0;}break;case WM_SOCKET_NOTIFY:FingerOnSocketNotify(wParam, lParam);return 0;}return DefWindowProc(hWnd, message, wParam, lParam);// 系统默认处理函数
}static SOCKET FingerListenSoc(HWND hWnd, unsigned short port)
{struct sockaddr_in soc_addr; /* socket地址结构 */SOCKET lstn_soc; /* 侦听socket句柄 */int result;/* 创建侦听socket */lstn_soc = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);result = WSAAsyncSelect(lstn_soc, hWnd, WM_SOCKET_NOTIFY,FD_ACCEPT | FD_READ | FD_CLOSE);/* 由系统来分配地址 */soc_addr.sin_family = AF_INET;soc_addr.sin_port = htons(port);soc_addr.sin_addr.s_addr = INADDR_ANY;/* 绑定socket */result = bind(lstn_soc, (struct sockaddr*)&soc_addr, sizeof(soc_addr));if (result == SOCKET_ERROR){closesocket(lstn_soc);return INVALID_SOCKET;}//套接口所对应的TCP控制块从CLOSED状态转变到LISTEN状态result = listen(lstn_soc, SOMAXCONN); /* 侦听来自客户端的连接 */if (result == SOCKET_ERROR){closesocket(lstn_soc);return INVALID_SOCKET;}LogPrintf(TEXT("Finger server is running ...\r\n"));return lstn_soc;
}static SOCKET FingerOnAccept(SOCKET lstn_soc)
{struct sockaddr_in soc_addr; /* socket地址结构 */int i, addr_len = sizeof(soc_addr); /* 地址长度 */SOCKET data_soc;/* 接受新的连接 */data_soc = accept(lstn_soc, (struct sockaddr*)&soc_addr, &addr_len);if (data_soc == INVALID_SOCKET){LogPrintf(TEXT("accept error: %i.\r\n"), WSAGetLastError());return INVALID_SOCKET;}for (i = 0; i < FINGER_MAX_SOC; i++){if (hClntSoc[i] == INVALID_SOCKET){hClntSoc[i] = data_soc;break;}}return data_soc;
}static int FingerOnRead(SOCKET clnt_soc)
{int i, j, result, buflen = FINGER_MAX_BUF - 1;int iFind = 0, iCount = TABLE_SIZE(FingerUser);char cBuf[FINGER_MAX_BUF], cSendBuf[FINGER_MAX_BUF], * pEnd;struct UserInfo* pUser;/* 查找客户端对应的socket句柄 */for (i = 0; i < FINGER_MAX_SOC; i++){if (hClntSoc[i] == clnt_soc)break;}if (i == FINGER_MAX_SOC)return FALSE;result = recv(clnt_soc, cBuf, buflen, 0);  /* 接收数据 */if (result <= 0){closesocket(clnt_soc);hClntSoc[i] = INVALID_SOCKET;return FALSE;}cBuf[result] = 0; // 在后方添加一个'\0', 也是buflen = FINGER_MAX_BUF -1的原因 LogPrintf(TEXT("recv >: %s\r\n"), cBuf);/* 搜索用户名结尾的 "\r\n" */pEnd = strstr(cBuf, FINGER_LINE_END);if ((pEnd != NULL) && (pEnd != cBuf))*pEnd = 0; /*结尾的 "\r\n" 换成 0, 便于查找 */for (j = 0; j < iCount; j++) /* 查找用户信息 */{pUser = &FingerUser[j];if (strcmp(cBuf, FINGER_LINE_END) == 0) /* 所有用户 */buflen = sprintf(cSendBuf, "%s\r\n", pUser->szUser);else if (strcmp(cBuf, pUser->szUser) == 0) /* 特定用户 */buflen = sprintf(cSendBuf, "%s: %s, %s\r\n", pUser->szUser,pUser->szFullName, pUser->szMessage);elsecontinue;iFind++;result = send(clnt_soc, cSendBuf, buflen, 0);if (result > 0)LogPrintf(TEXT("send <: %s\r\n"), cSendBuf);}if (!iFind)send(clnt_soc, "The user is not found.\r\n", 24, 0);closesocket(clnt_soc); // 关闭连接 hClntSoc[i] = INVALID_SOCKET;return TRUE;
}static void FingerOnClose(SOCKET clnt_soc)
{int i;/* 查找客户端对应的socket句柄 */for (i = 0; i < FINGER_MAX_SOC; i++){if (hClntSoc[i] == clnt_soc){closesocket(clnt_soc);hClntSoc[i] = INVALID_SOCKET;break;}}
}static void FingerOnSocketNotify(WPARAM wParam, LPARAM lParam)
{int iResult = 0;WORD wEvent, wError;wEvent = WSAGETSELECTEVENT(lParam); /* LOWORD */wError = WSAGETSELECTERROR(lParam); /*HIWORD */switch (wEvent){case FD_READ:if (wError){LogPrintf(TEXT("FD_READ error #%i."), wError);return;}FingerOnRead(wParam);break;case FD_ACCEPT:if (wError || (wParam != hLstnSoc)){LogPrintf(TEXT("FD_ACCEPT error #%i."), wError);return;}FingerOnAccept(wParam);break;case FD_CLOSE:FingerOnClose(wParam);break;}
}static void LogPrintf(const TCHAR * szFormat, ...)
{int iBufLen = 0, iIndex;TCHAR szBuffer[FINGER_MAX_BUF];va_list pVaList;va_start(pVaList, szFormat);
#ifdef UNICODEiBufLen = _vsnwprintf(szBuffer, FINGER_MAX_BUF, szFormat, pVaList);
#elseiBufLen = _vsnprintf(szBuffer, FINGER_MAX_BUF, szFormat, pVaList);
#endifva_end(pVaList);iIndex = GetWindowTextLength(hWndLog);SendMessage(hWndLog, EM_SETSEL, (WPARAM)iIndex, (LPARAM)iIndex);SendMessage(hWndLog, EM_REPLACESEL, FALSE, (LPARAM)szBuffer);SendMessage(hWndLog, EM_SCROLLCARET, 0, 0);
}

resource1.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含文件。
// 供 Resource.rc 使用
//
#define IDR_MENU1                       101
#define IDR_FINGER_SRV                  101
#define ID_CMD_ST                       40001
#define ID_CMD_                         40002
#define ID_CMD_EXIT                     40003
#define IDM_START                       40004
#define IDM_EXIT                        40005// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40006
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

Resource.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource1.h"
#define APSTUDIO_READONLY_SYMBOLS
/
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/
#undef APSTUDIO_READONLY_SYMBOLS
/
// 中文(简体,中国) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#pragma code_page(936)
#ifdef APSTUDIO_INVOKED
/
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN"resource1.h\0"
END
2 TEXTINCLUDE
BEGIN"#include ""winres.h""\r\n""\0"
END
3 TEXTINCLUDE
BEGIN"\r\n""\0"
END
#endif    // APSTUDIO_INVOKED
/
//
// Menu
//
IDR_FINGER_SRV MENU
BEGINPOPUP "CMD"BEGINMENUITEM "Start",                       IDM_STARTMENUITEM SEPARATORMENUITEM "Exit",                        IDM_EXITEND
END#endif    // 中文(简体,中国) resources
/
#ifndef APSTUDIO_INVOKED
/
//
// Generated from the TEXTINCLUDE 3 resource.
//
/
#endif    // not APSTUDIO_INVOKED

Finger客户端程序

Finger客户端程序FingerClnt根据用户的输入向服务器发送请求,查询对应用户的信息。

程序可以输入的信息有两个:用户名和主机。

  • 用户名是要向服务器查询的用户名称;

    用户名可以为空,为空时,客户端只向服务器发送一个空行“\r\n”,要求得到服务器所有已登录用户的信息。

    用户名不为空时,程序取得用户名,在后面追加协议要求的行结束符“\r\n”,调用send把数据发送给服务器,查询这个特定用户的信息。

  • 主机是服务器的IP地址或域名。

    使用协议规定的知名端口。

FingerClnt.c

#include <winsock2.h>
#include <stdio.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")  /* WinSock使用的库函数 */
#define WM_GETHOST_NOTIFY  (WM_USER + 1)  /* 定义域名查询消息 */
#define WM_SOCKET_NOTIFY   (WM_USER + 11) /* 定义socket消息 */
#define FINGER_DEF_PORT     79 /* 侦听的端口 */
#define FINGER_NAME_LEN    256 /* 一般名字缓冲区长度 */
#define FINGER_MAX_BUF    1024 /* 最大的接收缓冲区 */
/* 定义控件的风格 */
#define STATIC_STYLE   (WS_CHILDWINDOW | WS_VISIBLE | SS_LEFT)
#define BUTTON_STYLE   (WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON)
#define EDIT_STYLE     (WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT)
#define EDIT_STYLE_EXT (EDIT_STYLE | ES_MULTILINE | ES_READONLY | \WS_HSCROLL | WS_VSCROLL)
/* 控件的标识, 是控件在数组中的下标 */
#define ID_EDIT_USER    1  /* 用户 */
#define ID_EDIT_HOST    3  /* 主机 */
#define ID_BTN_FINGER   4  /* 查询按钮的ID */
#define ID_EDIT_LOG     5  /* 日志控件的标识 */
#define TABLE_SIZE(a) (sizeof(a) / sizeof( a[0]) )
/* 控件的属性结构 */
struct Widget
{int iLeft;      /* 左上角的x坐标 */int iTop;       /* 左上角的y坐标 */int iWidth;     /* 宽度 */int iHeigh;     /* 高度 */int iStyle;     /* 控件的风格 */TCHAR *szType;  /* 控件类型: button, edit etc. */TCHAR *szTitle; /* 控件上显示的文字 */
};
struct Finger
{HWND   hWnd;        /* 窗口句柄 */HANDLE hAsyncHost;  /* 域名查询句柄 */SOCKET hSoc;        /* socket句柄  */char cHostEnt[MAXGETHOSTSTRUCT]; /* 域名查询缓冲区 */char szUser[FINGER_NAME_LEN];    /* 用户名 */char szHost[FINGER_NAME_LEN];    /* 主机 */
};
/* 定义Finger程序使用的控件 */static struct Widget FgrWgt[] =
{/* 用户名 */{ 1, 1, 6,  2, STATIC_STYLE, TEXT("static"), TEXT("用户:") },{ 7, 1, 24, 2, EDIT_STYLE,   TEXT("edit"), TEXT("Alice") },/* 主机 */{ 33, 1, 6,  2, STATIC_STYLE, TEXT("static"), TEXT("主机:") },{ 38, 1, 24, 2, EDIT_STYLE,   TEXT("edit"), TEXT("127.0.0.1") },/* Finger按钮 */{ 64, 1, 12, 2, BUTTON_STYLE, TEXT("button"), TEXT("Finger") },/* 信息 */{ 1, 4, 64, 20, EDIT_STYLE_EXT, TEXT("edit"), TEXT("") }
};
/* 定义全局变量 */
static HWND hWndWgt[TABLE_SIZE(FgrWgt)];
static struct Finger gFingerCtrl = { 0, 0, INVALID_SOCKET };
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static void LogPrintf(const TCHAR *szFormat, ...);int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)
{TCHAR szClassName[] = TEXT("FingerClnt");MSG         msg;WNDCLASS    wndclass;WSADATA     wsaData;WSAStartup(WINSOCK_VERSION, &wsaData); /* 初始化 *//* 注册窗口类 */wndclass.style         = CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc   = WndProc;  /* 这是窗口过程 */wndclass.cbClsExtra    = 0;wndclass.cbWndExtra    = 0;wndclass.hInstance     = hInstance;wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);wndclass.lpszMenuName  = NULL; /* 菜单 */wndclass.lpszClassName = szClassName; /* 窗口类名 */if(!RegisterClass(&wndclass)){MessageBox(NULL, TEXT("Requires Windows NT!"), szClassName, 0);return 0;}gFingerCtrl.hWnd = CreateWindow(szClassName,/* 与注册类名相同 */TEXT("Finger Client"),/* 窗口标题 */WS_OVERLAPPEDWINDOW,  /* 窗口风格 */CW_USEDEFAULT,        /* 初始x坐标 */CW_USEDEFAULT,        /* 初始y坐标 */CW_USEDEFAULT,        /* 初始宽度 */CW_USEDEFAULT,        /* 初始高度 */NULL,                 /* 父窗口句柄 */NULL,                 /* 菜单句柄 */hInstance,            /* 程序实例句柄 */NULL);                /* 程序参数 */ShowWindow(gFingerCtrl.hWnd, iCmdShow); /* 显示窗口 */UpdateWindow(gFingerCtrl.hWnd);         /* 更新窗口 *//* 消息循环 */while(GetMessage(&msg, NULL, 0, 0)){TranslateMessage(&msg);DispatchMessage(&msg);}if (gFingerCtrl.hSoc != INVALID_SOCKET)closesocket(gFingerCtrl.hSoc);WSACleanup();return msg.wParam;
}static void FingerOnCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
{HINSTANCE hInstance = ((LPCREATESTRUCT) lParam)->hInstance;int i, iCount = TABLE_SIZE(FgrWgt);int cxChar, cyChar;struct Widget *pWgt;// GetDialogBaseUnits返回系统的对话基本单位// 该基本单位为系统字体字符的平均宽度和高度。cxChar = LOWORD(GetDialogBaseUnits());cyChar = HIWORD(GetDialogBaseUnits());/* 创建控件 */for (i = 0; i < iCount; i++){pWgt = &FgrWgt[i];hWndWgt[i] = CreateWindow(pWgt->szType, pWgt->szTitle,pWgt->iStyle, pWgt->iLeft * cxChar, pWgt->iTop * cyChar,pWgt->iWidth * cxChar, pWgt->iHeigh * cyChar,hWnd, (HMENU) i, hInstance, NULL);}
}static BOOL FingerOnCommand(HWND hWnd,WPARAM wParam,LPARAM lParam)
{int wmId = LOWORD(wParam), wmEvent = HIWORD(wParam);/* 处理BN_CLICKED, 得到用户输入的信息 */if ((wmId == ID_BTN_FINGER) && (wmEvent == BN_CLICKED)){if (gFingerCtrl.hAsyncHost)return TRUE;if (gFingerCtrl.hSoc != INVALID_SOCKET){closesocket(gFingerCtrl.hSoc);gFingerCtrl.hSoc = INVALID_SOCKET;}GetWindowText(hWndWgt[ID_EDIT_USER], gFingerCtrl.szUser,FINGER_NAME_LEN);GetWindowText(hWndWgt[ID_EDIT_HOST], gFingerCtrl.szHost,FINGER_NAME_LEN);// 异步方式 gFingerCtrl.hAsyncHost = WSAAsyncGetHostByName(hWnd,WM_GETHOST_NOTIFY, gFingerCtrl.szHost,gFingerCtrl.cHostEnt, MAXGETHOSTSTRUCT);if (gFingerCtrl.hAsyncHost == 0)MessageBox(hWnd, TEXT("Get Host Error"), NULL, 0);return TRUE;}return FALSE;
}static SOCKET FingerQuery(HWND hWnd)
{struct sockaddr_in soc_addr; /* socket地址结构 */SOCKET soc; /* Finger的socket句柄 */int result;unsigned long addr;struct hostent *host_ent;host_ent = (struct hostent *)gFingerCtrl.cHostEnt;addr = *(unsigned long *)host_ent->h_addr; /* 网络字节序 */soc = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);result = WSAAsyncSelect(soc, hWnd, WM_SOCKET_NOTIFY,FD_CONNECT | FD_READ | FD_CLOSE);/* 连接的地址和端口 */soc_addr.sin_family = AF_INET;soc_addr.sin_port = htons(FINGER_DEF_PORT);soc_addr.sin_addr.s_addr = addr;/* 与服务器建立连接 */result = connect(soc, (struct sockaddr *)&soc_addr, sizeof(soc_addr));// 返回0 表示已经成功建立连接了 if ((result == SOCKET_ERROR) &&(WSAGetLastError() != WSAEWOULDBLOCK)){closesocket(soc);MessageBox(hWnd, TEXT("Can't connect server"), NULL, 0);return INVALID_SOCKET;}return soc;
}static int FingerOnConnect(SOCKET clnt_soc)
{int result;char cSendBuf[FINGER_MAX_BUF];result = sprintf(cSendBuf, "%s\r\n", gFingerCtrl.szUser);result = send(clnt_soc, cSendBuf, result, 0);return result;
}static int FingerOnRead(SOCKET clnt_soc)
{int result, buflen = FINGER_MAX_BUF -1;char cBuf[FINGER_MAX_BUF];/* 接收数据 */result = recv(clnt_soc, cBuf, buflen, 0);if (result <= 0){closesocket(clnt_soc);return result;}cBuf[result] = 0; // 末尾添加一个 '\0' LogPrintf(TEXT("%s\r\n"), cBuf);return result;
}static void FingerOnSocketNotify(WPARAM wParam, LPARAM lParam)
{int iResult = 0;WORD wEvent, wError;wEvent = WSAGETSELECTEVENT(lParam); /* LOWORD */wError = WSAGETSELECTERROR(lParam); /* HIWORD */switch (wEvent){case FD_CONNECT:if (wError){LogPrintf(TEXT("FD_CONNECT error #%i"), wError);return;}FingerOnConnect(wParam);break;case FD_READ:if (wError){LogPrintf(TEXT("FD_READ error #%i"), wError);return;}FingerOnRead(wParam);break;case FD_CLOSE:closesocket(wParam);gFingerCtrl.hSoc = INVALID_SOCKET;break;}
}static LRESULT CALLBACK WndProc(HWND hWnd, UINT message,WPARAM wParam, LPARAM lParam)
{int cyChar, cxClient, cyClient;int iError;struct Widget *pWgt;switch (message){case WM_CREATE:FingerOnCreate(hWnd, wParam, lParam);return 0;case WM_SIZE:cxClient = LOWORD(lParam);cyClient = HIWORD(lParam);cyChar = HIWORD(GetDialogBaseUnits());pWgt = &FgrWgt[ID_EDIT_LOG];MoveWindow(hWndWgt[ID_EDIT_LOG], pWgt->iLeft,pWgt->iTop * cyChar, cxClient, cyClient, FALSE);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;case WM_COMMAND:if (FingerOnCommand(hWnd, wParam, lParam))return 0;break;case WM_GETHOST_NOTIFY:iError = WSAGETASYNCERROR(lParam);if (iError || wParam != (WPARAM)gFingerCtrl.hAsyncHost){gFingerCtrl.hAsyncHost = 0;MessageBox(hWnd, TEXT("Get Host Result Error"), NULL, 0);return 0; /* 发生错误 */}gFingerCtrl.hAsyncHost = 0;gFingerCtrl.hSoc = FingerQuery(hWnd);return 0;case WM_SOCKET_NOTIFY:FingerOnSocketNotify(wParam, lParam);return 0;}return DefWindowProc(hWnd, message, wParam, lParam);
}static void LogPrintf(const TCHAR * szFormat, ...)
{int iBufLen = 0, iIndex;TCHAR szBuffer[FINGER_MAX_BUF];va_list pVaList;va_start(pVaList, szFormat);
#ifdef UNICODEiBufLen = _vsnwprintf(szBuffer, FINGER_MAX_BUF, szFormat, pVaList);
#elseiBufLen = _vsnprintf(szBuffer, FINGER_MAX_BUF, szFormat, pVaList);
#endifva_end(pVaList);iIndex = GetWindowTextLength(hWndWgt[ID_EDIT_LOG]);SendMessage(hWndWgt[ID_EDIT_LOG], EM_SETSEL, (WPARAM)iIndex, (LPARAM)iIndex);SendMessage(hWndWgt[ID_EDIT_LOG], EM_REPLACESEL, FALSE, (LPARAM)szBuffer);SendMessage(hWndWgt[ID_EDIT_LOG], EM_SCROLLCARET, 0, 0);
}

WinSock异步编程相关推荐

  1. WinSock网络编程实用宝典(一)

    一.TCP/IP 体系结构与特点    1.TCP/IP体系结构   TCP/IP协议实际上就是在物理网上的一组完整的网络协议.其中TCP是提供传输层服务,而IP则是提供网络层服务.TCP/IP包括以 ...

  2. 【C++】多线程与异步编程【四】

    文章目录 [C++]多线程与异步编程[四] 0.三问 1.什么是异步编程? 1.1同步与异步 1.2 **阻塞与非阻塞** 2.如何使用异步编程 2.1 使用全局变量与条件变量传递结果 实例1: 2. ...

  3. Python网络编程(4)——异步编程select epoll

    在SocketServer模块的学习中,我们了解了多线程和多进程简单Server的实现,使用多线程.多进程技术的服务端为每一个新的client连接创建一个新的进/线程,当client数量较多时,这种技 ...

  4. 异步编程模型--使用 IAsyncResult 对象

    先推荐阅读下面的资料: MSDN:异步编程设计模式 IBM developerworks: 使用异步 I/O 大大提高应用程序的性能 参考博文: 1.正确使用异步操作 2.Lab:体会ASP.NET异 ...

  5. 笑了,面试官问我知不知道异步编程的Future。

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 荒腔走板 老规矩,先来一个简短的荒腔走板,给冰冷的技术文注 ...

  6. JavaScript 异步编程--Generator函数、async、await

    JavaScript 异步编程–Generator函数 Generator(生成器)是ES6标准引入的新的数据类型,其最大的特点就是可以交出函数的执行的控制权,即:通过yield关键字标明需要暂停的语 ...

  7. @async 默认线程池_.NET Web应用中为什么要使用async/await异步编程?

    布莱恩特:.NET Core开发精选文章目录,持续更新,欢迎投稿!​zhuanlan.zhihu.com 前言 1.什么是async/await? await和async是.NET Framework ...

  8. 57 Node.js异步编程

    技术交流QQ群:1027579432,欢迎你的加入! 欢迎关注我的微信公众号:CurryCoder的程序人生 1.Node.js异步编程 1.1 Node.js中的异步API 如果异步API后面的代码 ...

  9. Spring Boot实战:异步编程指南

    通过本文你可以了解到下面这些知识点: Future 模式介绍以及核心思想 核心线程数.最大线程数的区别,队列容量代表什么: ThreadPoolTaskExecutor 饱和策略: SpringBoo ...

最新文章

  1. 小记 | 从 0 到 1,看我玩弄千万日志于股掌
  2. background:#e5eecc;
  3. python列表生成式和if语句、if...else语句、zip函数结合使用
  4. MySQL索引类型 btree索引和hash索引的区别
  5. shouldOverrideUrlLoading(拦截url加载,除资源请求的url) shouldInterceptRequest(拦截所有url请求)
  6. 【阿里妈妈营销科学系列】第一篇:消费者资产分析
  7. 【MATLAB】混合粒子群算法原理、代码及详解
  8. 软件工程采取了哪些措施以保证最终能够交付给用户一个高质量、低成本的软件产品?
  9. mysql效率索引_mysql下普通索引和唯一索引的效率对比
  10. 【报告分享】如何嫁给“改变世界的男人”-程序员之理想女友大调查.pdf(附下载链接)...
  11. D37 682. Baseball Game
  12. ROS 内外网做双网卡绑定负载分流教程bonding 配置教程
  13. 初识TensorFlow
  14. idea 用鼠标滚轮调整代码文字大小
  15. KMP算法 next数组 nextval数组
  16. java排队系统模型,排队论模型(三):M / M / s/ s 损失制排队模型
  17. Oracle数据库岗位,Oracle数据库岗位职责
  18. linux中读写执行的含义,Linux中读写执行权限的真正含义
  19. 为抖音而生的多闪,如何获取抖音的用户数据?
  20. 国外常见16款著名的实时网站统计系统

热门文章

  1. 角色动画(四)——动作捕捉
  2. 来教你打造一个私人网盘。至于做什么用,就别多问啦
  3. linux 端口没有进程号,「linux专栏」何为端口号?端口号和进程号如何互查?答案在这里...
  4. 编程英语:常见代码错误 error 语句学习(3)
  5. Redis命令详解:Transactions
  6. OpenCV系列之轮廓属性 | 二十三
  7. [转]使你更有思想的20本书
  8. 2022-4-8 Leetcode 1114.按顺序打印
  9. 网络性能--速率,带宽,吞吐量
  10. 学习Linux命令(11)