Winsock程式設計初步之 Winsock編程原理

來源:互聯網
上載者:User

Winsock程式設計初步之 Winsock編程原理
 
 
  本課程主要講Windows中TCP/IP編程介面Winsock,版本為1.1。高版本的Winsock實際與1.1版相差不多,主要是進行了一些擴充,如可超越TCP/IP協議直接用socket來實現IPX、NETBIOS等其它通訊協定。

  這敘述方便在本文的其餘部分中提到的Winsock指的就是Winsock1.1。

  通過Winsock可實現點對點或廣播通訊程式,實際這兩者之間的區別不大,編程時其程式流程所用代碼幾乎相同,不同的地方在於目標地址選擇的不同。本課程中所舉執行個體為點對點的形式,並以客戶/伺服器形式來構建通過Winsock進行通訊的點對點通訊,並對通訊過程的兩點分別命名為Server和Client。

為更清楚的說明出Winsock的結構原理,下面以電信局的普通電話語音為比較對象進行說明:
1、電信局提供電話語音類似版主們這的Server,普通電話使用者類似版主們這的Client。

2、首先電信局必須建立一個電話總機。這就如果版主們必須在Server端建立一個Socket(通訊端),這一步通過調用socket()函數實現。

3、電信局必須給電話總機分配一個號碼,以便使使用者要撥找該號碼得到電話語音,同時接入該電信局的使用者必須知道該總機的號碼。同樣,版主也在Server端也要為這一通訊端指定一port(連接埠),並且要串連該Server的Client必須知道該連接埠。這一步通過調用bind()函數實現。

4、接下來電信局必須使總機開通並使總機能夠高效地監聽使用者撥號,如果電信局所提供服務的使用者數太多,你會發現撥打電信局總機老是忙音,通常電信局內部會使該總機對應的電話號碼連到好幾個負責交換的處理中心,在一個處理中心忙於處理當前的某個使用者時,新到使用者可自動轉到一下處理中心得到服務。同樣版主們的Server端也要使自己的套介面設定成監聽狀態,這是通用listen()函數實現的,listen()的第二個參數是等待隊列數,就如同你可以指定電信局的建立幾個負責交換的處理中心。

5、使用者知道了電信局的總機號後就可以進行撥打請求得到服務。在Winsock的世界裡做為Client端是要先用socket()函數建立一個通訊端,然後調connect()函數進行串連。當然和電話一樣,如果等待隊列數滿了、與Server的線路不通或是Server沒有提供此項服務時,串連就不會成功。

5、電信局的總機接受了這使用者撥打的電話後負責接通使用者的線路,而總機本身則再回到等待的狀態。Server也是一樣,調用accept()函數進入監聽處理過程,Server端的代碼即在中處暫停,一旦Server端接到申請後系統會建立一個新的通訊端來對此串連做服務,而原先的通訊端則再回到監聽等待的狀態。

6、當你電話掛完了,你就可以掛上電話,彼此間也就離線了。Client和Server間的通訊端的關閉也是如此;這個關閉離線的動作,可由Client端或Server端勸嬤骰方先關閉。有些電話查詢系統不也是如此嗎?關閉通訊端的函數為
closesocket()。

從以上情況可以看出在伺服器端建立一個通訊端,並進入實際的監聽步驟的過程如下:socket()->bind()->listen()->accept()

那麼在accept()完了後,版主們說在Server端將產生一個新的通訊端,然後Server將繼續進入accept()狀態,版主們該如何用這個新的通訊端來進行與Client端的通訊呢,這就用到了recv()函數,而Client端則是通過send()函數來向伺服器發資訊的。

在用戶端也是採取類似的過程,其調用Winsock的過程如下:
socket()->connect()->send()
首先建立一個socket,然後用connect()函數將其與Server端的socket串連,串連成功後調用send()發送資訊。

//A simplest web server
//Written by Shen zhiliang for learning Winsock & HTTP
file://zhiliang@sina.com
 
#include "winsock.h"
#include "stdio.h"
#include "conio.h"
#include "io.h"

#define BUFLEN 2048
#define DEFPATH ("C://frontpage webs//content//")
#define DEFFILE ("INDEX.HTM")
#define HTTPHEAD ("HTTP/1.0 200 OK/010Date: Monday, 04-Jan-99 17:06:17 GMT/x0aServer: HTTP-Server/1.0/x0a MIME-version: 1.0/x0a")
#define MIMEHTML ("Content-type: text/html/x0a Last-modified: Friday, 26-Sep-97 09:36:54 GMT/x0a")
#define MIMEGIF ("Content-type: /image/gif/x0a Last-modified: Friday, 26-Sep-97 09:36:54 GMT/x0a")
#define MIMEJPEG ("Content-type: /image/jpeg/x0a Last-modified: Friday, 26-Sep-97 09:36:54 GMT/x0a")
#define MIMEPAIN ("Content-type: text/pain/x0a Last-modified: Friday, 26-Sep-97 09:36:54 GMT/x0a")
#define HTMLHEAD ("<html><body>/x0a")
#define HTMLTAIL ("/n</body></html>")

void P(char * a)
{
    printf("Error in : %s/n",a);
}

/*
char * httphead(FILE * fp)
{
    struct tm *newtime;
    time_t aclock;
    time( &aclock );
    newtime = localtime( &aclock );
    printf( "The current date and time are: %s", asctime( newtime ) );
}
*/

void HtmlError(SOCKET s,unsigned int code,char * msg)
{
    char tmp[1024];
    sprintf(tmp,"Error code: %d %s",code,msg);
    send(s,HTTPHEAD,strlen(HTTPHEAD),0);
    send(s,HTMLHEAD,strlen(HTMLHEAD),0);
    send(s,tmp,strlen(tmp),0);
    send(s,HTMLTAIL,strlen(HTMLTAIL),0);
}

void SendHtmlFile(SOCKET s,char * filename)
{
    FILE * fp;
    char * tmp;
    char fullname[512];
    char buf[1024];
    int i;
 
    strcpy(fullname,DEFPATH);
 
    if(strlen(filename)==0||(strlen(filename)==1&&filename[0]=='/'))
        strcat(fullname,DEFFILE);
    else
    {
        do{
            tmp=strchr(filename,'/');
            if(tmp!=NULL)
                tmp[0]='//';
        }while(tmp!=NULL);

        if(filename[0]=='//')
            strcat(fullname,&filename[1]);
        else
            strcat(fullname,filename);
    }

    FILE * fpt=fopen("recv.dat","a+b");
    fwrite(fullname,sizeof(char),strlen(fullname),fpt);
    fclose(fpt);
    fp=fopen(fullname,"rb");
 
    if(fp==NULL)
    {
        char msg[512];
        if(filename[0]=='//')
            filename[0]='/';
        sprintf(msg," URI no found: %s",filename);
        HtmlError(s,404,msg);
        return;
    }

    send(s,HTTPHEAD,strlen(HTTPHEAD),0);
    if(stricmp(&filename[strlen(filename)-4],".GIF")==0)
        send(s,MIMEGIF,strlen(MIMEGIF),0);
    else if(stricmp(&filename[strlen(filename)-4],".JPG")==0|| stricmp(&filename[strlen(filename)-5],".JPEG")==0)
        send(s,MIMEJPEG,strlen(MIMEJPEG),0);
    else if(stricmp(&filename[strlen(filename)-4],".HTM")==0|| stricmp(&filename[strlen(filename)-5],".HTML")==0)
    {
        send(s,MIMEHTML,strlen(MIMEHTML),0);
    }
 
    long fs=_filelength(_fileno(fp));
    buf[0]=0;
    sprintf(buf,"Content-length: %ld/x0a/x0a",fs);
    send(s,buf,strlen(buf),0);
    for(i=0;!feof(fp);i++)
    {
        buf[i]=fgetc(fp);
        if(i>=1023||feof(fp))
        {
            send(s,buf,i,0);
            i=0;
        }
    }

    fclose(fp);
}

void SocketError(char * call)
{
    fprintf(stderr," WinSock Error# function: %s, error code:%ld/n",call,WSAGetLastError());
}

main(int argc,char ** argv)
{
    int iRes,iPort,iTmp;
    SOCKET s,rs;
    SOCKADDR_IN sin,rsin;
    WSADATA wsad;
    WORD wVersionReq;
    char recvBuf[BUFLEN];
 
    if(argc<2)
    {
        fprintf(stderr,"Usage: WebServer 999/n/t999 - Port number for this server.");
        return -1;
    }

    iPort=0;
    iPort=atoi(argv[1]);
    if(iPort<=0)
    {
        fprintf(stderr,"must specify a port number");
        return -1;
    }
    wVersionReq=MAKEWORD(1,1);
 
    iRes=WSAStartup(wVersionReq,&wsad);
    if(iRes!=0)
    {
        SocketError("WSAStartup()");
        return -1;
    }
 
    s=socket(PF_INET,SOCK_STREAM,0);
    if(s==INVALID_SOCKET)
    {
        SocketError("socket()");
        return -1;
    }

    sin.sin_family=PF_INET;
    sin.sin_port=htons(iPort);
    sin.sin_addr.s_addr=INADDR_ANY;
    iTmp=sizeof(sin);

    if(bind(s,(LPSOCKADDR)&sin,iTmp)==SOCKET_ERROR)
    {
        SocketError("bind()");
        closesocket(s);
        WSACleanup();
        return -1;
    }
 
    if(listen(s,1)==SOCKET_ERROR)
    {
        SocketError("listen()");
        closesocket(s);
        WSACleanup();
        return -1;
    }

    fprintf(stderr,"WebServer Running....../n");
    iTmp=sizeof(rsin);
    rs=0;
    while(1)
    {
        if(_kbhit()!=0)
        {
            if(_getch()!=27)
                break;
            if(rc!=0)
            closesocket(rs);
            closesocket(s);
            WSACleanup();
            fprintf(stderr,"WebServer Stopped....../n");
            return 0;
        }

        rs=accept(s,(LPSOCKADDR)&rsin,&iTmp);
        if(rs==INVALID_SOCKET)
        {
            SocketError("accept()");
            closesocket(s);
            WSACleanup();
            return -1;
        }

        iRes=recv(rs,recvBuf,BUFLEN,0);
        printf("RECEIVED DATA: /n---------------------------------/n%s/n---------------------------------/n",recvBuf);
        if(iRes==SOCKET_ERROR)
        {
            SocketError("recv()");
            closesocket(rs);
            closesocket(s);
            WSACleanup();
            return -1;
        }
 
        char * sRes;
        sRes=strstr(recvBuf,"GET");
        if(sRes!=NULL&&(sRes-recvBuf)<3)
            sRes=strchr(recvBuf,'/');
        if(sRes!=NULL)
        {
            char * sRes1;
            sRes1=strchr(sRes,'/r');
            if(strchr(sRes,' ')<sRes1)
                sRes1=strchr(sRes,' ');
            if(sRes1!=NULL&&(sRes1-sRes)<256)
            {
                char tmp[256];
                strncpy(tmp,sRes,(sRes1-sRes));
                tmp[sRes1-sRes]=0;
                int i;
                for(i=strlen(tmp)-1;(tmp[i]==' '||tmp[i]=='/t')&&i>=0;i--)
                    tmp[i]=0;
                for(i=0;tmp[i]==' '||tmp[i]=='/t';i++);
                SendHtmlFile(rs,&tmp[i]);
            }
        }
        else
        {
            HtmlError(rs,202,"Bad request");
        }
        closesocket(rs);
    }

    return 0;
}

//A simplest web client
//Written by Shen zhiliang for learning Winsock & HTTP
//zhiliang@sina.com
//1998.7.29

#include "winsock.h"
#include "stdio.h"

#define BUFLEN 4096

void SocketError(char * call)
{
    fprintf(stderr," WinSock Error# function: %s, error code:%ld/n",call,WSAGetLastError());
}

main(int argc,char ** argv)
{
    int iRes,iPort,iTmp;
    SOCKET s,rs;
    SOCKADDR_IN sin,rsin;
    WSADATA wsad;
    WORD wVersionReq;
    char recvBuf[BUFLEN];
 
    if(argc<4)
    {
        fprintf(stderr,"Usage: sockserver ip port message/n");
        return -1;
    }
    if(inet_addr(argv[1])==INADDR_NONE)
    {
        fprintf(stderr,"Error ip gaving/n");
        return -1;
    }
    iPort=0;
    iPort=atoi(argv[2]);
    sin.sin_addr.s_addr=inet_addr(argv[1]);
    sin.sin_family=PF_INET;
    sin.sin_port=htons(iPort);
    if(iPort<=0)
    {
        fprintf(stderr,"must specify a number for port/n");
        return -1;
    }
    wVersionReq=MAKEWORD(1,1);
 
    iRes=WSAStartup(wVersionReq,&wsad);
    if(iRes!=0)
    {
        SocketError("WSAStartup()");
        return -1;
    }
 
    s=socket(PF_INET,SOCK_STREAM,0);
    if(s==INVALID_SOCKET)
    {
        SocketError("socket()");
        return -1;
    }
    iTmp=sizeof(sin);
    fprintf(stderr,"WinSock Client Start....../n");
    if(connect(s,(LPSOCKADDR)&sin,iTmp)==SOCKET_ERROR)
    {
        SocketError("connect()");
        closesocket(s);
        WSACleanup();
        return -1;
    }

    strcpy(recvBuf,argv[3]);
    strcat(recvBuf,"/r/n/r/n");
    iRes=send(s,recvBuf,strlen(recvBuf),0);
    if(iRes==SOCKET_ERROR)
    {
        SocketError("send()");
        closesocket(s);
        WSACleanup();
        return -1;
    }
    printf("Sent Data:/n------------------/n%s/n------------------/n",recvBuf);

    FILE * fp=fopen("send.dat","a+b");
    if(fp==NULL)
        return -1;
    iRes=recv(s,recvBuf,BUFLEN,0);
    while(iRes!=SOCKET_ERROR&&iRes!=0)
    {
        printf("Received Data:/n------------------/n%s/n------------------/n",recvBuf);
        fwrite(recvBuf,sizeof(char),iRes,fp);
        iRes=recv(s,recvBuf,BUFLEN,0);
    }
 
    fclose(fp);
    closesocket(s);
    WSACleanup();
    return 0;
}

Winsock函數用法說明

WSAStartup()
連結應用程式與Winsock.DLL 的第一個函數。
格 式:
  int WSAStartup( WORD wVersionRequested,LPWSADATA lpWSAData )
參 數:
  wVersionRequested 欲使用的 Windows Sockets API 版本
  lpWSAData 指向 WSADATA 資料的指標
傳回值:
  成功 - 0
  失敗 - WSASYSNOTREADY / WSAVERNOTSUPPORTED / WSAEINVAL
說明:
  此函數「必須」是應用程式呼叫到 Windows Sockets DLL 函數中的第一個函數呼叫成功後,才可以再呼叫其他 Windows Sockets DLL 的函數。此函數亦讓使用者可以指定要使用的 Windows Sockets API 版本,及擷取設計者的一些資訊。

socket()
建立Socket。
格 式:
  SOCKET socket( int af, int type, int protocol )
參 數:
  af 目前只提供 PF_INET(AF_INET)
  type Socket 的型態 (SOCK_STREAM、SOCK_DGRAM)
  protocol 通訊協定(如果使用者不指定則設為0)
傳回值:
  成功 - Socket 的識別碼
  失敗 - INVALID_SOCKET(呼叫 WSAGetLastError() 可得知原因)
說明:
  此函數用來建立一 Socket,並為此 Socket 建立其所使用的資源。Socket 的型態可為 Stream Socket 或 Datagram Socket。

bind()
指定 Socket 的 Local 地址 (Address)。
格 式:
  int bind( SOCKET s, const struct sockaddr FAR *name,int namelen );
參 數:
  s Socket的識別碼
  name Socket的地址值
  namelen name的長度
傳回值:
  成功 - 0
  失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
說明:
  此一函數是指定 Local 地址及 Port 給某一未定名之 Socket。使用者若不在意地址或 Port 的值,那麽他可以設定地址為 INADDR_ANY,及 Port 為 0;那麼Windows Sockets 會自動將其設定適當之地址及 Port (1024 到 5000之間的值),使用者可以在此 Socket 真正串連完成後,呼叫 getsockname() 來獲知其被設定的值。
bind() 函數要指定地址及 port,這個地址必須是執行這個程式所在機器的 IP地址,所以如果讀者在設計程式時可以將地址設定為 INADDR_ANY,這樣Winsock 系統會自動將機器正確的地址填入。如果您要讓程式只能在某台機器上執行的話,那麼就將地址設定為該台機器的 IP 位址。由於此端是 Server 端,所以版主們一定要指定一個 port 號碼給這個 socket。

listen()
設定 Socket 為監聽狀態,準備被串連。
格 式:
  int listen( SOCKET s, int backlog );
參 數:
  s Socket 的識別碼
  backlog 未真正完成串連前(尚未呼叫 accept 前)彼端的串連要求的最大個數
傳回值:
  成功 - 0
  失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
說明:
  使用者可利用此函數來設定 Socket 進入監聽狀態,並設定最多可有多少個在未真正完成串連前的彼端的串連要求。(目前最大值限制為 5, 最小值為1)

connect()
要求串連某一 TCP Socket 到指定的對方。
格 式:
  int connect( SOCKET s, const struct sockaddr FAR *name, int namelen );
參 數:
  s Socket 的識別碼
  name 此 Socket 想要串連的對方地址
  namelen name的長度
傳回值:
  成功 - 0
  失敗 - SOCKET_ERROR (呼叫WSAGetLastError()可得知原因)
說明:
  此函數用來向對方要求建立串連。若是指定的對方地址為 0 的話,會傳回錯誤值。當串連建立完成後,使用者即可利用此一 Socket 來做傳送或接收資料之用了。

accept()
接受某一 Socket 的串連要求,以完成 Stream Socket 的串連。
格 式:
  SOCKET accept(SCOKET s, SOCKADDR *addr,int FAR *addrlen )
參 數:
  s Socket的識別碼
  addr 存放來串連的彼端的地址
  addrlen addr的長度
傳回值:
  成功 - 新的Socket識別碼
  失敗 - INVALID_SOCKET (呼叫 WSAGetLastError() 可得知原因)
說明:
  Server 端的應用程式呼叫此一函數來接受 Client 端要求的 Socket 串連動作請求。

closesocket()
關閉某一Socket。
格 式:
  int closesocket( SOCKET s );
參 數:
  s Socket 的識別碼
傳回值:
  成功 - 0
  失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
說明:
  此一函數是用來關閉某一 Socket 。

WSACleanup()
結束 Windows Sockets DLL 的使用。
格 式:
  int WSACleanup( void );
參 數: 無
傳回值:
  成功 - 0
  失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
說明:
  當應用程式不再需要使用Windows Sockets DLL 時,須呼叫此一函數來登出使用,以便釋放其佔用的資源。

send()
使用串連式(connected)的 Socket 傳送資料。
格 式:
  int send( SOCKET s, const char FAR *buf, int len, int flags );
參 數:
  s Socket 的識別碼
  buf 存放要傳送的資料的暫存區
  len buf 的長度
  flags 此函數被呼叫的方式
傳回值:
  成功 - 送出的資料長度
  失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
說明:
  此函數用於將資訊從本端通過socket發送到遠程端。

recv()
自 Socket 接收資料。
格 式:
  int recv( SOCKET s, char FAR *buf, int len, int flags );
參 數:
  s Socket 的識別碼
  buf 存放接收到的資料的暫存區
  len buf 的長度
  flags 此函數被呼叫的方式
傳回值:
  成功 - 接收到的資料長度 (若對方 Socket 已關閉,則為 0)
  失敗 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
說明:
  此函數用來自串連式的 Datagram Socket 或 Stream Socket 接收資料。對 Stream Socket 言,版主們可以接收到目前 input buffer 內有效資料,但其數量不超過 len 的大小。

WSAStartup()
連結應用程式與 Windows Sockets DLL 的第一個函數。
格 式:
  int WSAStartup( WORD wVersionRequested,LPWSADATA lpWSAData );
參 數:
  wVersionRequested 可使用的 Windows Sockets API 最高版本
  lpWSAData 指向 WSADATA 資料的指標
傳回值:
  成功 - 0
  失敗 - WSASYSNOTREADY / WSAVERNOTSUPPORTED / WSAEINVAL
說明:
  此函數「必須」是應用程式呼叫到 Windows Sockets DLL 函數中的第一個,也唯有此函數呼叫成功後,才可以再呼叫其他 Windows Sockets DLL 的函數。此函數亦讓使用者可以指定要使用的 Windows Sockets API 版本,及擷取設計者的一些資訊。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.