Windows網絡編程之Socket通信

一、Windows編程簡介

1、Winsock簡介

Winsock是Windows下網絡編程的標準接口,它允許兩個或多個應用程序在相同機器上,或者通過網絡相互交流。Winsock庫有兩個版本,Winsock1和Winsock2,需要在程序中包含頭文件winsock2.h,它包含了絕大部分socket函數和相關結構類型的聲明和定義。同時要添加的還有到WS2_32.lib庫的鏈接。包含必要的頭文件,設置好鏈接環境之後,就可開始coding了。Winsock封裝了TCP/IP協議,我們可以通過調用Winsock的接口函數來調用TCP/IP的各種功能。

2、Winsock編程流程

Winsock編程分爲服務器端和客戶端兩部分,TCP服務器端程序創建過程:服務器端創建監聽套接字,併爲它關聯一個本地地址(指定端口號和IP地址),然後進入監聽狀態準備接受客戶端的連接請求。客戶端程序創建過程:客戶端創建套接字後即可調用connect函數去試圖連接服務器監聽套接字。當服務器端的accept函數返回後,connect函數也返回。此時客戶端使用socket函數創建的套接字,服務器端使用accept函數創建的套接字,雙方就可以通信了。

二、socket通信程序代碼實現

開發環境:VC++ 6.0 、Win 7系統
ps:將服務器端工程和客戶端工程添加到同一個工作區

1、服務器端:

/*
服務器端
*/

#include<stdio.h>
//WinSock是一個網絡編程接口,是對TCP/IP協議的一種封裝
#include<Winsock2.h>
#pragma comment(lib,"ws2_32")//鏈接一個ws2_32.lib的庫文件


DWORD WINAPI RecvThread(void *Param){
   SOCKET socketConnection = *((SOCKET *)Param);    //設置socket
   char recvBuf[50];    
   while(1){//接收並顯示消息
    recv(socketConnection,recvBuf,50,0);
    printf("%s\n",recvBuf);
   }
   return 0;
}

void main(){
    /*
    1.任何基於WinSock的編程首先必須初始化 WinSock DLL庫
        ps:所有變量的定義要放到函數最前面,否則會報錯:syntax error : missing ';' before 'type'
    */
    WORD wVersionRequested;//使用WinSock的版本爲:wVersionRequested
    WSADATA wsaData;
    int err;
    SOCKET socketServer;
    SOCKADDR_IN addrSrv;
    SOCKADDR_IN addrClient;
    int len;
    SOCKET socketConnection;
    char sendBuf[50];



    wVersionRequested = MAKEWORD(1,1);
    err = WSAStartup(wVersionRequested , &wsaData);
    if(err!=0){
        return;
    }
    if(LOBYTE(wsaData.wVersion)!=1 || HIBYTE(wsaData.wVersion)!=1 ){
        WSACleanup();
        return;
    }

    /*
    2.創建一個套接字。WinSock通信的所有的數據傳輸都是通過套接字來完成的
    */
    socketServer = socket(AF_INET,SOCK_STREAM,0);//創建套接字
    addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
    addrSrv.sin_family=AF_INET;//將sin_family字段設置爲AF_INET,代表WinSock使用的是IP地址族
    addrSrv.sin_port=htons(6000);//端口號
    /*
    3.通過綁定函數bind來實現套接字和需要進行通訊的地址建立聯繫
    */
    bind(socketServer,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//綁定套接字到一個IP地址和一個端口上
    listen(socketServer,5);//將套接字設置爲監聽模式等待連接請求
    len = sizeof(SOCKADDR);
    //請求到來後,接收連接請求,返回一個新的對應於此次連接的套接字
    socketConnection = accept(socketServer,(SOCKADDR*)&addrClient,&len);
    //創建一個子線程調用RecvThread函數,專門用來接收消息
    CreateThread(NULL,0,RecvThread,&socketConnection,0,NULL);
    //發送消息就在主線程當中實現
    while(1){
        printf("輸入要發送給客戶端的消息:\n");
        scanf("%s",sendBuf);    //從鍵盤輸入要發送給客戶端的消息,存入數組當中
        send(socketConnection,sendBuf,strlen(sendBuf)+1,0); //發送消息
    }
}

2、客戶端:

/*
客戶端程序
*/
#include<stdio.h>
#include<Winsock2.h>
#pragma comment(lib,"ws2_32")//鏈接一個ws2_32.lib的庫文件

//接收消息
DWORD WINAPI RecvThread(void *Param){
   SOCKET socketClient = *((SOCKET *)Param);    //設置socket
   char recvBuf[50];
   while(1){//接收並顯示消息
    recv(socketClient,recvBuf,50,0);
    printf("%s\n",recvBuf);
   }
   return 0;
}

void main(){
    WORD wVersionRequested;
    WSADATA wsaData;
    SOCKET socketClient;//所有變量的定義要放到函數最前面,否則會報錯:syntax error : missing ';' before 'type'
    SOCKADDR_IN addrSrv;
    //char recvBuf[50];//字符型數組,用來接收消息
    char sendBuf[50];//字符型數組,用來發送消息
    int err;

    wVersionRequested = MAKEWORD(1,1);
    err = WSAStartup(wVersionRequested,&wsaData);
    if(err!=0){
        return;
    }
    if(LOBYTE(wsaData.wVersion)!=1 || HIBYTE(wsaData.wVersion)!=1 ){
        WSACleanup();//關閉加載的套接字庫
        return;
    }
    socketClient = socket(AF_INET,SOCK_STREAM,0);
    addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");//IP地址
    addrSrv.sin_family=AF_INET;//將sin_family字段設置爲AF_INET,代表WinSock使用的是IP地址族
    addrSrv.sin_port=htons(6000);//端口號
    connect(socketClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//向服務器端發出連接請求
    //創建子線程調用RecvThread函數,專門用來接收服務器端發過來的消息
    CreateThread(NULL,0,RecvThread,&socketClient,0,NULL);
    //發送消息在主線程當中實現
    while(1){
        printf("請輸入要發給服務器端的消息:\n");
        scanf("%s",sendBuf);
        send(socketClient,sendBuf,strlen(sendBuf)+1,0);//發送消息
    //  closesocket(socketClient);
    //  WSACleanup();//關閉加載的套接字庫
    }

}

3、運行效果:

三、遇到的問題及解決辦法

問題1:服務器端和客戶端程序都不能連續發送消息。

原因:將接收消息和發送消息都放到主線程當中來操作,因爲這兩個操作需要一直運行,而主線程在同一時刻只能執行其中一個,所以勢必會導致接收或者發送消息的其中一個阻塞,這就出現了服務器端和客戶端程序都不能發送消息的問題。
解決辦法:將客戶端和服務器端的接收消息部分的代碼都放到子線程當中去執行,而主線程就專心發送消息,這樣就能實現客戶端和服務器端都能連續的、即時的向對方發送消息。

問題2:增加子線程來接收消息後,如何保證接收和發送消息是處於同一次連接中。

解決辦法:提前做好服務器和客戶端的連接工作,返回一個套接字,然後在創建接收消息子線程的時候,將該套接字的引用傳給CreateThread函數的第四個參數。這樣就能保證收發消息是通過同一個套接字來處理的。

四、總結

1、CreateThread函數

//函數原型:
CreateThread(
_In_opt_LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_SIZE_T dwStackSize,
_In_LPTHREAD_START_ROUTINE lpStartAddress,
_In_opt___drv_aliasesMemLPVOID lpParameter,
_In_DWORD dwCreationFlags,
_Out_opt_LPDWORD lpThreadId
);


//參數說明
lpThreadAttributes:指向SECURITY_ATTRIBUTES型態的結構的指針。在Windows 98中忽略該參數。在Windows NT中,NULL使用默認安全性,不可以被子線程繼承,否則需要定義一個結構體將它的bInheritHandle成員初始化爲TRUE
dwStackSize,設置初始棧的大小,以字節爲單位,如果爲0,那麼默認將使用與調用該函數的線程相同的棧空間大小。任何情況下,Windows根據需要動態延長堆棧的大小。
lpStartAddress,指向線程函數的指針,形式:@函數名,函數名稱沒有限制,但是必須以下列形式聲明:
DWORD WINAPI 函數名 (LPVOID lpParam) ,格式不正確將無法調用成功。
//也可以直接調用void類型
//但lpStartAddress要這樣通過LPTHREAD_START_ROUTINE轉換如: (LPTHREAD_START_ROUTINE)MyVoid
//然後在線程聲明爲:
void MyVoid()
{
return;
}
lpParameter:向線程函數傳遞的參數,是一個指向結構的指針,不需傳遞參數時,爲NULL。
dwCreationFlags :線程標誌,可取值如下
(1)CREATE_SUSPENDED(0x00000004):創建一個掛起的線程,
(20:表示創建後立即激活。
(3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize參數指定初始的保留堆棧 的大小,否則,dwStackSize指定提交的大小。該標記值在Windows 2000/NT and Windows Me/98/95上不支持。
lpThreadId:保存新線程的id。
返回值:函數成功,返回線程句柄;函數失敗返回false。若不想返回線程ID,設置值爲NULL。
函數說明:
創建一個線程。
語法:
hThread = CreateThread (&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章