同步
每个函数在下一条语句执行以前必须完成
异步
Windows的消息是异步的;不按照事先定义的顺序发生的。
当程序开始一个任务时;可以告诉Windows在任务完成时发送一条消息;收到消息时根据任务的完成结果再决定下一步做什么;处理完这条消息;控制权又返回给Windows;系统继续执行其他的任务。
同步模型中;当执行一些需要花费很长时间才能完成的功能时;程序会被阻塞;无法进行其他的操作;只能等待这个功能完成。
异步WinSock则不同;在执行一个费时的网络操作时;程序用WSAAsyncSelect向Windows系统注册一条消息;指明感兴趣的网络事件;告诉系统任务完成时用这条消息来通知程序。这样;正在进行的网络操作如果不能立即完成;会返回错误码;告诉系统正在处理而不会阻塞程序;程序还可以进行其他的各种操作。网络操作完成时;无论成功还是失败;应用程序的窗口过程会收到之前注册的消息;消息中有操作完成的结果;程序根据消息中的参数判断发生了什么网络事件;决定下一步的工作。
该函数提供一个异步I/O模型;使用它应用程序不会在调用某一个套接口函数时阻塞;函数会立即返回给调用者。
当要求的操作完成时;应用程序会收到消息;程序根据消息中指明的事件来决定做什么样的处理。
int WSAAPI WSAAsyncSelect(SOCKET s, HWND hWnd,u_int wMsg,long IEvent);
成功返回0;表明应用程序的事件注册成功;
失败返回SOCKET_ERROR;应用程序可以调用WSAGetLastError()得到具体的错误码。
WSAAsyncSelect要求WinSock监测套接口s上的事件;当检查到lEvent参数中规定的网络事件时;向窗口hWnd发送wMsg消息。
会自动把套接口设置为非阻塞模式;要再设为阻塞模式;应用程序必须再调用WSAAsyncSelect ;并且将IEvent 参数设置为0;清除与套接口相关的事件;然后调用ioctlsocket 设置为阻塞模式
程序感兴趣的事件必须一次设置完成; 后面的设置会覆盖前面的
套接口网络事件
当套接口上指定的发生网络事件的应用程序收到wMsg时;wParam是发生网络事件的套接口句柄;IParam 的低16字节表明发生了什么事件;高16位是错误码
WinSock定义了两个宏来提取网络事件和错误码
#define WSAGETSELECTEVENT(lParam) LOWORD(lParam)
#define WSAGETSELECTERROR(lParam) HIWORD(lParam)
调用WSAAsyncSelect的返回值与网络事件错误之间的区别
套接口上的事件具有继承性;套接口上的事件具有继承性;调用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
只有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消息的错误码 --> 表示对方是否是正常关闭还是放弃连接
Finger协议的主要功能是查询某一主机上的用户信息。
主机返回用户容易阅读的状态报告;包括用户名、终端位置、任务名称、空闲时间等。
Finger协议的知名端口号是79;协议的格式没有特别的要求;大部分情况下客户端只需要发送一个;命令行;。根据;命令行;和服务器的不同;客户端收到的信息会有所变化。
服务器一发送完数据就关闭连接。
如果客户端发送的是空行;即只有<CRLF>;服务器把当前使用系统的所有用户都发送给客户端。
如果客户端规定了用户名;如;Alice;;那么服务器应该只把这个用户的情况报告给客户端。
如果用户名与服务器上的多个登录用户匹配;这些匹配的用户信息应该都发送给客户端。
当查询的用户没有登录时;服务器报告用户名、最后的注销时间。
用户也可以留下一条短消息;服务器在应答中包含这条消息。
常用的操作系统;如Linux、Windows都带有Finger程序;基本格式为;finger [user];host。其中user规定了想要查询的用户;它是可选的;没有这个参数;则显示服务器上所有用户的信息。而host指定了要查询的服务器。
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 ;
; /* 行结束符 */
#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 ...
;));
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.
;), 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; // 在后方添加一个; ;, 也是buflen = FINGER_MAX_BUF -1的原因
LogPrintf(TEXT(;recv >: %s
;), cBuf);
/* 搜索用户名结尾的 ;
; */
pEnd = strstr(cBuf, FINGER_LINE_END);
if ((pEnd != NULL) && (pEnd != cBuf))
*pEnd = 0; /*结尾的 ;
; 换成 0, 便于查找 */
for (j = 0; j < iCount; j;;) /* 查找用户信息 */
{
pUser = &FingerUser[j];
if (strcmp(cBuf, FINGER_LINE_END) == 0) /* 所有用户 */
buflen = sprintf(cSendBuf, %s
;, pUser->szUser);
else if (strcmp(cBuf, pUser->szUser) == 0) /* 特定用户 */
buflen = sprintf(cSendBuf, %s: %s, %s
;, pUser->szUser,
pUser->szFullName, pUser->szMessage);
else
continue;
iFind;;;
result = send(clnt_soc, cSendBuf, buflen, 0);
if (result > 0)
LogPrintf(TEXT(;send <: %s
;), cSendBuf);
}
if (!iFind)
send(clnt_soc, ;The user is not found.
;, 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 ;
END
2 TEXTINCLUDE
BEGIN
;#include ;;winres.h;;
;
; ;
END
3 TEXTINCLUDE
BEGIN
;
;
; ;
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客户端程序FingerClnt根据用户的输入向服务器发送请求;查询对应用户的信息。
程序可以输入的信息有两个;用户名和主机。
用户名是要向服务器查询的用户名称;
用户名可以为空;为空时;客户端只向服务器发送一个空行“ ”;要求得到服务器所有已登录用户的信息。
用户名不为空时;程序取得用户名;在后面追加协议要求的行结束符“ ”;调用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
;, 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; // 末尾添加一个 ; ;
LogPrintf(TEXT(%s
;), 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);
}