1. 程式人生 > >利用流式套接字傳輸資料檔案

利用流式套接字傳輸資料檔案

伺服器端:
1>建立一個基於對話方塊的應用程式StreamSocketServer
2>設計介面,將“確定”按鈕的標題改為“啟動”
3>雙擊“啟動按鈕”,新增以下程式碼,初始化網路,啟動伺服器監聽

void CStreamSocketServerDlg::OnOK() 
{
    // TODO: Add extra validation here
    WSADATA wsaData;
    /*
    typedef struct WSAData
    {
        WORD wVersion;  //為希望使用的Winsock版本
        WORD wHeighVersion;  //返回現有的Winsock庫的最高版本
char szDescription[WSADESCRIPTION_LEN+1]; //通常不用 char szSystemStatus[WSASYS_STATUE_LEN+1]; //通常不用 unsigned short iMaxSockets; //可同時開啟的套接字數 unsigend short iMaxUdpDg; //資料報最大長度 char FAR * lpVendorInfo; //為制定Winsock實施方案的廠商資訊所預留,在任何一個Win32平 臺上都沒有使用這個欄位] } */ //初始化TCP協議
BOOL ret = WSAStartup(MAKEWORD(2, 2), &wsaData); /* int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); wVersionRequested 使用者制定準備載入的Winsock庫的版本. 巨集定義MAKEWOARD(X, Y) 獲得wVersionRequested的正確值, [X為高位位元組,Y為低位位元組, lpWSAData 為指向LPWSDATA結構的指標,包含了載入庫版本有關的資訊 */ if(ret != 0
) { MessageBox("初始化網路協議失敗!"); return; } //建立伺服器端套接字 ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); /* SOCKET socket(int af, int type, int protocol); af用於制定網路型別,一般取AF_INET,表示該套接字在Internet域中進行通訊 type用於指定套接字型別,SOCK_STREAM表示流套接字, SOCK_DGRAM表示資料報套接字 protocol用於指定網路協議,預設為0,表示TCP/IP協議 */ if(ServerSock == INVALID_SOCKET) { MessageBox("建立失敗!"); closesocket(ServerSock); } //繫結到本地一個埠上 sockaddr_in localaddr; /* struct sockaddr_in { short sin_family; //必須為AF_INET,表示該socket處於Internet域 u_short sin_port; //用於指定服務埠 struct in_addr sin_addr; //把一個IP地址儲存為一個4位元組的數,其資料型別為無符號長整數 型別 char sin_zero[8]; //只充當填充項角色,使得與sockaddr長度保持一致 } */ localaddr.sin_family = AF_INET; localaddr.sin_port = htons(6000); /* htons的功能:將一個無符號短整型的主機數值轉換為網路位元組順序,即大尾順序(big-endian) 引數u_short hostshort:16位無符號整數 返回值:TCP/IP網路位元組順序. */ localaddr.sin_addr.s_addr = 0; if(bind(ServerSock, (struct sockaddr *)&localaddr, sizeof(sockaddr)) == SOCKET_ERROR) { MessageBox("繫結地址失敗!"); closesocket(ServerSock); WSACleanup(); return; } /* int bind(SOCKET s, const struct sockaddr FAR *name, int namelen); s標識一個未捆綁套接字的控制代碼,用來等待客戶機的連線 name是賦予套接字的地址 */ //將ServerSock設定為非同步非阻塞模式,併為它註冊各種網路非同步事件 //m_hWnd為應用程式的主對話方塊或主視窗的控制代碼 if(WSAAsyncSelect(ServerSock, m_hWnd, NETWORK_EVENT, FD_ACCEPT|FD_CLOSE|FD_READ|FD_WRITE) == SOCKET_ERROR) { MessageBox("註冊失敗!"); WSACleanup(); return; } /* int WSAAsyncSelect( SOCKET s, HWND hWnd, //接收訊息的視窗控制代碼 unsigned int wMsg; //網路事件發生時要接收的訊息 long lEvent; //被註冊的網路事件 ); lEvent引數值及意義: FD_READ 希望在套接字s收到資料時收到訊息 FD_WRITE 希望在套接字s可以傳送資料時收到訊息 FD_ACCEPT 希望在套接字s收到連線請求是收到訊息 FD_CONNECT 希望在套接字s連線成功是收到訊息 FD_CLOSE 希望在套接字s連線關閉是收到訊息 FD_OOB 希望在套接字s收到外帶資料時收到訊息 NETWORK_EVENT 為自定義訊息,並新增其響應處理函式OnNetEvent(WPARAM wParam, LPRAM lParam) */ listen(ServerSock, 31); //設定為偵聽模式,最多要31個連線 MessageBox("服務啟動成功!"); CDialog::OnOK(); }

4>註冊自定義訊息NETWORK_EVENT, 並新增其相應處理函式OnNetEvent
這裡寫圖片描述
在StreamSocketServerDlg.h中新增成員函式:

void OnNetEvent(WPARAM wParam, LPARAM lParam);

在StreamSocketServerDlg.cpp中實現:

void CStreamSocketServerDlg::OnNetEvent(WPARAM wParam, LPARAM lParam)
{
    int iEvent = WSAGETSELECTEVENT(lParam); //呼叫Winsock API函式,得到網路事件型別
    SOCKET CurSock = (SOCKET)wParam;  //呼叫Winsock API函式,得到此事件的客戶端套接字
    switch(iEvent)
    {
    case FD_ACCEPT:  //客戶端連線請求事件
        TRACE("Get client call!\n");
        OnAccept(CurSock); 
        break;
    case FD_CLOSE:   //客戶端斷開事件
        OnClose(CurSock);
        break;
    case FD_READ:   //客戶端網路包到達事件
        OnReceive(CurSock);
        break;
    case FD_WRITE:  //傳送網路資料事件
        break;
    default: break;
    }
}

5>新增客戶端的連線請求處理函式

void CStreamSocketServerDlg::OnAccept(SOCKET CurSock)
{
    SOCKET TempSock;
    struct socaaddr_in ConSocketAddr;
    int addrlen;
    addrlen = sizeof(ConSocketAddr);
    TempSock = accept(CurSock, (struct sockaddr*)&ConSocketAddr, &addrlen);
    /*
    用於接受連線請求
    SCOKET accept(
        SCOKET s,   //Socket的識別碼
        struct sockaddr FAR * addr,  //存放連線的客戶端地址
        int FAR* addrlen  //地址長度
    )
    */
    if(ConnectSock == INVALID_SOCKET)
        MessageBox("INVALID_SOCKET");
    TRACE("建立新連線,sock:%d\n", TempSock);  //輸出除錯資訊
    inet_ntoa(ConSocketAddr.sin_addr);
    ConnectSock = TempSock;
    char *filename = "c:\\001.doc";
    if(!SendFile(filename, ConnectSock))
        MessageBox("傳送失敗!");

}

6>添加發送資料檔案函式

BOOL CStreamSocketServerDlg::SendFile(char *name, SOCKET conn)
{
    char *FileName = name;
    SOCKET TcpConn = conn;
    CFile file;
    if(!file.Open(FileName, CFile::modeRead))
    {
        printf("開啟%s失敗!\n", FileName);
        return FALSE;
    }
    int NumBytes;  //用來儲存每次傳送時資料塊的大小
    UINT Total = 0;  //用來儲存套接字已經傳送的總的位元組數
    int BufSize = 1024;  //傳送緩衝區的大小
    int Size = BufSize;  //讀取檔案的大小
    LPBYTE pBuf = new BYTE[BufSize];  //傳送緩衝區
    DWORD dwTemp = 0;
    UINT FileLength = file.GetLength();  //得到檔案的大小併發送出去
    send(TcpConn, (char *)&FileLength, 4, 0);
    /*
    int send(  //返回傳送的字元總數
        SOCKET s,
        const char FAR *buf,  //存放要傳送的資料的暫存區
        int len,  //buf的長度
        int flags   //此函式被呼叫的方式
    )
    */
    file.SeekToBegin();
    while(Total < FileLength)
    {
        if((int)(FileLength - Total) < Size)
            Size = FileLength - Total;
        Size = file.Read(pBuf, Size);
        /*
        virtual UINT Read(
            void* lpBuf,  //是把資源讀入哪裡
            UINT nCount   //是讀入的位元組數
        );
        */
        NumBytes = send(TcpConn, (char *)pBuf, Size, 0);
        if(NumBytes == SOCKET_ERROR)
        {
            send(TcpConn, "ERROR", sizeof("ERROR")+1, 0);
            return FALSE;
        }
        Total += NumBytes;
        file.Seek(Total, CFile::begin);
    }
    delete[] pBuf;
    file.Close();
    closesocket(TcpConn);
    return TRUE;
}

客戶端:
1>新建一個基於對話方塊的程式StreamSocketClient
2>設計介面,新增編輯框m_CtrlPAddress輸入IP地址,m_iPort用於輸入埠,將“確定”按鈕改為“連線”,“取消”改為“關閉”
3>雙擊“連線”,新增以下程式碼:

void CStreamSocketClientDlg::OnOK() 
{
    // TODO: Add extra validation here
    UpdateData(FALSE);  //初始化對話方塊中的資料
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    wVersionRequested = MAKEWORD(2, 2);  //連線兩個給定的無符號引數
    err = WSAStartUp(wVersionReqested, &wsaData);
    if(err != 0)
        return;
    if(LOBYTE(wsaData.wVersion) != 1) || HIBYTE(wsaData.wVersion) != 1) //LOBYTE()得到一個16bit數

最低(最右邊)那個位元組
    {
        WSACleanup();
        return;
    }
    SOCKET TcpClient = socket(AF_INET, SOCK_STREAM, 0);
    SOCKADDR_IN SerAddr;
    SerAddr.sin_family AF_INET;
    SerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    /*
    struct in_addr{
        union{
            struct{ u_char s_b1, s_b2, s_b3, s_b4;} S_un_b;
            struct{u_short S_w1, S_w2} S_un_w;
            u_long S_addr;
        }S_un;
    };
    */
    SerAddr.sin_port = htos(m_iPort);
    connect(TcpClient, (SOCKADDR*)&SerAddr, sizeof(SerAddr));  //與伺服器端建立連線
    /*
    int connect(
        SOCKET s,  //伺服器端Socket的識別碼
        const struct sockaddr FAR* name,  //Socket想要連線的對方地址
        int namelen  //地址長度
    )
    */
    long FileLength;
    recv(TcpClient, (char *)&FileLength, sizeof(long), 0);
    /*
    int recv(
        SOCKET s,   
        char FAR *buf,   //存放接收到資料的暫存區
        int len,    //buf的長度
        int flags   //此函式的呼叫方式
    )
    */
    if(RecvFile("c:\\003.doc", TcpClient, FileLength))
        MessageBox("傳輸結束!\n");
    else
        MessageBox("傳輸失敗!\n");
    CDialog::OnOK();
}

4>新增接收檔案函式

BOOL CStreamSocketClientDlg::RecvFile(char *name, SOCKET conn, UINT filelen)
{
    char *FileName = name;
    SOCKET client = conn;
    CFile file;
    if(!file.Open(FileName, CFile::modeWrite))
    {
        printf("開啟%s失敗!\n", FileName);
        return FALSE;
    }
    int NumBytes;  //用來儲存每次接收時資料塊的大小
    UINT Total = 0;  //用來儲存套接字已經接收的總的位元組數
    int BufSize = 1024;  //接收緩衝區的大小
    int Size = BufSize;  //寫檔案的大小
    LPBYTE pBuf = new BYTE[BufSize];  //接收緩衝區
    DWORD dwTemp = 0;
    file.SeekToBegin();
    while(Total < filelen)
    {
        if((int)(filelen - Total) < Size)
            Size = filelen - Total;
        NumBytes = recv(client, (char *)pBuf, Size, 0);

        if(NumBytes == SOCKET_ERROR)
        {
            printf("接收失敗!\n");
            return FALSE;
        }
        file.Write(pBuf, NumBytes);  
        /*
        virtual void Write(  
            const void* lpBuf,   //儲存要寫的東西的字串或者字元陣列
            UINT nCount );   //要從這個字串或者字元陣列中寫多少個字元到檔案中
        */
        Total += NumBytes;
        file.Seek(Total, CFile::begin);
    }
    delete[] pBuf;
    file.Close();
    closesocket(client);
    return TRUE;
}

介面:
這裡寫圖片描述

這裡寫圖片描述