Socket概述及TCP/IP的C++實現

網絡通訊實際是應用進程之間的通訊,而要完整的描述一個應用進程在網絡中的位置必須用 IP+端口;web

Socket就是一種在網絡中進行數據通訊的一種抽象描述。它是一種協議,本地地址,本地端口的抽象。編程

image

 

Socket它是面向C/S模型而設計的。windows

     Windows Sockets 規範,又稱爲WinSock,是微軟聯合其餘幾家公司推出的Windows 操做系統環境下的網絡編程接口。它繼承了UNIX下的Socket,是Windows下標準、通用的TCP/IP編程接口。緩存

     目前推出了1.1版本,這個版本只支持TCP/IP協議;2.x之後,支持更多的網絡與協議規範。好比無線網絡通訊等。服務器

     版本 1.1  頭文件 WINSOCK.h   連接庫文件  wsock32.lib  動態庫文件  Winsock.dll網絡

     版本 2.2  頭文件  WINSOCK2.h 連接庫文件  ws2_32.lib   動態庫文件 WS2_32.dllsocket

 

     Winsock 提供了兩種形式的Socket:流式套接字(stream socket)和 數據報套接字(datagram socket)。其中,流式套接字只TCP協議,數據報套接字支持UDP協議。tcp

     如下是基於套接字的TCP協議實現過程:函數

基於流式套接字的編程模式以下:post

image

   基於數據報套接字的編程模式以下:

image

下面這個是一個特例,也就是說在UDP中也能夠由connect(),經過connect()將本地的UDP 的socket與遠程的socket鏈接起來。

四、「有鏈接」的UDP


雖然UDP是無鏈接的,可是也能夠經過調用connect()將本地的UDP socket FD與一個遠程的UDP socket FD鏈接起來——只須要指定這個遠程sockFD的地址,假設這個地址是sockaddr_in remoteSockAddr,代碼以下:
  1.     if (connect(sockFD,
  2.                 (sockaddr*)&remoteSockAddr,
  3.                 sizeof(remoteSockAddr)) < 0) {
  4.         sockClass::error_info("connect() failed.");
  5.     }
複製代碼
創建鏈接後的UDP RecvQ就不會將非來自remoteSockAddr的數據包收入。

請注意UDP的connect()與TCP的 connect()很不相同,TCP是鏈接服務器的監聽socket,而且會阻塞直到服務器調用accept()。通常的說法,UDP的鏈接並不會改變 UDP的各類特色,好比,即便鏈接,UDP也不知道遠程主機是否在線鏈接或者是否斷開——可是,我我的認爲,改變了本機的RecvQ接收數據包的過濾機制,也就改變了UDP本來能夠接收來自任何地址信息的屬性。

若是但願斷開UDP的鏈接,須要使用一個特定的「斷開」地址,代碼以下:
  1.     sockaddr descon_sock_addr;
  2.     memset(&descon_sock_addr, 0, sizeof(descon_sock_addr));
  3.     descon_sock_addr.sa_family = AF_UNSPEC;
  4.     if (connect(sockFD,
  5.                 &descon_sock_addr,
  6.                 sizeof(descon_sock_addr)) < 0) {
  7.         sockClass::error_info("des connect() failed.");
  8.     }
複製代碼
請注意這裏的地址族AF_UNSPEC直接賦值給了一個sockaddr結構。我試過,使用sockaddr_in也是能夠的,可是不管是哪一個結構,首先都得將整個結構對象清零,不然可能報錯。

 

 

 

注:咱們能夠利用WinSock API函數也能夠利用MFC提供的WinSock封裝類。

      有一點小疑惑,網絡編程中爲什麼不須要客戶端IP地址及端口號即:不須要客戶端套接字地址。

由於,咱們在計算機網絡中,咱們在傳數據時,它會自動帶上本機的IP地址。好比,客戶端向服務器端發送某個

數據報時,它確定會在報頭加上源IP地址,而目的IP地址及端口號這個就須要本身手動加入數據報中,否則,當路由器進行解析

的時候,就不知道該往哪一個網絡裏面的主機裏面傳送了。

 

      根據上面的介紹,我想你們必定想練練手,好,下面我將本身的demo提供給你們。最後會提供給你們一個連接的。

廢話很少說,直接看結果:如下是UDP傳輸結果,上面是服務器端程序,顯示的數據是從客戶端發過去的。

第二個界面與上面相反,客戶端程序,顯示的數據從服務器端發過來的。固然,咱們在實際編程時,能夠有的放矢,

有時候不須要回傳數據進行驗證之類的。不是雙工模式,而是單工模式。

image

如下是TCP的執行結果:

先打開服務器端,顯示以下:

image

再開啓客戶端,顯示以下:

image

 

很少解釋,一切全在代碼中。

socketserver.cpp文件:

  1 // socketserver.cpp : 定義控制檯應用程序的入口點。
  2 //
  3 
  4 #include "stdafx.h"
  5 #include "conio.h"
  6 #include "windows.h"
  7 //socket頭文件
  8 #include "winsock.h"
  9 //socket庫的lib
 10 #pragma comment(lib,"ws2_32.lib")
 11 
 12 void TCPServer()
 13 {
 14     /***************建立服務器端套接字SOCKET*******************/
 15     /*******socket()函數解釋:IP協議族,數據流方式,TCP協議****/
 16    SOCKET socksvr=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
 17    if(INVALID_SOCKET == socksvr)
 18    {
 19        return;
 20    }
 21    /*************創建服務器端套接字地址***********************/
 22    /********************綁定IP和端口號******************/
 23    struct sockaddr_in svraddr = {0};
 24    svraddr.sin_family = AF_INET;//表明internet協議族
 25    /**htons()函數解釋:是將u_short型變量從主機字節順序變換爲TCP/IP網絡字節順序**/
 26    /**這裏涉及大小端系統問題。intel處理器是低位字節在****************/
 27    /**較低地址存放,而高位字節在較高地址存放,與網絡字節順序相反,故須要調換過來****/
 28    svraddr.sin_port = htons(5678);
 29   //htonl()函數是將u_long型變量從主機字節順序變爲TCP/IP網絡字節順序。
 30    svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//此宏爲0,當前機器上任意IP地址,也能夠指定當前機的ip和端口。
 31   //綁定,將服務器端套接字與服務器端套接字地址綁定
 32    bind(socksvr,(struct sockaddr *)&svraddr,sizeof(svraddr));//指定名字,類型,長度。綁定套接字。
 33    //偵聽
 34    listen(socksvr,SOMAXCONN);//第一個參數是套接字,第二個參數是等待鏈接隊列的最大長度。
 35    //等候客戶端創建鏈接 
 36    printf("等候客戶端.......\n");
 37    //創建客戶端套接字地址,主要是爲了接收客戶端返回參數之用
 38    struct sockaddr_in clientaddr = {0};
 39    int nLen = sizeof(clientaddr);
 40    //如下是創建客戶端套接字並創建鏈接函數。有一個確認的過程。
 41    //注:後面填的是客戶端地址長度的地址。
 42    SOCKET sockclient = accept(socksvr,(struct sockaddr*)&clientaddr,&nLen);//創建鏈接函數
 43    printf("客戶端已鏈接\n");
 44    /********如下是數據收發部分*********/
 45    //先接收後發送,由上面知,數據已在sockclient中,咱們只需讀此結構即可知曉數據
 46    CHAR szText[100] = {0};
 47    //接收緩衝區數據 
 48    recv(sockclient,szText,100,0); //接收函數,一直處於偵聽模式,等待服務器端發送數據的到來。
 49    printf("%s\n",szText);
 50    CHAR szSend[100] = "Hello Client";
 51    send(sockclient,szSend,sizeof(szSend),0);//發送函數。
 52   /****accept/recv/send 都是堵塞函數,須要把因此的數據都接收完或發送完才能夠工做。*****/
 53     // getch();//暫停一下
 54    //關閉socket
 55     closesocket(sockclient);
 56     closesocket(socksvr);
 57   
 58 
 59 }
 60 
 61 void UDPServer()
 62 {
 63     //建立socket
 64     SOCKET socksvr = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
 65     if(INVALID_SOCKET == socksvr)
 66     {
 67         return ;
 68     }
 69     //服務器套接字地址
 70     //綁定ip與端口,先定義端口等一些信息。
 71     struct sockaddr_in svraddr = {0};
 72     svraddr.sin_family = AF_INET;
 73     svraddr.sin_port = htons(5780);
 74     svraddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
 75     bind(socksvr,(struct sockaddr*)&svraddr,sizeof(svraddr));
 76      
 77     /********如下是數據收發部分*********/
 78     //客戶端套接字地址,接收客戶端數據時須要用,數據都在套接字裏面。
 79     CHAR szRecv[100] = {0};
 80     struct sockaddr_in clientaddr = {0};
 81     int nLen = sizeof(clientaddr);
 82     /*下面函數前四個參數同TCP接收數據函數recv()同樣,後兩個中,一個是返回發送*******/
 83     /*數據地址的主機的地址,包括IP地址以及端口號,最後一個爲地址長度的地址。*******/
 84     /*此函數中,先是服務器端的套接字,後是客戶端的地址*/
 85     //從後往前讀此函數
 86     recvfrom(socksvr,szRecv,100,0,(struct sockaddr*)&clientaddr,&nLen);//構造ip地址
 87     printf("%s\n",szRecv);
 88 
 89    //注1:該程序也能夠向客戶端發送數據。
 90    //注2:服務器端中,必須也是先接收後發送,否則,咱們沒法知道客戶端的地址。下面函數中clientaddr已知曉
 91     CHAR szSend[100] = "hello udp client";
 92     //從前日後讀此函數
 93      sendto(socksvr,szSend,100,0,(struct sockaddr*)&clientaddr,nLen);//發送時構造ip地址和端口。
 94 
 95 //    getch();//能夠暫停顯示,這個很重要。
 96 
 97     //關閉socket
 98     closesocket(socksvr);
 99 
100 
101 }
102 
103 
104 
105 
106 
107 int main(int argc, _TCHAR* argv[])
108 {
109     //初始化socket庫
110     WSADATA wsa = {0}; //WinSockApi 取WSA+DATA組成套接字結構體
111     WSAStartup(MAKEWORD(2,2),&wsa);
112     //服務器
113     TCPServer();
114     //UDPServer();
115     //清理套接字資源
116     WSACleanup();
117     getch();//暫停一下
118 
119     return 0;
120 }

 

socketclient.cpp 文件:

 1 // socketclint.cpp : 定義控制檯應用程序的入口點。
 2 //
 3 
 4 #include "stdafx.h"
 5 #include "conio.h"
 6 #include "windows.h"
 7 #include "winsock.h"
 8 #pragma comment(lib,"ws2_32.lib")
 9 
10 void TCPClient()
11 {
12      //建立socket
13     SOCKET sockclient = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
14     if(INVALID_SOCKET == sockclient)
15     { 
16         return;
17     }
18     //鏈接服務器,創建服務器端套接字地址
19     struct sockaddr_in addr = {0};
20     addr.sin_family = AF_INET;
21     addr.sin_port = htons(5678);
22     //對於inet_addr()函數,它是把「xxx.xxx.xxx.xxx」形式表示的IPV4地址,轉換爲IN_ADDR結構體可以
23     //接收的形式(unsigned long型,由於IN_ADDR結構體中的負責接收的S_addr成員變量的類型是unsigned long型)
24     addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//本機ip
25 
26     //向服務器發出鏈接請求,固然咱們也能夠經過connet函數的返回值判斷到底有無鏈接成功。
27     int iRetVal = connect(sockclient,(struct sockaddr*)&addr,sizeof(addr));
28     if(SOCKET_ERROR == iRetVal)
29     {
30         printf("服務器鏈接失敗!");
31         closesocket(sockclient);
32         return;
33     }
34     printf("服務器鏈接成功!\n");
35     //數據收發
36     CHAR szSend[100] = "hello server";   //客戶端  先發後收
37     send(sockclient,szSend,sizeof(szSend),0);  //發送函數,能夠經過返回值判斷髮送成功與否。
38     
39     //接收服務器回傳的數據
40     CHAR szRecv[100] = {0};
41     recv(sockclient,szRecv,100,0); //接收函數
42     printf("%s\n",szRecv);//表示以字符串的格式輸出服務器端發送的內容。
43     
44     //  getch();//暫停一下
45     //關閉socket
46     closesocket(sockclient);
47 }
48 void UDPClient()
49 {
50     //建立SOCKET ,ip協議族,數據報方式,udp協議。
51     SOCKET sockclient = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
52     if(INVALID_SOCKET == sockclient)
53     {
54         return ;
55     }
56     //數據收發,服務器端套接字地址
57     struct sockaddr_in svraddr = {0};
58     svraddr.sin_family = AF_INET;
59     svraddr.sin_port = htons(5780);
60     svraddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//指定服務器端的IP與端口。
61     CHAR szSend[100] = "hello udp server";
62     /*此函數先是客戶端的套接字,而後是服務器端地址*/
63     //簡單理解爲:從函數前面的客戶端套接字的發送數據緩存區中將數發送給服務器端地址
64     sendto(sockclient,szSend,100,0,(struct sockaddr*)&svraddr,sizeof(svraddr));//發送時構造ip地址和端口。
65     
66     //注:該程序也能夠接收服務器端回傳的數據。
67     CHAR szRecv[100];  
68     //簡單理解爲:從函數後面的服務器端地址中取數到客戶端套接字的接收緩衝區szRecv中
69     int len = sizeof(svraddr);
70     recvfrom(sockclient,szRecv,100,0,(struct sockaddr*)&svraddr,&len);
71     printf("%s \n",szRecv);
72     //關閉socket
73     closesocket(sockclient);
74 }
75 
76 int main(int argc, _TCHAR* argv[])
77 {
78     //初始化socket庫
79     WSADATA wsa = {0};
80     WSAStartup(MAKEWORD(2,2),&wsa);
81     //tcp客戶端
82     TCPClient();
83     //UDPClient();
84     //清理套接字資源
85     WSACleanup();
86     getch();
87     
88     
89     return 0;
90 }

已傳至PUDN,文件名1_1-socket,用戶可自行下載。

相關文章
相關標籤/搜索