[收]Windows Socket网络程序设计

本文介绍了 Windows Socket 的基本概念及编程方法,包括其相对于 Berkeley Sockets 的改进之处,Socket 的三种类型及其在网络通信中的作用。并通过实例展示了如何在 Windows 环境下实现面向连接的 Socket 通信。
 Windows Socket网络程序设计
       Windows SocketsMicrosoft Windows的网络程序设计接口,它是从Berkeley Sockets扩展而来的。Windows Sockets在继承了Berkeley Sockets主要特征的基础上,又对它进行了重要扩充。这些扩充主要是提供了一些异步函数,并增加了符合Windows消息驱动特性的网络事件异步选择机制。这些扩充有利于应用程序开发者编制符合Windows编程模式的软件,它使在Windows下开发高性能的网络程序成为可能。
 Socket网络程序设计原理
       SocketBSD UNIX提供的网络应用编程接口,它采用客户/服务器的通讯机制,使网络客户方和服务器方通过Socket实现网络之间的连接和数据交换。Socket提供了一系列的系统调用,使用这些系统调用可以实现TCP, UDP,ICMPIP等多种网络协议之间的通讯。
       Socket有三种主要类型:stream sockets, datagram sockets raw sockets Stream socket接口定义了一种可靠的面向连接的服务,它实现了无差错无重复的顺序数据传输。它通过内置的流量控制解决了数据的拥塞,应用程序可以发送任意长度的数据,将数据当作字节流。Datagram socket接口定义了一种无连接的服务,数据通过相互独立的包进行传输,包的传输是无序的,并且不保证是否出错、丢失和重复。包长度是有限的(隐含长度为8,192Bytes,最大长度可设为32,768Bytes)。Raw socket接口允许对低层协议如IPICMP的直接存取,它主要用于新的网络协议实现的测试等。
       下面我们通过一个面向连接的传输发生的典型情况来说明socket网络通信的实现。
 
                                                                 

socket( )
bind( )
listen( )
accept( )
close( )
read( )
...
write( )
close( )
read( )
...
write( )
socket( )
connect( )
Connection Establishment
wait connection for client
……
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

6.3 面向连接的协议实现的Socket调用
 
       从图6.3可以看出,客户和服务器的关系不是对称的,服务器首先启动,然后在某一时间启动客户与服务器建立连接。服务器和客户开始都必须用调用socket()建立一个套接字(socket),然后服务器调用bind()将套接字与一个本地网络地址捆扎在一起,再用调用listen()使套接字处于一种被动的准备接收状态,同时规定它的请求队列长度,之后服务器就可以调用accept()来接收连接了。客户在建立套接字之后,便可以通过调用connect()和服务器建立连接。连接建立后,客户和服务器之间就可以通过连接发送和接收数据(调用read()write())。最后,待数据传送结束,双方调用close()关闭套接字。
6.3.2 WinSock对Socket的扩充
       BSD Socket支持阻塞(blocking)和非阻塞(non_blocking)两种工作方式。在阻塞方式下工作,connect()accept()read()recv()等调用在执行时都处于阻塞状态直到它成功或出错返回。在非阻塞方式下工作,这些调用是立即返回的,但是它们是否完成得靠查询才能知道。对于Windows这种非抢先多任务操作系统来说,这两种工作方式都是很难以接受的,为此,WinSock在尽量与BSD Socket保持一致外,又对它作了必要的扩充。
       WinSockBSD Socket的扩充主要是在基于消息、对网络事件的异步存取接口上。表6.2列出了WinSock扩充的函数功能。
 
6.2 WinSock扩充函数功能表
     
    
WSAAsyncGetHostByAddr()
 
标准Berkeley函数getXbyY的异步版本,例如:函数WSAAsyncGetHostByName()就是提供了标准Berkeley函数gethostbyname的一种基于消息的异步实现。
WSAAsyncGetHostByName()
WSAAsyncGetProtoByName()
WSAAsyncGetProtoByNumber()
WSAAsyncGetServByName()
WSAAsyncGetServByPort()
WSAAsyncSelect()
函数select()的异步版本
WSACancelAsyncRequest()
取消函数WSAAsyncGetXByY执行中的实例
WSACancelBlockingCall()
取消一个执行中的“阻塞”API调用
WSACleanup()
终止使用隐含的Windows Sockets DLL
WSAGetLastError()
获取Windows Sockets API的最近错误号
WSAIsBlocking()
检测隐含的Windows Sockets DLL是否阻塞了一个当前线索的调用
WSASetBlockingHook()
设置应用程序自己的“阻塞”处理函数
WSASetLastError()
设置Windows Sockets API的最近错误号
WSAStartup()
初始化隐含的Windows Sockets DLL
WSAUnhookBlockingHook()
恢复原来的“阻塞”处理函数
 
       从表6.2可以看出,WinSock的扩充功能可以分为如下几类:
       (1) 异步选择机制
       异步选择函数WSAAsyncSelect()允许应用程序提名一个或多个感兴趣的网络事件,所有非阻塞的网络I/O例程(如send()recv()),不管它是已经使用还是即将使用,都可作为WSAAsyncSelect()函数选择的候选。当被提名的网络事件发生时,Windows应用程序的窗口函数将收到一个消息,消息附带的参数指示被提名过的某一网络事件。
 
       (2) 异步请求例程
       异步请求例程允许应用程序用异步方式获取请求的信息,如WSAAsyncGetXByY()类函数允许用户请求异步服务,这些功能在使用标准Berkeley函数时是阻塞的。函数WSACancelAsyncRequest()允许用户终止一个正在执行的异步请求。
       (3) 阻塞处理方法
       WinSock在调用处于阻塞时进入一个叫“Hook”的例程,它负责处理Windows消息,使得Windows的消息循环能够继续。WinSock还提供了两个函数(WSASetBlockingHook()WSAUnhookBlockingHook())让用户能够设置和取消自己的阻塞处理例程。另外,函数WSAIsBlocking()可以检测调用是否阻塞,函数WSACancelBlockingCall()可以取消一个阻塞的调用。
       (4) 出错处理
       为了和以后的多线索环境(如Windows/NT)兼容,WinSock提供了两个出错处理函数WSAGetLastError()WSASetLastError()来获取和设置本线索的最近错误号。
       (5) 启动与终止
       WinSock的应用程序在使用上述WinSock函数前,必须先调用WSAStartup()函数对Windows Sockets DLL进行初始化,以协商WinSock的版本支持,并分配必要的资源。在应用程序退出之前,应该先调用函数WSACleanup()终止对Windows Sockets DLL的使用,并释放资源,以利下一次使用。
       在这些函数中,实现Windows网络实时通信的关键是异步选择函数WSAAsyncSelect()的使用,其原型如下:
int PASCAL FAR WSAAsyncSelect(SOCTET s, HWND hWnd, unsigned int wMsg, long lEvent);
它请求Windows Sockets DLL在检测到在套接字s上发生的lEvent事件时,向窗口hWnd发送一个消息wMsg。它自动地设置套接字s处于非阻塞工作方式。参数lEvent由表6.3所列事件的一个或多个组成。例如,我们要在套接字s读准备好或写准备好时接到通知,可以使用下面的语句:
rc = WSAAsyncSelect(s, hWnd, wMsg, FD_READ | FD_WRITE);
当套接字s上被提名的一个网络事件发生时,窗口hWnd将收到消息wMsg,变量lParam的低字指示网络发生的事件,高字指示错误码。应用程序就可以通过这些信息来决定自己的下一步动作。
 
6.3 异步选择网络事件表
                 
FD_READ
希望在套接字s收到数据(即读准备好)时接到通知
FD_WRITE
希望在套接字s可发送数据(即写准备好)时接到通知
FD_OOB
希望在套接字s上有带外数据到达时接到通知
FD_ACCEPT
希望在套接字s上有外部连接到来时接到通知
FD_CONNECT
希望在套接字s连接建立完成时接到通知
FD_CLOSE
希望在套接字s关闭时接到通知
       本章的例子是一个简单的点对点网络实时通信程序Echo。实例分两部分:客户程序wecho.c与服务器程序wechos.c。其工作过程是:服务器首先启动,它创建套接字之后等待客户的连接;客户在启动后,创建套接字,然后和服务器建立连接;连接建立后,客户接收键盘输入,然后将数据发送到服务器,服务器收到数据后,只是简单地发送回来,客户将收到的数据在窗口中显示;如此循环,当客户接收到的输入数据是字母“Q”时,它关闭连接和套接字后退出,服务器在用户关闭窗口时关闭套接字。
6.3.3.1 网络客户程序
       首先介绍客户程序,该源文件取名为wecho.c,其内容在下面列出。为了方便读者,在程序中关键部分采用中文注释的形式给出说明,读者可照这些注释加强对本节内容的理解。
 
/* This is a Sample for WinSock. It uses WinSock routine to communicate with Server through a port -- USERPORT.
Usage: wecho servername */
 
#include <winsock.h>
#include <windows.h>
#include <stdio.h>
#include <ctype.h>
#include <memory.h>
#include <string.h>
#include "wecho.h"
 
HANDLE       hInst, AsyncHnd;
char server_address[256] = {0};
char buffer[MAXGETHOSTSTRUCT];
char FAR *        lpBuffer = &buffer[0];   
SOCKET         s = 0;
int                        connected = 0;    
struct sockaddr_in dst_addr;
struct hostent  *hostaddr;  
struct hostent      hostnm;
unsigned short    port = USERPORT;    // 用户端口号,应大于1024。客户和服务器的端口号必须相同。
int sock_type = SOCK_STREAM;   
BOOL    InitWindow(HANDLE);
long FAR PASCAL ClientProc(HWND, unsigned, UINT, LONG);
VOID           AlertUser(HWND, LPSTR);
BOOL          Client(HWND);
BOOL                 set_select(HWND, long);      
BOOL          make_skt(HWND);
BOOL          connect_skt(HWND);
BOOL          send_pkt(HWND, int);    
int                    receive_pkt(HWND);
VOID           close_skt(VOID);
void                     DisplayInfo(HWND, int);
 
/****************************************************************************/
int PASCAL
WinMain( HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
         HWND hWnd;
         MSG     msg;
 
         lstrcpy((LPSTR) server_address, lpCmdLine);
               
         if (!hPrevInstance)
                   if (!InitWindow(hInstance))
                           return (NULL);
         hInst = hInstance;
 
         hWnd = CreateWindow("ClientClass", "Windows ECHO Client", WS_OVERLAPPEDWINDOW,
                        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                        NULL, NULL, hInstance, NULL);
 
         if (!hWnd)
                   return (NULL);
 
         ShowWindow(hWnd, nCmdShow);
         UpdateWindow(hWnd);
 
         EnableMenuItem(GetMenu(hWnd), IDM_STOP, MF_DISABLED | MF_GRAYED);
         PostMessage(hWnd, WM_USER,(WPARAM) 0, (LPARAM) 0);
                  
         while (GetMessage(&msg, NULL, NULL, NULL)) {
                  TranslateMessage(&msg);
                   DispatchMessage(&msg);
         }
         return (msg.wParam);
}
 
BOOL
InitWindow( HANDLE hInstance )
{
         WNDCLASSWndClass;
 
         WndClass.style = CS_HREDRAW | CS_VREDRAW;
         WndClass.lpfnWndProc = ClientProc;
         WndClass.cbClsExtra = 0;
         WndClass.cbWndExtra = 0;
         WndClass.hInstance = hInstance;
         WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
         WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
         WndClass.hbrBackground = COLOR_WINDOW + 1;
         WndClass.lpszMenuName = (LPSTR) "ClientMenu";
         WndClass.lpszClassName = (LPSTR) "ClientClass";
 
         return (RegisterClass((PWNDCLASS) & WndClass));
}
 
long FAR PASCAL
ClientProc( HWND hWnd, unsigned message, UINT wParam, LONG lParam)
{
         int                 length;
         WSADATA       wsaData;
         int     Status;
         LPSTR               msgstr;
 
         switch (message) {     
                   case WM_USER:// 用户初始化消息。启动Windows Sockets DLL,协商版本支持。
                         Status = WSAStartup(0x101, &wsaData);
                         if (Status != 0) {
                                  AlertUser(hWnd, "WSAStartup() failed /n");
                                  PostQuitMessage(0);
                         }
 
                      if (LOBYTE(wsaData.wVersion) != 1 ||          HIBYTE(wsaData.wVersion) != 1) {
                                     AlertUser(hWnd, "WSAStartup() Version not match/n");
                                     WSACleanup();
                                 PostQuitMessage(0);
                         }
     
                            //通过主机名异步获取主机信息。   
                       AsyncHnd = WSAAsyncGetHostByName(hWnd, UM_REQ,
                               server_address, buffer, MAXGETHOSTSTRUCT);
                            break;
                                    
                  case WM_COMMAND:
                            switch (wParam) {
                                 case IDM_START:  
                                               if (!Client(hWnd)) {
                                                        AlertUser(hWnd, "Start Failed");     
                                                    EnableMenuItem(GetMenu(hWnd), IDM_START, MF_ENABLED);
                                                        EnableMenuItem(GetMenu(hWnd),IDM_STOP,
                                                                                    MF_DISABLED|MF_GRAYED);
                                     }
                                     else {
                                               EnableMenuItem(GetMenu(hWnd), IDM_START,
                                                                 MF_DISABLED | MF_GRAYED);
                                               EnableMenuItem(GetMenu(hWnd), IDM_STOP, MF_ENABLED);
                                     }
                                     break;
 
                            case IDM_STOP:       
                                     WSACleanup(); //退出前注销对Windows Sockets DLL的使用。
                                     PostQuitMessage(0);
                                     break;
                            }
                            break;
 
                   case WM_CHAR:
                            if (wParam == 'q' | wParam == 'Q') {
                                     PostMessage(hWnd, WM_COMMAND, (WPARAM)IDM_STOP, (LPARAM)0);
                                     break;
                            }
                           PostMessage(hWnd, UM_SOCK,(WPARAM)wParam, (LPARAM)FD_USERWRITE);
                            break;
                  
                  case UM_REQ: //异步请求完成消息,结果在缓冲区中。
                         if (WSAGETASYNCERROR(lParam)) {
                                 AlertUser(hWnd, "WSAAsyncGetHostByName ERROR /n");
                                 WSACleanup();
                                 PostQuitMessage(0);
                        }
                        memcpy(&hostnm, lpBuffer, sizeof(struct hostent));
                            break;
                                    
                   case UM_SOCK: //异步选择消息。
                            switch (lParam) {
                                     case FD_CONNECT: //连接建立完成,置标志。
                                               connected = 1;
                                               break;
                                                       
                                     case FD_READ: //数据读准备好,读数据并显示。
                                            if ((length = receive_pkt(hWnd)) == 0)
                                            {
                                                     AlertUser(hWnd, "Receive Packet Failed");
                                                     close_skt();
                                                        break;
                                            }
                                            DisplayInfo(hWnd, length);
                                               break;
                                                       
                                     case FD_WRITE: //写准备好。
                                               break;
                                                       
                                     case FD_USERWRITE: //用户写数据消息,发送数据。
                                               if (!connected) {
                                                        AlertUser(hWnd, "Connection not created");
                                                     break;
                                           }      
                                              length = 1;   
                                               buffer[0] = (char)wParam;
                                         if (!(send_pkt(hWnd, length))) {
                                                    AlertUser(hWnd, "Packet Send Failed");
                                                     close_skt();
                                                     break;
                                           }      
                                               break;
                                                       
                                     case FD_CLOSE: //连接关闭,置标志。
                                               connected = 0;
                                               if (WSAAsyncSelect(s,hWnd,0,0) == SOCKET_ERROR)
                                                        AlertUser(hWnd, "WSAAsyncSelect Failed");
                                                    AlertUser(hWnd, "Socket has been closed");
                                                    EnableMenuItem(GetMenu(hWnd),IDM_START, F_ENABLED);
                                                        EnableMenuItem(GetMenu(hWnd), IDM_STOP, MF_DISABLED |
                                                                                   MF_GRAYED);
                                                        break;
                                                       
                                     default:
                                            if (WSAGETSELECTERROR(lParam) != 0) {
                                                       AlertUser(hWnd, "Socket Report Failure");
                                                     close_skt();
                                                     break;
                                           }    
                                           sprintf(msgstr, "lParam = 0x%lx, wParam = 0x%x",lParam,wParam);
                                           AlertUser(hWnd, msgstr);   
                                           break;
                                     }   
                                     break;
                                                    
                  case WM_DESTROY:
                            close_skt();
                        WSACleanup(); //退出前注销对Windows Sockets DLL的使用。
                        PostQuitMessage(0);
                         break;
 
                  default:
                        return (DefWindowProc(hWnd, message, wParam, lParam));
        }
        return (NULL);
}
 
VOID         //报警子程序。
AlertUser( HWND hWnd, LPSTR lpszWarning )
{
         MessageBox(hWnd, lpszWarning, "Windows Client", MB_OK | MB_ICONEXCLAMATION);
}
 
/****************************************************************************/
BOOL        //客户程序创建套接字并建立连接。
Client( HWND hWnd )
{
      EnableMenuItem(GetMenu(hWnd), IDM_START, MF_ENABLED);
      EnableMenuItem(GetMenu(hWnd), IDM_STOP, MF_ENABLED);
                  
         if (!make_skt(hWnd)) {         //创建套接字。
                   close_skt();
                   return(FALSE);
         }
 
    if (!set_select(hWnd, FD_CONNECT | FD_READ | FD_WRITE)) {//异步选择网络事件。
              close_skt();
               return(FALSE);
     }
   
         if (!connect_skt(hWnd)) {     //建立连接。
              close_skt();
              return(FALSE);
    }
    return(TRUE);
}
 
/****************************************************************************/
int     //接收数据子程序。
receive_pkt(HWND hWnd)
{
      int     errno, length, len;
 
         length = 1024;  
    if ((len = recv(s, lpBuffer, length, 0)) == SOCKET_ERROR) { //接收网络数据。
                   errno = WSAGetLastError(); 
                   if (errno==WSAEWOULDBLOCK)
                            return(TRUE);
                   AlertUser(hWnd, "Received Failed");
                close_skt();
                   return(FALSE);
      }      
    
         length = len;   
    if (length == 0) {
              AlertUser(hWnd, "Connection was Closed");
              close_skt();
    }
    return(length);
}
 
BOOL        //异步选择子程序。
set_select( HWND hWnd, long lEvent)
{
      if (WSAAsyncSelect(s, hWnd, UM_SOCK, lEvent) == SOCKET_ERROR) {
                  AlertUser(hWnd, "WSAAsyncSelect Failed");
                  return (FALSE);
      }
    return (TRUE);
}     
 
BOOL        //创建套接字子程序。
make_skt( HWND hWnd )
{
    if ((s = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) {
              AlertUser(hWnd, "Socket Failed");
                   return (FALSE);
    }
    return (TRUE);
}
 
BOOL        //建立连接子程序。
connect_skt( HWND hWnd )
{    
         int errno;   
                  
    memset((void *) &dst_addr, sizeof(dst_addr), 0);
    dst_addr.sin_family = AF_INET;
    dst_addr.sin_port = htons(port);
      dst_addr.sin_addr.s_addr = *((unsigned long *)hostnm.h_addr_list[0]);
 
      if (connect(s, (struct sockaddr *) & dst_addr, sizeof(dst_addr)) == SOCKET_ERROR) {
              errno = WSAGetLastError();
                   if (errno != WSAEWOULDBLOCK) {
                        AlertUser(hWnd, "Connect Failed");
                        close_skt();
                        return (FALSE);
                   }
      }
      return (TRUE);
}
 
BOOL        //发送数据子程序。
send_pkt( HWND hWnd, int len)
{
         int length;
                  
    if ((length = send(s, lpBuffer, len, 0)) == SOCKET_ERROR) {
               AlertUser(hWnd, "Send Failed");
                   close_skt();
                   return (FALSE);
      }   
      else if (length != len) {
              AlertUser(hWnd, "Send Length NOT Match!");
                   close_skt();
                   return(FALSE);
      }
     return (TRUE);
}
 
VOID         //关闭套接字子程序。
close_skt()
{
      if (s) {
              (VOID) closesocket(s);
                   s = 0;
      }
}
 
void //显示接收数据子程序。
DisplayInfo(HWND hWnd, int len)
{
         HDC dc;
      int l;
      char line[16];
      static int col = 0, row = 0;
 
         buffer[len] = 0;
         if (dc = GetDC(hWnd)) {
            l = wsprintf((LPSTR) line, "%s", lpBuffer);
             TextOut(dc, 10*col, 16*row, (LPSTR) line, l);
                   ReleaseDC(hWnd, dc);
      }
      col += len;
      if (col > 40) {
             col = 0;
                   row ++;
      }
}
6.3.3.2 网络服务器程序
       下面我们介绍服务器程序,源文件取名为wechos.c,其内容如下:
 
#include <winsock.h>
/* This is a Sample for WinSock. It uses WinSock routine as a server to communicate with Client through a port -- USERPORT.
Usage: wechos.exe */
 
#include <stdio.h>
#include <ctype.h>
#include <memory.h>
#include <string.h>
#include "wechos.h"
 
#define MAX_LENGTH      1024
 
HANDLE          hInst;
char      buffer[MAX_LENGTH];
char FAR *        lpBuffer = &buffer[0];
int                        length=0;
SOCKET s = 0, oldskt;
struct sockaddr_in dst_addr;
unsigned short    port = USERPORT;
int     sock_type = SOCK_STREAM;  
 
BOOL                 InitWindow(HANDLE);
long FAR PASCAL ServerProc(HWND, unsigned, UINT, LONG);
VOID           AlertUser(HWND, LPSTR);
BOOL           Server(HWND);
BOOL                 set_select(HWND, long);      
BOOL           make_skt(HWND);
BOOL           accept_skt(HWND);
BOOL           send_pkt(HWND, int);    
BOOL                 receive_pkt(HWND, int *);
VOID           close_skt(SOCKET);
 
/****************************************************************************/
int PASCAL
WinMain( HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
         HWND hWnd;
         MSG     msg;
 
         if (!hPrevInstance) {
                   if (!InitWindow(hInstance))
                            return (NULL);
         }
         else return(NULL);                         // Don't allow another SERVER is executing
 
         hInst = hInstance;
 
      hWnd = CreateWindow("ServerClass", "Windows ECHO Server", WS_OVERLAPPEDWINDOW,
                        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                        NULL, NULL, hInstance, NULL);
 
      if (!hWnd)
               return (NULL);
 
        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
InitWindow( HANDLE hInstance )
{
         WNDCLASS WndClass;
 
         WndClass.style = CS_HREDRAW | CS_VREDRAW;
         WndClass.lpfnWndProc = ServerProc;
         WndClass.cbClsExtra = 0;
         WndClass.cbWndExtra = 0;
         WndClass.hInstance = hInstance;
         WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
         WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
         WndClass.hbrBackground = COLOR_WINDOW + 1;
         WndClass.lpszMenuName = (LPSTR) "ServerMenu";
         WndClass.lpszClassName = (LPSTR) "ServerClass";
 
         return (RegisterClass((PWNDCLASS) & WndClass));
}
 
long FAR PASCAL
ServerProc( HWND hWnd, unsigned message, UINT wParam, LONG lParam)
{
         WSADATA wsaData;
         int Status;
 
         switch (message) {     
                   case WM_USER:         //用户消息,初始化Windows Sockets DLL,协商版本支持。
                            Status = WSAStartup(0x101, &wsaData);
                            if (Status != 0) {
                             AlertUser(hWnd, "WSAStartup() failed /n");
                              PostQuitMessage(0);
                            }
 
                            if (LOBYTE(wsaData.wVersion) != 1 ||          HIBYTE(wsaData.wVersion) != 1) {
                                     AlertUser(hWnd, "WSAStartup() Version not match/n");
                                     WSACleanup();
                                 PostQuitMessage(0);
                         }
   
                            if (!Server(hWnd)) {
                              AlertUser(hWnd, "Start Failed");     
                              EnableMenuItem(GetMenu(hWnd), IDM_START, MF_ENABLED);
                                     EnableMenuItem(GetMenu(hWnd), IDM_EXIT, MF_DISABLED | MF_GRAYED);
                            }
                            break;
                                    
                   case WM_COMMAND:
                            switch (wParam) {
                                     case IDM_START:
                                         if (!Server(hWnd)) {
                                                       AlertUser(hWnd, "Start Failed");     
                                                        EnableMenuItem(GetMenu(hWnd), DM_START,MF_ENABLED);
                                                        EnableMenuItem(GetMenu(hWnd), IDM_EXIT,
                                                                                    MF_DISABLED | MF_GRAYED);
                                               }
                                               break;
 
                            case IDM_EXIT:
                                     WSACleanup();           //退出前注销对Windows Sockets DLL的使用。
                                     PostQuitMessage(0);
                               break;
                   }
                   break;
 
                   case UM_SOCK:         //异步选择消息。
                            switch (lParam)        {
                                     case FD_ACCEPT:      //接收套接字上的连接。
                                               if (!accept_skt(hWnd)) {
                                                   AlertUser(hWnd,"Accept socket Failed");
                                                   break;
                                               }
                                               set_select(hWnd, FD_READ);
                                               break;
                                                       
                                     case FD_READ: //数据读准备好,接收网络数据。
                                               length = 1024;
                                           if (!receive_pkt(hWnd, &length)) {
                                               AlertUser(hWnd, "Receive Packet Failed");
                                               break;
                                               }
                                               set_select(hWnd, FD_WRITE);
                                               break;
        
                                     case FD_WRITE: //写准备好,发送数据。
                                          if (!(send_pkt(hWnd, length))) {
                                                        AlertUser(hWnd, "Send Packet Failed");
                                                        break;
                                            }      
                                 set_select(hWnd, FD_READ);
                                               break;
 
                                     case FD_CLOSE:         //连接关闭。
                                               if (WSAAsyncSelect(s,hWnd,0,0) == SOCKET_ERROR)
                                                   AlertUser(hWnd, "WSAAsyncSelect Failed");
                                               AlertUser(hWnd, "Socket has been closed");
                                               EnableMenuItem(GetMenu(hWnd), IDM_START,MF_ENABLED);
                                               break;
                                    
                            default:
                                  if (WSAGETSELECTERROR(lParam) != 0) {
                                           AlertUser(hWnd, "Socket Report Failure");
                                            close_skt(s);
                                               s = oldskt;
                                            EnableMenuItem(GetMenu(hWnd),IDM_START, F_ENABLED);
                                   }    
                                   break;
                   }   
                   break;
                                                       
                   case WM_DESTROY:
                            WSACleanup(); //退出前注销对Windows Sockets DLL的使用。
                            PostQuitMessage(0);
                            break;
 
                   default:
                            return (DefWindowProc(hWnd, message, wParam, lParam));
         }
         return (NULL);
}
 
VOID         //报警子程序。
AlertUser( HWND hWnd, LPSTR lpszWarning )
{
         MessageBox(hWnd, lpszWarning, "Windows Server", MB_OK| MB_ICONEXCLAMATION);
}
 
/****************************************************************************/
BOOL        //服务器程序创建套接字,并注册FD_ACCEPT网络事件。
Server( HWND hWnd )
{
      EnableMenuItem(GetMenu(hWnd), IDM_START,MF_DISABLED | MF_GRAYED);
      EnableMenuItem(GetMenu(hWnd), IDM_EXIT, MF_ENABLED);
                  
         if (!make_skt(hWnd))
                   return(FALSE);
 
      if (!set_select(hWnd, FD_ACCEPT))
                 return(FALSE);
      return(TRUE);
}
 
/****************************************************************************/
BOOL       //接收数据子程序。
receive_pkt(HWND hWnd, int *len)
{
      int     length, errno;
 
         length = *len;  
      if ((length = recv(s, lpBuffer, length, 0)) == SOCKET_ERROR) {
                   errno = WSAGetLastError(); 
                   if (errno != WSAEWOULDBLOCK)
                       return(FALSE);
    }      
         *len = length;   
         return(TRUE);
}
 
BOOL        //异步选择子程序。
set_select( HWND hWnd, long lEvent)
{
         if (WSAAsyncSelect(s, hWnd, UM_SOCK, lEvent) == SOCKET_ERROR) {
              AlertUser(hWnd, "WSAAsyncSelect Failed");
                   return (FALSE);
      }
      return (TRUE);
}     
  
BOOL        //创建套接字子程序。  
make_skt(HWND hWnd)
{
         SOCKADDR_IN   sin;
      unsigned long on=1;
 
      s = socket (AF_INET, SOCK_STREAM, 0); //创建套接字。
    if (s == INVALID_SOCKET) {
              AlertUser(hWnd, "Socket Failed");
            PostQuitMessage(0);
                   return(FALSE);
      }
 
      sin.sin_family = AF_INET;
      sin.sin_addr.s_addr = 0;
      sin.sin_port = htons (port);
      if (bind (s, (LPSOCKADDR) &sin, sizeof (sin))) { //建立本地连接。
              close_skt(s);
              PostQuitMessage(0);
              return(FALSE);
      }
   
         if (listen (s, 1)) return(FALSE);      //将套接字变为被动套接字,等待接收连接。
         return(TRUE);
}
 
BOOL        //接收套接字上连接。
accept_skt(HWND hWnd)
{
      SOCKET newskt;     
      struct sockaddr tcpaddr;
      int len;
    
         len = sizeof(struct sockaddr); 
      newskt = accept (s, (struct sockaddr far *)&tcpaddr, (int far *)&len);
      if (newskt == INVALID_SOCKET)
              return(FALSE);
      else {
                   oldskt = s;           //保存监听套接字。
              s = newskt;         //使用接收套接字用于数据传输。
      }
      return(TRUE);
}
 
BOOL        //发送数据子程序。
send_pkt( HWND hWnd, int len)
{
         if (send(s, lpBuffer, len, 0) == SOCKET_ERROR)
              return (FALSE);
      return (TRUE);
}
 
VOID         //关闭套接字子程序。
close_skt(SOCKET skt)
{
    (VOID) closesocket(skt);
}
 
       客户/服务器工作模式是Socket网络程序典型的模式,通过对这一实例程序的理解,我们可以基本掌握Windows Sockets网络程序设计的基本原理及实现方法。用户可以将此程序作为范例,通过对它们进行修改和扩充,设计出符合自己要求的应用程序。


 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值