发信人: richwxy(风吟)
整理人: wenbobo(2002-05-17 18:00:32), 站内信件
|
Y()类函数
. 这些函数是对BSD标准函数的扩充.函数WSACancelAsyncRequest()允许用户中止一个正
在执行的异步请求.
3.阻塞处理方法
WINSOCK提供了"钩子函数"负责处理Windows消息,使Windows的消息循环能
够继续.WIN
SOCK提供了两个函数(WSASetBlockingHook()和WSAUnhookBlockingHook())让应用程序设
置或取消自己的"钩子函数".函数WSAIsBlocking()可以检测是否阻塞,函数WSACancelBl
ockingCall()可以取消一个阻塞的调用.
4.错误处理
WINSOCK提供了两个WSAGetLastError()和WSASetLastError()来获取和设
置最近错误号
.
5.启动和终止
由于Windows Sockets的服务是以动态连接库WINSOCK.DLL形式实现的,所
以必须要先调
用WSAStartup()函数对Windows Sockets DLL进行初始化,协商WINSOCK的版本支持,并分
配必要的资源.在应用程序关闭套接口后,还应调用WSACleanup()终止对Windows Socket
s DLL的使用,并释放资源,以备下一次使用.
在这些函数中,实现Windows网络实时通信的关键是异步选择函数WSAAsyncSelect()
的使
用. 用法及详细说明参见第5.3.7.
3.3 Windows Sockets与UNIX套接口编程实例
下面是一个简单的基于连接的点对点实时通信程序.它由两部分组成,服务器在主机
UNI
X下直接运行, 客户机在Windows下运行.
3.3.1 SERVER介绍
由于SERVER是在UNIX下运行的,它对套接口的使用都是BSD的标准函数,程序也比较
简单
, 只有一段程序,下面简要解释一下.
首先,建立自己的套接口.在互连网的进程通信中,全局标识一个进程需要一个被称
为"半
相关"的三元组(协议,本地主机地址,本地端口号)来描述,而一个完整的进程通信实例则
需要一个被称为"相关"的五元组(协议, 本地主机地址,本地端口号,远端主机地址,远端
端口号)来描述.
s=socket(AF_INET, SOCK_STREAM, 0)
该函数建立指定地址格式,数据类型和协议下的套接口,地址格式为AF_INET(唯一支
持的
格式),数据类型SOCK_STREAM表示建立流式套接口,参数三为0,即协议缺省.
bind(s, (struct sockaddr *)&server, sizeof(server))
该函数将建立服务器本地的半相关,其中,server是sockaddr_in结构,其成员描述了
本地
端口号和本地主机地址,经过bind()将服务器进程在网上标识出来.
然后,建立连接.先是调用listen()函数表示开始侦听.再通过accept()调用等待接
收连
接.
listen(s,1)表示连接请求队列长度为1,即只允许有一个请求,若有多个请求,则出
现错
误,给出错误代码WSAECONNREFUSED.
ns = accept(s, (struct sockaddr *)&client, &namelen))
accept()阻塞(缺省)等待请求队列中的请求,一旦有连接请求来,该函数就建立一个
和s
有相同属性的新的套接口.client也是一个sockaddr_in结构,连接建立时填入请求连接的
套接口的半相关信息.
接下来,就可以接收和发送数据了.
recv(ns,buf,1024,0)
send(ns,buf,pktlen,0)
上面两个函数分别负责接收和发送数据,recv从ns(建立连接的套接口)接收数据放
入bu
f中,send则将buf中数据发送给ns.至于第四个参数,表示该函数调用方式,可选择MSG_DO
NTROUTE和MSG_OOB, 0表示缺省.
最后,关闭套接口.
close(ns);
close(s);
3.3.2 CLIENT介绍
客户端是在Windows上运行的,使用了一些Windows Sockets的扩展函数,稍微复杂一
些.
包括了.RC和.C两个文件,其中的主窗口函数ClientProc()是程序的主要部分,下面简单解
释一下.
首先,是在WinMain()中建立好窗口后,即向主窗口函数发一条自定义的WM_USER消息
, 做
相关的准备工作.在主窗口函数中,一接收到WM_USER消息,首先调用WSAStartup()函数初
始化Windows Sockets DLL,并检查版本号.如下:
Status = WSAStartup(VersionReqd, lpmyWSAData);
其中,VersionReqd描述了WINSOCK的版本(这里为1.1版),lpmyWSAData指向一个WSAD
ATA
结构,该结构描述了Windows Sockets的实现细节.
WSAStartup()之后,进程通过主机名(运行时命令行参数传入)获取主机地址,如下:
hostaddr = gethostbyname(server_address);
hostaddr指向hostent结构,内容参见5.2.1.
然后,进程就不断地消息循环,等待用户通过菜单选择"启动".这时,通过调用Client
()来
启动套接口.在Client()中,首先也是调用socket()来建立套接口.如下:
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
AlertUser(hWnd, "Socket Failed");
return (FALSE);
}
紧接着,调用WSAAsyncSelect()函数提名FD_CONNECT网络事件,如下:
if (!SetSelect(hWnd, FD_CONNECT))
return (FALSE);
SetSelect()主要就是调用WSAASyncSelect(),让Windows Sockets DLL在侦测到连
接建
立时,就发送一条UM_SOCK的自定义消息,使消息循环继续下去.如下:
BOOL SetSelect(HWND hWnd, long lEvent)
{
if (WSAAsyncSelect(s, hWnd, UM_SOCK, lEvent) == SOCKET_ERROR)
{
AlertUser(hWnd, "WSAAsyncSelect Failure.");
return (FALSE);
}
return (TRUE);
}
为建立连接,必须马上调用connect()如下,由于先调用了WSAASyncSelect(),connec
t()
便是非阻塞调用.进程发出连接请求后就不管了,当连接建立好后,WINSOCK DLL自动发一
条消息给主窗口函数,以使程序运行下去.
connect(s, (struct sockaddr FAR *)&dst_addr, sizeof(dst_addr));
窗口函数在收到UM_SOCK消息后,判断是由哪个网络事件引起的,第一次,必然是由连
接事
件引起的,这样,就会执行相应的程序段,同样调用SetSelect()来提名FD_WRITE事件.希望
在套接口可发送数据时接到消息.在收到FD_WRITE消息时,先调用send()发送数据,再调用
SetSelect()来提名FD_READ事件, 希望在套接口可接收数据是接到消息.在收到FD_READ
消息时,先调用recv()来接收数据再提名FD_WRITE事件,如此循环下去.直到发生连接关闭
的事件FD_CLOSE,这时就调用WSAAsyncSelect(s,hWnd,0,0)来停止异步选择.在窗口函数
接到WM_DESTROY消息时(即关闭窗口之前),先调用closesocket()(作用同UNIX 中的clos
e())来关闭套接口,再调用WSACleanup()终止Windows Sockets DLL,并释放资源.
3.3.3 源程序清单
程序1:CLIENT.RC
ClientMenu MENU
BEGIN
POPUP "&Server"
BEGIN
MENUITEM "&Start...", 101
MENUITEM "&Exit", 102
END
END
程序2:CLIENT.C
#define USERPORT 10001
#define IDM_START 101
#define IDM_EXIT 102
#define UM_SOCK WM_USER + 0X100
#include <alloc.h>
#include <mem.h>
#include <windows.h>
#include <winsock.h>
#define MAJOR_VERSION 1
#define MINOR_VERSION 2
#define WSA_MAKEWORD(x,y) ((y)*256+(x))
HANDLE hInst;
char server_address[256] = {0};
char buffer[1024];
char FAR * lpBuffer = &buffer[0];
SOCKET s = 0;
struct sockaddr_in dst_addr;
struct hostent far *hostaddr;
struct hostent hostnm;
struct servent far *sp;
int count = 0;
BOOL InitApplication(HINSTANCE hInstance);
long FAR PASCAL ClientProc(HWND hWnd, unsigned message, UINT wParam, LONG lP
aram);
void AlertUser(HWND hWnd, char *message);
BOOL Client(HWND hWnd);
BOOL ReceivePacket(HWND hWnd);
BOOL SetSelect(HWND hWnd, long lEvent);
BOOL SendPacket(HWND hWnd, int len);
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
MSG msg;
lstrcpy((LPSTR)server_address, lpCmdLine);
if (!hPrevInstance)
if (!InitApplication(hInstance))
return (FALSE);
hInst = hInstance;
hWnd = CreateWindow("ClientClass", "Windows ECHO Client", WS_OVERLAPPEDW
IND
OW,\
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL
, NULL,\
hInstance, NULL);
if (!hWnd)
return (FALSE);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
PostMessage(hWnd, WM_USER, (WPARAM)0, (LPARAM)0);
while (GetMessage(&msg, NULL, NULL, NULL))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
BOOL InitApplication(HINSTANCE hInstance)
{
WNDCLASS WndClass;
char *szAppName = "ClientClass";
// fill in window class information
WndClass.lpszClassName = (LPSTR)szAppName;
WndClass.hInstance = hInstance;
WndClass.lpfnWndProc = ClientProc;
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hIcon = LoadIcon(hInstance, NULL);
WndClass.lpszMenuName = "ClientMenu";
WndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
WndClass.style = CS_HREDRAW | CS_VREDRAW;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
// register the class
if (!RegisterClass(&WndClass))
return(FALSE);
return(TRUE);
}
long FAR PASCAL ClientProc(HWND hWnd, unsigned message, UINT wParam, LONG lP
aram)
{
int length, i;
WSADATA wsaData;
int Status;
switch (message)
{
case WM_USER:
{
WORD wMajorVersion, wMinorVersion;
LPWSADATA lpmyWSAData;
WORD VersionReqd;
int ret;
wMajorVersion = MAJOR_VERSION;
wMinorVersion = MINOR_VERSION;
VersionReqd = WSA_MAKEWORD(wMajorVersion,wMinorVersion);
lpmyWSAData = (LPWSADATA)malloc(sizeof(WSADATA));
Status = WSAStartup(VersionReqd, lpmyWSAData);
if (Status != 0)
{
AlertUser(hWnd, "WSAStartup() failed\n");
PostQuitMessage(0);
}
hostaddr = gethostbyname(server_address);
if (hostaddr == NULL)
{
AlertUser(hWnd, "gethostbyname ERROR!\n");
WSACleanup();
PostQuitMessage(0);
}
_fmemcpy(&hostnm, hostaddr, sizeof(struct hostent));
}
break;
case WM_COMMAND:
switch (wParam)
{
case IDM_START:
if (!Client(hWnd))
{
closesocket(s);
AlertUser(hWnd, "Start Failed");
}
break;
case IDM_EXIT:
// WSACleanup();
PostQuitMessage(0);
break;
}
break;
case UM_SOCK:
switch (lParam)
{
case FD_CONNECT:
if (!SetSelect(hWnd, FD_WRITE))
closesocket(s);
break;
case FD_READ:
if (!ReceivePacket(hWnd))
{
AlertUser(hWnd, "Receive Packet
Failed.\n");
closesocket(s);
break;
}
if (!SetSelect(hWnd, FD_WRITE))
closesocket(s);
break;
case FD_WRITE:
for (i = 0; i < 1024; i ++)
buffer[i] = (char)'A' + i % 26;
length = 1024;
if (!SendPacket(hWnd, length))
{
AlertUser(hWnd, "Packet Send Fai
led!\n");
closesocket(s);
break;
}
if (!SetSelect(hWnd, FD_READ))
closesocket(s);
break;
case FD_CLOSE:
if (WSAAsyncSelect(s, hWnd, 0, 0) == SOC
KET_ERROR)
AlertUser(hWnd, "WSAAsyncSelect
Failed.\n");
break;
default:
if (WSAGETSELECTERROR(lParam) != 0)
{
AlertUser(hWnd, "Socket Report F
ailure.");
closesocket(s);
break;
}
break;
}
break;
case WM_DESTROY:
closesocket(s);
WSACleanup();
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hWnd, message, wParam, lParam));
}
return(NULL);
}
void AlertUser(HWND hWnd, char *message)
{
MessageBox(hWnd, (LPSTR)message, "Warning", MB_ICONEXCLAMATION);
return;
}
BOOL Client(HWND hWnd)
{
memset(&dst_addr,'\0', sizeof (struct sockaddr_in));
_fmemcpy((char FAR *)&dst_addr.sin_addr,(char FAR *)hostnm.h_addr,host
nm.
h_length);
dst_addr.sin_family = hostnm.h_addrtype;
dst_addr.sin_port = htons(USERPORT);
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
AlertUser(hWnd, "Socket Failed");
return (FALSE);
}
if (!SetSelect(hWnd, FD_CONNECT))
return (FALSE);
connect(s, (struct sockaddr FAR *)&dst_addr, sizeof(dst_addr));
return (TRUE);
}
BOOL ReceivePacket(HWND hWnd)
{
HDC hDc;
int length;
int i1,i2,i3;
char line1[255], line2[255], line3[255];
count ++;
if ((length = recv(s, lpBuffer, 1024, 0)) == SOCKET_ERROR)
return (FALSE);
if (length == 0)
return (FALSE);
if (hDc = GetDC(hWnd))
{
i1 = wsprintf((LPSTR)line1, "TCP Echo Client No.%d", count);
i2 = wsprintf((LPSTR)line2, "Receive %d bytes",length);
i3 = wsprintf((LPSTR)line3, "Those are:%c, %c, %c, %c, %c, %c",b
uffer[0],b
uffer[1],buffer[2],buffer[100],buffer[1000],buffer[1023]);
TextOut(hDc, 10, 2, (LPSTR)line1, i1);
TextOut(hDc, 10, 22, (LPSTR)line2, i2);
TextOut(hDc, 10, 42, (LPSTR)line3, i3);
ReleaseDC(hWnd, hDc);
}
return (TRUE);
}
BOOL SetSelect(HWND hWnd, long lEvent)
{
if (WSAAsyncSelect(s, hWnd, UM_SOCK, lEvent) == SOCKET_ERROR)
{
AlertUser(hWnd, "WSAAsyncSelect Failure.");
return (FALSE);
}
return (TRUE);
}
BOOL SendPacket(HWND hWnd, int len)
{
int length;
if ((length = send(s, lpBuffer, len, 0)) == SOCKET_ERROR)
return (FALSE);
else
if (length != len)
{
AlertUser(hWnd, "Send Length NOT Match!");
return (FALSE);
}
return (TRUE);
}
程序3:SERVER.C
#include <sys/types.h>
#include <sys/mntent.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define USERPORT 10001
#define HOST_IP_ADDR "192.1.1.2"
main(int argc, char **argv)
{
char buf[1024];
struct sockaddr_in client;
struct sockaddr_in server;
int s;
int ns;
int namelen;
int pktlen;
if ((s=socket(AF_INET, SOCK_STREAM, 0))<0)
{
perror("Socket()");
return;
}
bzero((char *)&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(USERPORT);
server.sin_addr.s_addr = INADDR_ANY;
if (bind(s, (struct sockaddr *)&server, sizeof(server))<0)
{
perror("Bind()");
return;
}
if (listen(s,1)!=0)
{
perror("Listen()");
return;
}
namelen = sizeof(client);
if ((ns = accept(s, (struct sockaddr *)&client, &namelen)) ==-1)
{
perror("Accept()");
return;
}
for (;;)
{
if ((pktlen = recv(ns,buf,1024,0))<0)
{
perror("Recv()");
break;
}
else
if (pktlen == 0)
{
printf("Recv():return FAILED,connection is shut down!\n"
);
break;
}
else
printf("Recv():return SUCCESS,packet length = %d\n",pktl
en);
sleep(1);
if (send(ns,buf,pktlen,0)<0)
{
perror("Send()");
break;
}
else
printf("Send():return SUCCESS,packet length = %d\n",pktl
en);
}
close(ns);
close(s);
printf("Server ended successfully\n");
}
3.4 另一个精巧的应用程序实例-wshout
在本节中,我们通过一个经过精心选择的例子,进一步讨论一下Windows Sockets编
程技术。例如如何编制客户机或服务器程序,如何应用TCP有连接服务(流式套接口)或
UDP无连接服务(数据报套接口),如何进行阻塞或非阻塞方式的套接口操作等等,这些
都是经常碰到的问题。接下来要介绍的wshout程序,可以通过灵活地设置不同选项来达
到上述应用情况的任意组合,从而基本覆盖了应用Windows Sockets编程所可能碰到的问
题,具有很好的研究参考价值。
由于该程序思路清晰,结构精良,所以我们不打算很详细地剖析每一个语句,而只
是简要介绍一下整个程序的逻辑结构,并在源程序中加入适当的注释。我们相信,任何
具有基本C语言和Windows编程经验的读者,都能很轻松地读懂绝大部分内容。经过仔细
咀嚼和推敲后,更能得到一些编写优质程序的灵感。
该程序在FTP公司的PCTCP支撑环境下调试通过,不过只要读者拥有任何符合Window
s Sockets 1.1规范的实现,也能顺利执行该程序。
3.4.1 源程序目录
1. wshout.c wshout主程序
2. wshout.h wshout头文件
3. wshout.rc wshout资源文件
4. ushout.c UDP客户机程序
5. ulisten.c UDP服务器程序
6. tshout.c TCP客户机程序
7. tlisten.c TCP服务器程序
8. errno.c 获取WSAE*错误描述字符串程序
9. resolve.c 客户机/服务器启动程序
在编译本程序时,笔者用的是BC3.1,只需做一个PRJ工程文件,将上述.c文件及wi
nsock.lib包括进来就行了。请注意winsock.h应在include目录或当前目录中,winsock
.lib可利用winsock.dll通过implib工具来建立。如果读者使用其他的编译器,可自行作
相应的调整,在此不再赘述。 |
|