WinSock异步编程
简介
- 同步
每个函数在下一条语句执行以前必须完成 - 异步
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服务器程序
- 服务器程序固定在知名端口79上侦听
- 预先定义用户信息 --> 用于测试
- 目前程序只有开始菜单,没有结束或者停止菜单
- 开始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); else continue; 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 UNICODE iBufLen = _vsnwprintf(szBuffer, FINGER_MAX_BUF, szFormat, pVaList); #else iBufLen = _vsnprintf(szBuffer, FINGER_MAX_BUF, szFormat, pVaList); #endif va_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 BEGIN POPUP "CMD" BEGIN MENUITEM "Start", IDM_START MENUITEM SEPARATOR MENUITEM "Exit", IDM_EXIT END 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 UNICODE iBufLen = _vsnwprintf(szBuffer, FINGER_MAX_BUF, szFormat, pVaList); #else iBufLen = _vsnprintf(szBuffer, FINGER_MAX_BUF, szFormat, pVaList); #endif va_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); }