精华区 [关闭][返回]

当前位置:网易精华区>>讨论区精华>>编程开发>>C/C++>>网络与通讯>>: WINSOCK编程续一

主题:: WINSOCK编程续一
发信人: 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工具来建立。如果读者使用其他的编译器,可自行作
相应的调整,在此不再赘述。

[关闭][返回]