線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:3606
推到 Plurk!
推到 Facebook!

第八章WinSock 2應用實例

 
conundrum
尊榮會員


發表:893
回覆:1272
積分:643
註冊:2004-01-06

發送簡訊給我
#1 引用回覆 回覆 發表時間:2004-05-30 01:00:27 IP:61.64.xxx.xxx 未訂閱
 
第八章WinSock 2應用實例
http://www.cic.tsinghua.edu.cn/sys/book2/eight8.htm
        
本章將用兩個例子來介紹Windows Sockets 2函數的使用。 第一個例子是簡單的點對點網路即時通信程式ws2echo,它介紹基本的Windows Sockets 2函數的使用;第二個是多址廣播通信程式multichat,它介紹如何使用Windows Sockets 2多址廣播通信函數。
8.1  WinSock 2基本函數的使用
         為了介紹WinSock 2基本函數的使用,我們繼續前面的例子——簡單的點對點網路即時通信程式echo。該實例分兩部分:客戶程式與伺服器程式。其工作過程是:伺服器首先啟動,它創建套接字之後等待客戶的連接;客戶啟動後,創建套接字,然後和伺服器建立連接;連接建立後,客戶接收鍵盤輸入,收到一行後將資料發送到伺服器,伺服器收到資料後,只是簡單地發送回來,客戶將收到的資料在視窗中顯示;直到用戶關閉應用程式。
8.1.1  客戶程式
        首先介紹客戶程式,該原始檔案取名為ws2echoc.c,其內容在下面列出。為了方便讀者,在程式中關鍵部分採用中文注釋的形式給出說明,讀者可照這些注釋加強對第七章內容的理解。
#ifndef _WINSOCKAPI_
#define _WINSOCKAPI_   /* Prevent inclusion of winsock.h in windows.h */
#endif
#include 
#include 
#include 
#include 
#include "ws2echo.h"
 
SOCKET        Sockets;        // 套接字描述符
HWND        GlobalhWindow;
char    EchoClassStr[] = "WS2EchoClient"; 
char        sbuf[BUFFER_LENGTH], rbuf[BUFFER_LENGTH];        // 發送/接收緩衝區
DWORD        TotalByteSent;                // 總的發送位元組數
WSABUF  SendBuf, RecvBuf;        // 發送/接收緩衝區結構
 
int WINAPI
WinMain(
    IN HINSTANCE InstanceHandle,
    IN HINSTANCE PrevInstanceHandle,
    IN LPSTR  CmdLine,
    IN int    CmdShow)
{
    MSG      Message;      
 
    if (!InitWindow(InstanceHandle, PrevInstanceHandle)) {
        MessageBox(NULL, "Couldn't Initialize Echo!", "Error",
                        MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
                return(0);
    }
 
    GlobalhWindow = CreateWindow(EchoClassStr, "WinSock 2 Echo Client",
                WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                CW_USEDEFAULT, NULL, NULL, InstanceHandle, NULL);
 
    if (GlobalhWindow == NULL) {
           MessageBox(NULL, "CreateWindow()!", "Error", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
           return(0);
    }
 
    if (!InitWS2()) // 啟動WinSock 2.
                return(0);
 
    if (!MakeConnection()) // 與伺服器建立連接
                return(0);
 
    ShowWindow(GlobalhWindow, CmdShow);
    UpdateWindow(GlobalhWindow);
 
    while (GetMessage(&Message, NULL, 0, 0)) {
        TranslateMessage(&Message);
                DispatchMessage(&Message);
    }
 
    WSACleanup();        // 收到WM_QUIT消息後,結束WinSock 2.
    return(Message.wParam);
} // WinMain()
 
long CALLBACK
MainWndProc(
    IN HWND   WindowHandle,
    IN UINT   Message,
    IN WPARAM WParam,
    IN LPARAM LParam)
{
        int retcode;
        char MsgText[128];
 
    switch (Message) {
                case UM_SOCK:
                        switch (LParam) {
                                case FD_CONNECT:        //連接建立完成,置標誌。
                                        WSAAsyncSelect(Sockets, GlobalhWindow, UM_SOCK, FD_READ);
                                        break;
                                case FD_READ:        //資料讀準備好,接收網路資料。
                                        retcode = DoRecv(Sockets, &RecvBuf);
                                        if (retcode == 1) 
                                                DisplayInfo(WindowHandle, &RecvBuf);
                                        break;
                                case FD_WRITE: //寫準備好,發送資料。
                                        retcode = DoOverlappedCallbackSend(Sockets, &SendBuf);
                                        SendBuf.len = 0;
                                break;
                                case FD_CLOSE:        //連接關閉。
                                          closesocket(Sockets);
                                      break;
                               default:
                                           if (WSAGETSELECTERROR(LParam) != 0) {
                                                sprintf(MsgText, "Client: LParam=%d",WSAGETSELECTERROR(LParam));
                                                MessageBox(GlobalhWindow, MsgText,
                                                         "Error", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
                                                  closesocket(Sockets);
                                        }
                                           break;
                        }
                        break;
                    case WM_CHAR:
                       if (WParam == 0x0d) // 用戶鍵入回車鍵則發送一行。
                           PostMessage(GlobalhWindow, UM_SOCK, (WPARAM) WParam, (LPARAM)FD_WRITE);
                        else 
                                SendBuf.buf[SendBuf.len  ] = (char)WParam;
                       break;
                case WM_DESTROY:        // 關閉監聽套接字並退出應用程式
                        closesocket(Sockets);
                        PostQuitMessage(0);
                        break;
                default:
                        return (DefWindowProc(WindowHandle, Message, WParam, LParam));
        } // switch(Message)
 
        return (0);
} // MainWndProc()
 
/*
 * 啟動WinSock 2,協商WinSock版本號,初始化接收發送緩衝區。
 * 成功返回TRUE,失敗返回FALSE。
 */
BOOL
InitWS2(void)
{
    int           Error;              // catches return value of WSAStartup
    WORD          VersionRequested;   // passed to WSAStartup
    WSADATA       WsaData;            // receives data from WSAStartup
    BOOL          ReturnValue = TRUE; // return value
 
    VersionRequested = MAKEWORD(VERSION_MAJOR, VERSION_MINOR);
    Error = WSAStartup(VersionRequested, &WsaData); // 啟動WinSock 2
    if (Error) {
        MessageBox(GlobalhWindow, "Could not find high enough version of WinSock",
                        "Error", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        ReturnValue = FALSE;
    } else {// 確認WinSock 2 DLL支援我們需要的精確版本號,否則,調用WSACleanup()。
        if (LOBYTE(WsaData.wVersion) != VERSION_MAJOR 
                         || HIBYTE(WsaData.wVersion) != VERSION_MINOR) {
            MessageBox(GlobalhWindow, "Could not find the correct version of WinSock",
                                "Error",  MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
            WSACleanup();
            ReturnValue = FALSE;
        }
    }
 
        SendBuf.len = RecvBuf.len = 0; // 初始化接收/發送緩衝區。
        SendBuf.buf = sbuf;
        RecvBuf.buf = rbuf;
    return(ReturnValue);
} // InitWS2()
 
/*
 * 和伺服器建立連接。成功返回TRUE,失敗返回FALSE。
 */
BOOL
MakeConnection(void)
{
    int             ConnectStatus;    // the return value of WSAConnect
    int             Error;            // gets error values
    BOOL            ReturnValue;      // holds the return value
    struct sockaddr_in SockAddr;      // socket address for WSAConnect
    int             SockAddrLen;      // the length of the above
    UCHAR MsgText[MAX_ERROR_TEXT];
 
        Sockets = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (Sockets == INVALID_SOCKET) {
        sprintf(MsgText, "Could not open a socket. [%x]", WSAGetLastError());
        MessageBox(GlobalhWindow, MsgText, "Non-fatal error.", MB_OK | MB_SETFOREGROUND);
        return(FALSE);
    }
 
        WSAAsyncSelect(Sockets, GlobalhWindow, UM_SOCK, FD_CONNECT); // 設置連接建立的事件通知
 
    SockAddr.sin_family = AF_INET;
    SockAddr.sin_port = htons(UserPort);
           SockAddr.sin_addr.s_addr = inet_addr(ServerName);
    SockAddrLen = sizeof(SockAddr);
 
        // 和遠端伺服器建立連接。
         ConnectStatus = WSAConnect(Sockets, (struct sockaddr *)&SockAddr, SockAddrLen, 
                 NULL, NULL, NULL, NULL);
    if (ConnectStatus == SOCKET_ERROR) {
        Error = WSAGetLastError();
        if (Error != WSAEWOULDBLOCK) { // 錯誤WSAEWOULDBLOCK意味著連接稍後建立
            wsprintf(MsgText, "WSAConnect failed.  Error code: %d", Error);
            MessageBox(GlobalhWindow, MsgText, "Error", 
                                 MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
            ReturnValue = FALSE;
        }
    } 
    
        return(ReturnValue);
} // MakeConnection()
 
/*
 * 接收資料。
 */
int
DoRecv(SOCKET Socket, LPWSABUF lpRecvBuffer)
{
    DWORD  NumBytes;        // stores how many bytes we received
    int    Error;                // gets error values
    int    Result;                // gets return value from WSARecv
    DWORD  Flags=0;                // flags for WSARecv
    int    ReturnValue = 1;                // returnValue
 
    lpRecvBuffer->len = BUFFER_LENGTH - 1;
    Result = WSARecv(Socket, lpRecvBuffer, 1, &NumBytes, &Flags, NULL, NULL); // 接收資料
 
    if (Result == SOCKET_ERROR) {
        Error = WSAGetLastError();
        switch (Error) {
                        case WSAENETRESET:  
                        case WSAECONNRESET:
                                ReturnValue = -1;        // 連接被重置
                                break;
                        case WSAEWOULDBLOCK:        // 沒有資料可供讀取
                                ReturnValue = 0;
                                break;
                        default:        // 其他錯誤
                                ReturnValue = -2;
                                break;
        }
    }
        
        lpRecvBuffer->len = NumBytes;
    return(ReturnValue);
} // DoRecv()
 
/*
 * 使用回調函數的重疊發送方式發送資料。
 */
int
DoOverlappedCallbackSend(
        SOCKET Socket,
    LPWSABUF  Buffers)    
{
    int             Size = 0;      // how many bytes we send
    int             Error;         // return value of WSASend
    int             Errno;         // result of WSAGetLastError
    DWORD           BytesSent;     // needed in WSASend
    int             ReturnValue = 1;   // the return value
        WSAOVERLAPPED        Overlapped;
 
    // 發送資料。完成常式設置為函數SendCompFunc()。
        Error = WSASend(Socket, Buffers, 1, &BytesSent, 0, &Overlapped, SendCompFunc);
 
    if (Error == SOCKET_ERROR) {
        Errno = WSAGetLastError();
        if (Errno == WSAEWOULDBLOCK)  
                    // WSAEWOULDBLOCK意味著必須等待FD_WRITE事件才能發送
            ReturnValue = 0;
        else if (Errno == WSA_IO_PENDING)  // 重疊操作成功初始化
            ReturnValue = 1;
        else // 其他錯誤
            ReturnValue = -1;
    }
 
    // 如果沒有錯誤,意味著發送操作立即完成。
    return(ReturnValue);
} // DoOverlappedCallbackSend()
 
/*
 * 完成常式在已成功初始化的重疊操作完成時被調用。它用來統計發送的位元組數。
 * 參數:
 *   Error -- 提供重疊操作的完成狀態。
 *   BytesTransferred -- 提供實際發送的位元組數。
 *   Overlapped -- 提供一個指向WSAOVERLAPPED結構的指標,其hEvent域可以用來
 *           給完成常式傳遞上下文資訊。
 *   Flags -- 未用。
 */
void CALLBACK
SendCompFunc(
    IN DWORD           Error,
    IN DWORD           BytesTransferred,
    IN LPWSAOVERLAPPED Overlapped,
    IN DWORD           Flags)
{
        TotalByteSent  = BytesTransferred; // 統計發送位元組數。
        if (Error) 
        MessageBox(NULL, "Error during overlapped send.", "Error", MB_OK | MB_SETFOREGROUND);
} // SendCompFunc()
 
void        //顯示接收資料副程式。
DisplayInfo(HWND hWnd, WSABUF * lpBuffer)
{
        HDC        dc;
           int  l;
    char line[128];
    static int row = 0;
 
        lpBuffer->buf[lpBuffer->len] = 0;
        if (dc = GetDC(hWnd)) {
               l = wsprintf((LPSTR) line, "%s",lpBuffer->buf);
              TextOut(dc, 10, 16*row, (LPSTR) line, lpBuffer->len);
        ReleaseDC(hWnd, dc);
           }
           row   ;
}
 
BOOL
InitWindow(
    IN HINSTANCE InstanceHandle,
    IN HINSTANCE PrevInstanceHandle)
{
    WNDCLASS WndClass;           // window class structure
 
    if (!PrevInstanceHandle) {
        WndClass.style         = CS_HREDRAW | CS_VREDRAW;
        WndClass.lpfnWndProc   = MainWndProc;
        WndClass.cbClsExtra    = 0;
        WndClass.cbWndExtra    = 0;
        WndClass.hInstance     = InstanceHandle;
        WndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
        WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
        WndClass.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE   1);
        WndClass.lpszMenuName  = NULL;
        WndClass.lpszClassName = EchoClassStr;
        if (!RegisterClass(&WndClass)) 
            return(FALSE);
        }
        
        return(TRUE);
}
 
8.1.2  伺服器程式
        伺服器程式原始檔案名為ws2echos.c,其內容在下面列出。為了方便讀者,在程式中關鍵部分採用中文注釋的形式給出說明,讀者可照這些注釋加強對第七章內容的理解。
#ifndef _WINSOCKAPI_
#define _WINSOCKAPI_   /* Prevent inclusion of winsock.h in windows.h */
#endif
#include 
#include 
#include 
#include 
#include "ws2echo.h"
 
SOCKET        Sockets;        // 伺服器套接字
HWND        GlobalhWindow;        
char    EchoClassStr[] = "WS2EchoServer"; 
WSABUF        RecvBuf;        // 接收緩衝區結構
char        rbuf[BUFFER_LENGTH];        // 接收緩衝區
 
int WINAPI
WinMain(
    IN HINSTANCE InstanceHandle,
    IN HINSTANCE PrevInstanceHandle,
    IN LPSTR  CmdLine,
    IN int    CmdShow)
{
    MSG      Message;    
 
    if (!InitWindow(InstanceHandle, PrevInstanceHandle)) 
                return(0);
 
    GlobalhWindow = CreateWindow(EchoClassStr, "WinSock 2 Echo Server",
                WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                NULL, NULL, InstanceHandle, NULL);
 
    if (GlobalhWindow == NULL) 
                return(0);
 
    if (!InitWS2())  // 啟動WinSock 2.
                return(0);
 
    if (!DoListen())  // 建立監聽套接字
                return(0);
 
    ShowWindow(GlobalhWindow, CmdShow);
    UpdateWindow(GlobalhWindow);
 
    while (GetMessage(&Message, NULL, 0, 0)) {
             TranslateMessage(&Message);
             DispatchMessage(&Message);
    }
 
    WSACleanup(); // 收到WM_QUIT消息後,結束WinSock 2.
    return(Message.wParam);
} // WinMain()
 
long CALLBACK
MainWndProc(
    IN HWND   WindowHandle,
    IN UINT   Message,
    IN WPARAM WParam,
    IN LPARAM LParam)
{
        static SOCKET Socket;        // 數據套接字
        int retcode;
        char MsgText[128];
 
    switch (Message) {
                case UM_SOCK:
                        switch (LParam) {
                                case FD_ACCEPT: // 處理外來連接請求
                                        Socket = HandleAcceptMessage(LParam);
                                        break;
                                case FD_READ:        //資料讀準備好,接收網路資料。
                                        retcode = DoRecv(Socket, &RecvBuf);
                                        if (retcode == 1)
                                                retcode = DoOverlappedEventSend(Socket, &RecvBuf);
                                case FD_WRITE: //寫準備好,發送資料。
                                        DoOverlappedEventSend(Socket, &RecvBuf);
                                break;
                                case FD_CLOSE:        //連接關閉。
                                          closesocket(Socket);
                                      break;
                               default:
                                   if (WSAGETSELECTERROR(LParam) != 0) {
                                          sprintf(MsgText, "SERVER: Error Code = %d",
                                                 WSAGETSELECTERROR(LParam));
                                          MessageBox(GlobalhWindow, MsgText, "Error", 
                                                 MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
                                        }
                                           break;
                        }
                        break;
                case WM_DESTROY: // 關閉監聽套接字並退出應用程式
                        closesocket(Sockets);
                        PostQuitMessage(0);
                        break;
                default:
                        return (DefWindowProc(WindowHandle, Message, WParam, LParam));
        } // switch(Message)
 
        return (0);
} // MainWndProc()
 
 
/*
 * 啟動WinSock 2,協商WinSock版本號。成功返回TRUE,失敗返回FALSE。
 */
BOOL
InitWS2(void)
{
    int           Error;              // catches return value of WSAStartup
    WORD          VersionRequested;   // passed to WSAStartup
    WSADATA       WsaData;            // receives data from WSAStartup
    BOOL          ReturnValue = TRUE; // return value
 
    VersionRequested = MAKEWORD(VERSION_MAJOR, VERSION_MINOR);
    Error = WSAStartup(VersionRequested, &WsaData); // 啟動WinSock 2
    if (Error) {
        MessageBox(GlobalhWindow, "Could not find high enough version of WinSock",
                        "Error", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        ReturnValue = FALSE;
    } else { // 確認WinSock 2 DLL支援我們需要的精確版本號,否則,調用WSACleanup()。
         if  (LOBYTE(WsaData.wVersion) != VERSION_MAJOR 
                         || HIBYTE(WsaData.wVersion) != VERSION_MINOR) {
            MessageBox(GlobalhWindow, "Could not find the correct version of WinSock",
                "Error",  MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
            WSACleanup();
            ReturnValue = FALSE;
        }
    }
 
        RecvBuf.len = 0; // 初始化接收緩衝區
        RecvBuf.buf = rbuf;
    return(ReturnValue);
} // InitWS2()
 
/*
 * 創建伺服器套接字,並使之監聽外來連接。成功返回TRUE,失敗返回FALSE。
 */
BOOL
DoListen(void)
{
    struct sockaddr_in SockAddr; // 本地套接字地址
    int  Error;   
    char textBuf[MAX_ERROR_TEXT];
 
        // 創建流式重疊套接字
        Sockets = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (Sockets == INVALID_SOCKET) {
        sprintf(textBuf, "Could not open a socket. [%x]", WSAGetLastError());
        MessageBox(GlobalhWindow, textBuf, "Non-fatal error.", MB_OK | MB_SETFOREGROUND);
        return(FALSE);
    }
 
        memset(&SockAddr, 0, sizeof(SockAddr));
           SockAddr.sin_family = AF_INET;
           SockAddr.sin_addr.s_addr = 0;
           SockAddr.sin_port = htons (UserPort);
    Error = bind (Sockets, (LPSOCKADDR) &SockAddr, sizeof (SockAddr)); //建立本地連接。
        if (Error == SOCKET_ERROR) {
                sprintf(textBuf, "Could not bind the socket. [%x]", WSAGetLastError());
                MessageBox(GlobalhWindow, textBuf, "Non-fatal error", MB_OK | MB_SETFOREGROUND);
            closesocket(Sockets);
                return(FALSE);
        }
        
    Error = WSAAsyncSelect(Sockets, GlobalhWindow, UM_SOCK, FD_ACCEPT); // 設置非同步通知事件
        if (Error == SOCKET_ERROR) {
                MessageBox(GlobalhWindow, "Error: WSAAsyncSelect()", "Non-fatal error", 
                         MB_OK | MB_SETFOREGROUND);
              closesocket(Sockets);
                return(FALSE);
    }
 
        Error = listen(Sockets, SOMAXCONN); //將套接字變為被動套接字,等待接收連接。
        if (Error == SOCKET_ERROR) {
                MessageBox(GlobalhWindow, "Error: listen()", "Non-fatal error", 
                         MB_OK | MB_SETFOREGROUND);
               closesocket(Sockets);
                return(FALSE);
        }
 
        return(TRUE);
} // ListenAll()
 
/*
 * 處理FD_ACCEPT網路消息,建立與用戶端的連接。返回接收的資料套接字。
 */
SOCKET
HandleAcceptMessage(IN LPARAM LParam)
{
    SOCKET Socket;
        struct sockaddr address;
        int address_len;
    int Error;    
    char MsgText[MAX_ERROR_TEXT];
 
    Error = WSAGETSELECTERROR(LParam); // 檢查是否在試圖連接時有錯誤發生
    if (Error) {
        if (Error == WSAENETDOWN)  // 網路系統失敗
            MessageBox(GlobalhWindow, "The network is down!", "Uh-oh",
                                MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        else
            MessageBox(GlobalhWindow, "Unknown error on FD_ACCEPT", "Error",
                                MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        return(INVALID_SOCKET);
    }
 
        address_len = sizeof (address);
        Socket = WSAAccept(Sockets, &address, &address_len, NULL, (DWORD)NULL);
 
    if (Socket == INVALID_SOCKET) {
        Error = WSAGetLastError();
        if (Error == WSAECONNREFUSED) // AcceptCondFunc返回CF_REJECT...
            MessageBox(GlobalhWindow, "The connection attempt has been refused.",
                                "Connection refused.", MB_OK | MB_SETFOREGROUND);
        else { // An unexpected error code.
            wsprintf(MsgText, "WSAAccept failed.  Error code: %d", Error);
            MessageBox(GlobalhWindow, MsgText, "Error", 
                         MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
        }
                return(INVALID_SOCKET);
    }
 
    WSAAsyncSelect(Socket, GlobalhWindow, UM_SOCK, FD_READ | FD_WRITE | FD_CLOSE);
    return(Socket);
} // HandleAcceptMessage()
 
 
/*
 * 接收資料。
 */
int
DoRecv(SOCKET Socket, LPWSABUF lpRecvBuffer)
{
    DWORD  NumBytes;        // stores how many bytes we received
    int    Error;                // gets error values
    int    Result;                // gets return value from WSARecv
    DWORD  Flags=0;                // flags for WSARecv
    int    ReturnValue = 1;                // returnValue
 
    lpRecvBuffer->len = BUFFER_LENGTH - 1;
    Result = WSARecv(Socket, lpRecvBuffer, 1, &NumBytes, &Flags, NULL, NULL); // 接收資料
 
    if (Result == SOCKET_ERROR) {
        Error = WSAGetLastError();
        switch (Error) {
                        case WSAENETRESET:  
                        case WSAECONNRESET:
                                ReturnValue = -1;        // 連接被重置
                                break;
                        case WSAEWOULDBLOCK:        // 沒有資料可供讀取
                                ReturnValue = 0;
                                break;
                        default:        // 其他錯誤
                                ReturnValue = -2;
                                break;
        }
    }
        
        lpRecvBuffer->len = NumBytes;
    return(ReturnValue);
} // DoRecv()
 
 
/*
 * 使用事件通知的重疊發送方式發送資料。
 */
int
DoOverlappedEventSend(SOCKET Socket, LPWSABUF  Buffers)    
{
    int             Size = 0;      // how many bytes we send
    int             Error;         // return value of WSASend
    int             Errno;         // result of WSAGetLastError
    DWORD  dwFlags, BytesSent;     // needed in WSASend
    int             ReturnValue = 1;   // the return value
        WSAOVERLAPPED        Overlapped;
 
        Overlapped.hEvent = (WSAEVENT)CreateEvent(NULL, FALSE, FALSE, NULL);
    if (Overlapped.hEvent == NULL) {
                MessageBox(GlobalhWindow, "CreateEvent() in DoOverlappedEventSend()",
                        "Error", MB_OK | MB_ICONSTOP | MB_SETFOREGROUND);
                return(-1);
    }
 
    Error = WSASend(Socket, Buffers, 1, &BytesSent, 0, &Overlapped, NULL); // 發送資料
 
    if (Error == SOCKET_ERROR) {
        Errno = WSAGetLastError();
        if (Errno == WSAEWOULDBLOCK) // 不能立即發送,需等待FD_WRITE事件。
            ReturnValue = 0;
        else if (Errno == WSA_IO_PENDING) { // 重疊發送被成功初始化
                        // 等待重疊操作結果
                        if (WSAGetOverlappedResult (Socket, &Overlapped, &BytesSent, TRUE, &dwFlags))
                                ReturnValue = 1;
                        else 
                                ReturnValue = -1;
        }
        else //其他錯誤
            ReturnValue = -1;
    }
 
        Buffers->len = BytesSent;        // 發送位元組數
    return(ReturnValue);
} // DoOverlappedEventSend()
 
BOOL
InitWindow(
    IN HINSTANCE InstanceHandle,
    IN HINSTANCE PrevInstanceHandle)
{
    WNDCLASS WndClass;          
 
    if (!PrevInstanceHandle) {
        WndClass.style         = CS_HREDRAW | CS_VREDRAW;
        WndClass.lpfnWndProc   = MainWndProc;
        WndClass.cbClsExtra    = 0;
        WndClass.cbWndExtra    = 0;
        WndClass.hInstance     = InstanceHandle;
        WndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
        WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
        WndClass.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE   1);
        WndClass.lpszMenuName  = NULL;
        WndClass.lpszClassName = EchoClassStr;
        if (!RegisterClass(&WndClass)) 
            return(FALSE);
        }
        
        return(TRUE);
} 
 
8.1.3  頭文件
// version of Winsock we need
#define VERSION_MAJOR      2
#define VERSION_MINOR      0
#define UM_SOCK                        WM_USER 100
#define MAX_ERROR_TEXT        64
#define BUFFER_LENGTH      1024
#define ServerName                        "127.1.1.1"
#define UserPort                                6666
8.2  多址廣播程式
        Windows Sockets 2為支援IP multicast而定義了一組新的與協定無關的多點通訊應用程式介面,歸納起來可以用表8.1表示。
表8.1  WinSock 2的多點通訊API
WSAEnumProtocols()         檢測多址廣播支持
WSASocket()         指定多點通訊類型
WSAJoinLeaf()         加入一個多址廣播組並指定角色(發送者和/或接收者) 
WSAIoctl() SIO_MULTICAST_SCOPE         設置IP生存時間
WSAIoctl() SIO_MULTIPOINT_LOOPBACK         禁止內部回送
為了適用不同的多點通訊模式,WinSock 2定義了資料平面(data plane)和控制平面(control plane)兩個概念,每一個平面都可以是“有根(rooted)”的或“無根(non-rooted)”的。關於這些概念的詳細解釋,參見附錄B。IP multicast是一種無根的資料平面和控制平面。在使用WSASocket()函數請求一個多點通訊套接字時需要指定這些角色。
        函數WSAEnumProtocols()返回當前系統中安裝的協定的詳細描述,這些資訊存放在一個協定資訊結構(WSAPROTOCOL_INFO)的陣列中。在其中的域dwServiceFlags1中的一些標識指示此服務由IP/UDP協定提供,並且位元標識XP1_SUPPORT_MULTIPOINT指示該服務支援IP multicast。
        另外,在上面的API中沒有提到如何離開一個多址廣播組。唯一與協定無關的API是關閉套接字,即使用標準的closesocket()函數,它可以用來離開多址廣播組。
        下面給出一個簡單的例子,它示例了如何使用這些函數實現多址廣播通信。
 
#include    /* Include this file first to prevent inclusion of winsock.h by windows.h */
#include 
#include    /* tcpip specific options */
#include 
#include 
#include 
 
#define HEADER_ROWS        2         /* Number of console rows reserved for header */
#define INPUT_ROWS                2         /* Number of console rows reserved for input */
#define HELP_MSG "Type a message and press  to send it, CTRL\\{Z | C} exits."
#define HELP_MSG_LEN (sizeof(HELP_MSG)/sizeof(CHAR)-1)
const COORD Zero = {0, 0};                /* Zero coordinates */
 
/* Context associated with each multicast session */
typedef struct _MCAST_CONTEXT MCAST_CONTEXT, *PMCAST_CONTEXT;
struct _MCAST_CONTEXT {
    PMCAST_CONTEXT         next;           /* Next record in the list */
    COORD                   coord;          /* Current screen coordinates */
    LPTSTR                   str;            /* User address argument string */
    LPTSTR                        ifstr;                /* Interface address string (if specified) */
    SOCKET                   s;              /* Multicast socket */
    struct in_addr              addr;           /* Multicast address to join in */
    SHORT                    port;           /* Port on which to join */
    struct in_addr              ifad;           /* Interface to register multicast address */
    struct sockaddr_in          from;           /* Peer address */
    DWORD                   fromlen;        /* Peer address length */
    WSAOVERLAPPED         ovlp;           /* Overlapped (for asynchronous IO) */
    char                        buf[2048];        /* Buffer to receive peer messages */
} ;
 
int CheckWinsockVersion(VOID);
int ParseAddress(LPTSTR argv, PMCAST_CONTEXT *ctx);
int JoinSession(PMCAST_CONTEXT ctx);
int PostReceiveRequest(PMCAST_CONTEXT ctx);
void CALLBACK RecvCompletion(IN DWORD dwError, IN DWORD cbTransferred, 
        IN LPWSAOVERLAPPED lpOverlapped, IN DWORD dwFlags);
DWORD WINAPI InputThread(LPVOID param);
BOOL CALLBACK CtrlHandler(DWORD dwCtrlType);
VOID WriteMiniConsoleA(IN OUT COORD *coord, LPSTR output, DWORD len);
VOID PaintScreen(PMCAST_CONTEXT ctx);
void Usage(char * command);
 
HANDLE         HStdout, HStdin;
CONSOLE_SCREEN_BUFFER_INFO StdScreen;         /* Screen Information used by this program */
HANDLE         StopEvent;                        /* Event to be signal when we want to stop and exit */
HANDLE         HInputThread;                /* Handle of the input thread */
int            McastTTL = 1;                /* Time-To-Live of multicast datagram */
LONG           NumPendingRcvs = 0;         /* Number of pending receive requests */
BOOL           ReuseAddress = TRUE;
 
int main(int argc, char *argv[]) 
{
    int err=0;                                /* Error code */
    PMCAST_CONTEXT  ctx;         /* Current context */
    DWORD   idThread;                /* Input thread ID */
 
    HStdout = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    HStdin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
 
    if ((HStdout==INVALID_HANDLE_VALUE) || (HStdin==INVALID_HANDLE_VALUE)
                || !GetConsoleScreenBufferInfo(HStdout, &StdScreen))
        return -1;
 
    SetConsoleCtrlHandler(NULL, TRUE); 
    SetConsoleMode(HStdout, ENABLE_PROCESSED_OUTPUT);
SetConsoleMode(HStdin, ENABLE_LINE_INPUT 
         | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
 
    if ((argc<=1) || (strcmp(argv[1], "-?")==0)) {
        Usage(argv[0]);
           return -1;
    }
 
    err = CheckWinsockVersion();  // 檢查WinSock版本號
    if (err==0) {
        StopEvent = WSACreateEvent();
        if (StopEvent!=NULL) {
            /* Win32 console input (and output) does not provide for asynchronous (overlapped) operations,
               so we create a thread dedicated to handling user input */
            HInputThread = CreateThread(NULL, 0, InputThread, &ctx, CREATE_SUSPENDED, &idThread);
            if (HInputThread!=NULL) {
                err = ParseAddress(argv[1], &ctx); /* 分析命令行參數,提取地址 */
                if (err==0) {
                    err = JoinSession(ctx);  /* 加入多址廣播會話 */
                    if (err==0) {
                       err = PostReceiveRequest(ctx);         /* 在多址廣播套接字上發佈非同步接收請求 */
                       ResumeThread(HInputThread); /* 啟動輸入線程 */
                       SetConsoleCtrlHandler(NULL, FALSE);        /* Allow normal interrupt processing */
                       SetConsoleCtrlHandler(CtrlHandler,TRUE);/*Intercept interrupts to cleanup properly */
                                    /* Allow processing of received packets till stop event is signaled or wait error occurs */
                       while (TRUE) {
                            /* Wait alertably to let completion routines run */
                            DWORD status = WaitForSingleObjectEx(StopEvent, INFINITE, TRUE);
                            switch (status) {
                                                   case WAIT_IO_COMPLETION: /* Continue processing IO completion */
                                                          continue;
                                                   case WAIT_OBJECT_0:
                                                   default:  /* Break out in case of stop signal or error */
                                                           break;
                            }
                            break;
                        }
                          }
                                closesocket(ctx->s);  // 關閉套接字以強制結束所有未完成的I/O請求 */
                   while (NumPendingRcvs>0) /* Keep this thread around until all posted requests complete */
                        SleepEx (INFINITE, TRUE); /* Sleep alertably to let completion routines run */
                   free(ctx);/* Dispose of remaining contexts */
                }
                CloseHandle(HInputThread);/* Close input thread handle */
            }
            else {
                printf("Could not create input thread.\n");
                err = GetLastError();
            }
            WSACloseEvent(StopEvent);  /* Close stop event */
        }
        else {
            printf("Could not create stop event.\n");
            err = GetLastError();
        }
        WSACleanup();        /* 卸載WinSock DLL */
    }
 
    CloseHandle(HStdout);  /* Close input handles */
    if (HStdin != INVALID_HANDLE_VALUE)
        CloseHandle(HStdin);
    if (err==ERROR_INVALID_PARAMETER) 
                Usage(argv[0]);  /* Print usage message if we suspect that user passed in invalid parameter */
 
    return err;
}
 
void Usage(char *command) {
        printf(TEXT("Multi-Chat - join in and chat on multiple multicast addresses.\n")
                TEXT("Usage:\n")
                TEXT("   %s mc_addr[:port][,if_addr]\n")
                TEXT("Where:\n")
                TEXT("   mc_addr - multicast address to join,\n")
                TEXT("   port    - port on which to join,\n")
                TEXT("   if_addr - local interface to join on,\n")
                TEXT("Example:\n")
                TEXT("   %s 224.0.0.41:1002,166.111.4.82\n"),
                command, command);
}
 
/*
 * 檢查系統中是否安裝了合適版本的WinSock DLL。
 */ 
int CheckWinsockVersion(VOID) {
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    
    wVersionRequested = MAKEWORD(2, 0); /* 非同步I/O和多址廣播只有在WinSock 2.0以上版本才支持 */
    err = WSAStartup(wVersionRequested, &wsaData);
    if (err==0) {
        /* 確認WinSock DLL支持2.0。注意,即使WinSock DLL支持的版本高於2.0,
                  因為我們要求2.0,它也會在wVersion中返回2.0。 */
        if ((LOBYTE(wsaData.wVersion)==2) && (HIBYTE(wsaData.wVersion)==0)) 
            return 0; /* WinSock DLL可接受,成功返回 */
        WSACleanup();
        err = WSAVERNOTSUPPORTED; /* 不支持,失敗返回 */
    }
 
    /* Tell the user that we couldn't find a usable WinSock DLL.*/
    printf("WinSock DLL does not support requested API version.\n");
    return err;
}
 
/*
 * 分析命令行參數,提取多址廣播和介面位址,並為多址廣播會話上下文資訊分配空間及初始化。
 * 參數: argv為命令行參數,ctx為保存多址廣播上下文內容的變數。
 * 返回值:
 *        0——成功返回,即提取了地址並分配到空間;
 *        ERROR_INVALID_PARAMETER或WSAEINVAL——位址不能提取到合法位址
 *         ERROR_OUT_OF_MEMORY——沒有足夠的資源來分配多址廣播會話上下文資訊所需要的空間
 */ 
int ParseAddress(LPTSTR argv, PMCAST_CONTEXT *ctx) {
    struct sockaddr_in  addr;           /* Address structure returned by WinSock DLL */
    int   len;                           /* Address length returned by the WinSock DLL */
    LPTSTR tmpstr, pszAddr;        /* Pointer to the address to parse */    
    int err = 0;                                /* Error code */
 
    *ctx = (PMCAST_CONTEXT)malloc(sizeof(MCAST_CONTEXT));
    if (*ctx==NULL) {
        printf("Not enough memory to allocate session context.\n");
        return ERROR_NOT_ENOUGH_MEMORY;
    }
 
    (*ctx)->str = argv;
    pszAddr = strtok(argv, ",");        /* Separate out multicast address and local interface */
 
        /* Call WinSock2 DLL to parse the address */
    len = sizeof(addr);
    err = WSAStringToAddress(pszAddr, AF_INET, NULL, (LPSOCKADDR)&addr, &len);
    if (err==0) {
        (*ctx)->addr = addr.sin_addr;  // 多址廣播地址
        /* Get the port if it is provided */
        pszAddr = strtok(NULL, ",");
        tmpstr = strtok(argv, ":");
        tmpstr = strtok(NULL, ":");
        if (tmpstr != NULL)
                  (*ctx)->port = htons(atoi(tmpstr)); // 埠號
        else 
                  (*ctx)->port = 0; // 默認為0
 
       /* Get the interface address if it is provided*/
       if (pszAddr!=NULL) {
                  
                  (*ctx)->ifstr = pszAddr; /* Save the pointer to interface address text*/
            len = sizeof(addr);
            err = WSAStringToAddress(pszAddr, AF_INET, NULL, (LPSOCKADDR)&addr, &len);
            if (err==0)
                (*ctx)->ifad = addr.sin_addr; // 介面位址
            else {
                err = WSAGetLastError();
                printf("Could not parse interface address: %s", pszAddr);
            }
        }
        else { /* 沒有指定介面位址,使用默認位址 */
            (*ctx)->ifad.S_un.S_addr = INADDR_ANY;
                   (*ctx)->ifstr = pszAddr;
           }
    }
    else {
        err = WSAGetLastError();
        printf("Could not parse multicast address: %s\n", pszAddr);
    }
 
    return err;
}
 
/*
 * 加入多址廣播會話
 * 參數:ctx為多址廣播會話上下文
 */
int JoinSession(PMCAST_CONTEXT ctx) {
    int  err = 0;                                /* Error code */
    struct sockaddr_in  addr;         /* Address structure for WinSock DLL calls */
    int  len;                            /* Address length for WinSock DLL call
        
系統時間:2024-05-16 22:49:30
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!