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


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

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

image

 

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

     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.dll

 

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

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

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

image

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

image

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

4、“有連接”的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,用戶可自行下載。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM