找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

帖子
查看: 8083|回復: 0
收起左側

基于TCP和UDP的Socket編程

[復制鏈接]
ID:51090 發(fā)表于 2014-9-18 12:55 | 顯示全部樓層 |閱讀模式
在OSI的各層所使用的協(xié)議:
1.應用層:telnet,FTP,HTTP,DNS,SMTP,POP3
2.傳輸層:TCP,UDP
      TCP:面向連接的可靠的傳輸協(xié)議,通信前建立三次握手,握手成功后才能通信,對數(shù)據準確性要求較高的場合使用,如從網上載的安裝文件,不能缺少任何信息
      UDP:是無連接的,不可靠的傳輸協(xié)議,不需要建立連接,也沒有重傳和確認的機制,在實時性要求較高,但對數(shù)據準確度要求不是很高的場合使用,如視頻會議,在線觀看電影,當中丟失個別數(shù)據包并不影響整體的效果。
3.網絡層:IP

因為OSI七層結構較為復雜,所以使用較多的是TCP/IP模型,現(xiàn)在TCP/IP已經成為Internet上通用的工業(yè)標準
TCP/IP模型包括4個層次:應用層,傳輸層,網絡層,網絡接口

端口:
1.      為了標識通信實體中進行通信的進程(應用程序),TCP/IP協(xié)議提出了協(xié)議端口的概念
2.      端口是一種抽象的軟件結構(包括一些數(shù)據結構和I/O緩沖區(qū))。應用程序通過系統(tǒng)調用和某端口建立連接(binding)后,傳輸層傳給該端口的數(shù)據都被相應的進程所接收,相應進程發(fā)給傳輸層的數(shù)據都通過該端口輸出
3.      端口用一個整數(shù)型標識符來表示,即端口號。端口號跟協(xié)議相關,TCP/IP傳輸層的兩個協(xié)議TCP和UDP是完全獨立的的兩個軟件模塊,因此各自的端口號也相互獨立
4.      端口使用一個16位的數(shù)字來表示,它的范圍是0~65535,1024以下的端口號保留給預定義的服務,例如,http使用80端口

套接字(Socket)
1.      Socket的出現(xiàn),使得程序員可以很方便的訪問TCP/IP,從而開發(fā)各種網絡應用的程序
2.      套接字存在于通信區(qū)域中,通信區(qū)域也叫地址族,他是一個抽象的概念,主要用于通過套接字通信的進程的共有特性綜合在一起。套接字通常只與同一個區(qū)域的套接字交換數(shù)據。

套接字的類型
1.      流式套接字(SOCK_STREAM)
提供面向連接的,可靠的數(shù)據傳輸服務,數(shù)據無差錯,無重復的發(fā)送,且按發(fā)送的順序接收,基于TCP協(xié)議
2.      數(shù)據保式套接字(SOCK_DGRAM)
提供無連接的服務,數(shù)據包以獨立包形式發(fā)送,不提供無錯誤的保證,數(shù)據可能丟失或重復,且接收順序混亂,基于UDP協(xié)議

基于TCP(面向連接)的Socket編程
服務器端順序:
1.      加載套接字庫
2.      創(chuàng)建套接字(socket)
3.      將套接字綁定到一個本地地址和端口上(bind)
4.      將套接字設為監(jiān)聽模式,準備接收客戶請求(listen)
5.      等待客戶請求的到來;當請求帶來后,接受連接請求,返回一個新的對應于此次連接的套接字(accept)
6.      用返回的套接字和客戶端進行通信(send/recv)
7.      返回,等待另一個客戶請求
8.      關閉套接字(closesocket)

客戶端程序:
1.      加載套接字庫
2.      創(chuàng)建套接字(socket)
3.      向服務器發(fā)送連接請求(connect)
4.      和服務器端進行通信(send/receive)
5.      關閉套接字(closesocket)

基于UDP(面向無連接)的socket編程
服務器端(接收端)程序:
1.      加載套接字庫
2.      創(chuàng)建套接字(socket)
3.      將套接字綁定到一個本地地址和端口上(bind)
4.      等待接收數(shù)據(recvfrom)
5.      關閉套接字(closesocket)

客戶端(發(fā)送端)程序
1.      加載套接字庫
2.      創(chuàng)建套接字(socket)
3.      向服務器發(fā)送數(shù)據(sendto)
4.      關閉套接字(closesocket)

創(chuàng)建基于TCP協(xié)議的CS程序的Server端所涉及的相關函數(shù)說明(按使用的先后順序排列):
1.     int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData):作用是加載套接字庫和進行套接字庫的版本協(xié)商
a. 參數(shù)wVersionRequested:用于指定準備加載的Winsock庫的版本,高位字節(jié)指定所需要的Winsock庫的副版本,低位字節(jié)則是主版本,可用MAKEWORD(X,Y)(其中,x為高位字節(jié),y為低位字節(jié))方便獲得wVersionRequested的正確值。
b. 參數(shù)lpWSAData:指向WSADATA結構的指針,WSAStartup用其加載的庫版本有關的信息填在這個結構中
2.     SOCKET socket(int af,int type,int protocol):
a.      參數(shù)af指定地址族,對于TCP/IP協(xié)議的套接字,它只能是AF_INET(也可寫成PF_INET)。
b.      參數(shù)type指定Socket類型,對于1.1版本的Socket,它只支持兩種類型的套接字,SOCK_STREAM指定產生流式套接字,SOCK_DGRAM產生數(shù)據報套接字。
c.      參數(shù)protocol與特定的地址家族相關的協(xié)議,如果指定為0,那么他就會根據地址格式和套接字類別,自動為你選擇一個合適的協(xié)議。這是推薦使用的一種選擇協(xié)議的方式。
3.     int bind(SOCKET s,const struct sockaddr FAR *name,int namelen) :
a.      第一個參數(shù)指定要綁定的套接字,第二個參數(shù)指定該套接字的本地地址信息,是指向sockaddr結構的指針變量,由于該地址結構是為了所有的地址家族準備使用的,這個結構可能(通常會)隨使用的網絡協(xié)議不同而不同,所以,要用第三個參數(shù)指定該地址結構的長度。sockaddr機構定義如下:
struct sockaddr
{
      u_short sa_family;
      char sa_data[14];
}
b.      上述結構第一個字段指定該地址家族,在這里必須設為AF_INET。sa_data僅僅是表示要求一塊內存分配區(qū),起到占位的作用,該區(qū)域中指定與協(xié)議相關的具體地址信息。由于實際要求的只是內存區(qū),所以對于不同的協(xié)議家族,用不同的結構來替換sockaddr。在TCP/IP中,我們可以用SOCKADDR_IN結構來代替sockaddr,以方便我們填寫地址信息。
c.      struct SOCKADDR_IN{
    short sin_family;
    unsigned short sin_port;
    struct in_addr sin_addr;
    char sin_zero[8];
};
sin_family表示地址族,對于IP地址,sin_family成員將一直是AF_INET;成員sin_port指定的將要分配給套接字的端口;成員sin_addr給出的是套接字的主機IP地址;sin_zero只是一個填充數(shù),以使sockaddr_in結構和sockaddr結構的長度一樣,一般不用設置。
除了sin_family外,SOCKADDR_IN其他成員是按網絡字節(jié)順序表示的。所以需要進行轉換:htonl(INADDR_ANY),htons(6000),其中6000是端口號。
另外結構體的名稱大寫和小寫指的是同一個。
將IP地址指定為INADDR_ANY,允許套接字向任何分配給本機器的IP地址發(fā)送或接收數(shù)據。一般一臺機器一個網卡,但對于多網卡的機器,INADDR_ANY將簡化應用程序的編寫。將地址指定為INADDR_ANY,允許一個獨立的應用接受發(fā)自多個接口的回應。如果我們只想讓套接字使用多個IP中的一個地址,必須指定實際地址,要做到這一點,可以用inet_addr()函數(shù),這個函數(shù)需要一個IP地址(如192.168.80.88),返回一個適合分配給S_addr的u_long類型的數(shù)值。Inet_ntoa()函數(shù)完成相反的轉換,它接受一個in_addr結構體類型的參數(shù)并返回一個以點分十進制的IP地址字符串。
htonl把一個u_long類型從主機字節(jié)序轉換為網絡字節(jié)序。
htons把一個u_short類型從主機字節(jié)序轉換為網絡字節(jié)序。
4.     int listen(SOCKET s, int backlog):將套接字設置為監(jiān)聽模式,其中第二個參數(shù)設置等待請求連接的最大的值,即如果設置為n,則前n個請求會放置在系統(tǒng)的請求連接隊列中,應用程序會依次對這些請求進行服務,但第n+1個連接請求則會被拒絕。
5.     SOCKET accept(SOCKET s, const struct sockaddr FAR * addr, int FAR* addrlen):
從客戶端接收請求,并創(chuàng)建連接,如果連接成功,則會返回一個當前成功建立連接的套接字,該套接字不是上面創(chuàng)建的監(jiān)聽套接字,而是僅僅適用于當前的一個請求連接,如果建立連接失敗,則返回值是INVALID_SOCKET,并且可以適用WSAGetLastError()函數(shù)得到相關的失敗信息,具體的error code具體意義見MSDN中accept函數(shù)的最后部分的介紹
6.     send 函數(shù):向客戶端發(fā)送指定信息
7.     recv函數(shù):得到從客戶端傳遞過來的信息
8.     closesocket(SOCKET s):將指定的套接字關閉,從而釋放資源
9.     WSACleanup():終止對winsocket庫的使用
10. hello

服務器端的實現(xiàn)過程(Win32控制臺程序):
說明:
1.      對于winsock庫的類的使用,必須包含winsock2.h頭文件
2.      在setting中的link下的object/library modules中添加“ws2_32.lib”,注意和前面的字段之間用空格分隔
3.      本服務器程序先于服務器端啟動
#include "winsock2.h"
#include "stdio.h"
void main()
{
      //加載套接字(winsock)庫,加載這段代碼拷貝于MSDN中WSAStartup的介紹
      WORD wVersionRequested;
      WSADATA wsaData;
      int err;     
      wVersionRequested = MAKEWORD( 1, 1 ); //版本號為1.1
       err = WSAStartup( wVersionRequested, &wsaData );
      if ( err != 0 ) {                              
           return;
      }                                    

      if ( LOBYTE( wsaData.wVersion ) != 1 ||
        HIBYTE( wsaData.wVersion ) != 1 ) {
           WSACleanup( );
           return;
      }

      //創(chuàng)建套接字
      SOCKET sockServer=socket(AF_INET,SOCK_STREAM,0); //SOCK_STREAM參數(shù)設置為TCP連接
      SOCKADDR_IN addrServer; //設置服務器端套接字的相關屬性
      addrServer.sin_addr.S_un.S_addr=htonl(INADDR_ANY); //設置IP
      addrServer.sin_family=AF_INET;
      addrServer.sin_port=htons(1234); //設置端口號

      //將套接字綁定到本地地址和指定端口上
      bind(sockServer,(SOCKADDR*)&addrServer,sizeof(SOCKADDR));

      //將套接字設置為監(jiān)聽模式,并將最大請求連接數(shù)設置成5,超過此數(shù)的請求全部作廢
      listen(sockServer,5);

      SOCKADDR_IN addrClient; //用來接收客戶端的設置,包括IP和端口
      int len=sizeof(SOCKADDR);
      while(1) //不斷監(jiān)聽
      {
           //得到創(chuàng)建連接后的一個新的套接字,用來和客戶端進行溝通,原套接字繼續(xù)監(jiān)聽客戶的連接請求
           SOCKET sockConn=accept(sockServer,(SOCKADDR*)&addrClient,&len);
            if(sockConn!=INVALID_SOCKET) //創(chuàng)建成功
           {
                 char sendInfo[100];
                 //inet_ntoa將結構轉換為十進制的IP地址字符串
sprintf(sendInfo,"welcome %s to this test!",inet_ntoa(addClient.sin_addr));
                 //成功建立連接后向客戶端發(fā)送數(shù)據,結果將顯示在客戶端上
                 send(sockConn,sendInfo,strlen(sendInfo)+1,0);

                 //從客戶端接收數(shù)據,結果顯示在服務器上
                 char recvInfo[100];
                 recv(sockConn,recvInfo,100,0);
                 printf("%s\n",recvInfo);

                 //將本次建立連接中得到套接字關閉
                 closesocket(sockConn);
           }
           else
           {
                 int errCode=WSAGetLastError();
                 printf("the errcode is:%d\n",errCode);
           }         
      }
      //如果本程序不是死循環(huán),那么在此處還應添加以下代碼:
      closesocket(sockServer); //對一直處于監(jiān)聽狀態(tài)的套接字進行關閉
      WSACleanup(); //終止對winsocket庫的使用     
}

回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

手機版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表