[收]Windows Socket网络程序设计

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


 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值