【C++】socket編程(二):最簡單的socket通信(上)


-----初更----2018年1月26日(轉載請注明出處)----

前言:

昨天把socket基本的知識點總結了一下,今天就開始寫第一個socket程序吧,這篇文章中我們要做到的就是本機和本機通信,完成一次發送接收,雖然看着比較簡單,但是理解起來也需要費一番功夫,尤其是每個函數的使用。gogogo

#0x01    加載socket庫

從現在開始,程序要分為windows版和linux版,linux版就是加載一堆頭文件,windows呢就是加載dll后聲明一些函數,我們一點點開始,先看windows,想直接看linux的可以跳過windows

windows:

 1 #include <stdio.h>
 2 #include <winsock2.h>//socket頭文件
 3 #pragma comment (lib,"ws2_32.lib")//加載socket
 4 
 5 int main()
 6 {
 7     WSADATA wsaData;//生成句柄
 8     WSAStartup(MAKEWORD(2, 2), &wsaData);//初始化
 9 
10     /*codding*/
11 
12     WSACleanup();//結束調用
13     return 0;
14 }

這就是windows的大體結構,ws2_32.lib用的是編譯時加載,具體的加載方式和顯示加載還是隱式加載大家可以自行查閱,這里不做深究,下面對函數進行分析:

首先看一下WSADATA結構體:

typedef struct WSAData {
  WORD           wVersion;
  WORD           wHighVersion;
  char           szDescription[WSADESCRIPTION_LEN+1];
  char           szSystemStatus[WSASYS_STATUS_LEN+1];
  unsigned short iMaxSockets;
  unsigned short iMaxUdpDg;
  char FAR       *lpVendorInfo;
} WSADATA, *LPWSADATA;

具體每個變量是做什么的就要大廢周章了,大家可以點擊這里查看微軟官方的解釋,大概意思就是存放期望調用方使用的Windows套接字規范的版本

再看一下WSAStartup()函數,原型:

int WSAStartup(
  _In_  WORD      wVersionRequested,
  _Out_ LPWSADATA lpWSAData
);

第二個參數lpWSADATA就是WSADATA結構體,第一個WORD參數,用的MAKEWORD()函數,原型如下:

WORD MAKEWORD(
   BYTE bLow,
   BYTE bHigh
);

這個函數主要就是返回一個WORD參數。

接下來看看WSAClearup()函數:

int WSACleanup(void);

官方解釋是:The WSACleanup function terminates use of the Winsock 2 DLL(Ws2_32.dll).意思就是不再使用Ws2_32.dll庫了。

到此,socket庫的調用就說完了,具體還有很多很多沒有講,讀者可以自查資料深入研究,最后一節我會給使用到的鏈接,目前就會使用就行。

Linux:

linux下比較易於理解,主要就是包含一堆頭文件就行,demo如下:

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <arpa/inet.h>
 6 #include <sys/socket.h>
 7 #include <netinet/in.h>
 8 int main()
 9 {
10 /*codding*/
11 }

前三個是C++基礎文件,不做贅述,第四個unisted.h是linux下特有的頭文件,主要包含了read,write等函數,后三個主要是socket需要的庫文件。

#0x02    socket函數

前面講了一大推分量很小的東西,現在才開始真正的socket編程,這里要明白一個概念,什么是句柄,這個概念在本案例中可以初步認為就是一個int(顯然這是錯誤的,目前先這么理解,因為在linux中並沒有句柄)。首先需要調用socket函數,分為windows和linux版本

windows:

SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

Linux:

int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

其中SOCKET指的就是句柄,而在linux中用int代替。

現在已經創建一個套接字,供后面的程序使用,記住一個連接用一個套接字,什么意思,就是假如你的機器A和另一個機器B需要TCP連接,這時需要創建一個套接字,然后你的機器A和機器B需要另一個UDP連接,這時候你就需要再創建一個套接字。

win和linux用法是一樣的,我們來看一下他的原型:

SOCKET WSAAPI socket(
  _In_ int af,
  _In_ int type,
  _In_ int protocol
);

下面看一個每個值得取值,這里僅僅列出常見取值,其他取值有興趣可以參考這個

 af:協議族

  • AF_INET(PF_INET):使用IPv4
  • AF_INET6(PF_INET6):使用IPv6

 type:套接字的類型

  • SOCK_STREAM:面向連接的數據傳輸方式,也就是TCP連接
  • SOCK_DGRAM:無連接的數據傳輸方式,也就是UDP連接

 protool:傳輸協議(要和協議和套接字配套,本例指的是TCP或UDP)

  • IPPROTO_TCP:TCP協議
  • IPPROTO_UDP:UDP協議

好了,現在已經創建了套接字, 接下來就要給套接字綁定ip和端口了,查看bind()函數:

#0x03    bind()函數

值得一提的是,bind函數一般用在服務器上,也就是服務端,稍后會講解客戶端需要用什么函數,先看一下bind使用:

 windows:

bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));//Windows

 Linux:

bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

 以上都是實例,參數可能不是很准確,所以看一下函數原型不就知道該怎么寫了嘛 

int bind(
  _In_ SOCKET                s,
  _In_ const struct sockaddr *name,
  _In_ int                   namelen
);

 其中,第一個參數s指的就是一開始創建的套接字,不在贅述,這里主要講一下后面兩個參數:

第二個參數是要傳入一個sockaddr型的結構體,而這個結構體長啥樣呢,我們來看一下

struct sockaddr{
    sa_family_t  sin_family;   //地址族(Address Family),也就是地址類型
    char         sa_data[14];  //IP地址和端口號
};

第一個參數還好說,協議族,和創建socket的第一個參數一樣,俺么第二個參數呢,14位的地址和端口號應該怎么填?

這時候,我們就要借助另外一個結構體sockaddr_in(至於為什么要借助另外一個結構體,這里涉及到歷史遺留問題,感興趣可以自行查閱)

struct sockaddr_in{
    sa_family_t     sin_family;   //地址族(Address Family),也就是地址類型
    uint16_t        sin_port;     //16位的端口號
    struct in_addr  sin_addr;     //32位IP地址
    char            sin_zero[8];  //不使用,一般用0填充
};

這里把每個變量位數已經標好了,可以看到位數和上一個結構體一樣,所以這里我們用sockaddr_in結構體定義好后替換成sockaddr結構體,都坐下,正常操作是:

    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));//各位清零
    sockAddr.sin_family = PF_INET;//協議族
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");//ip地址
    sockAddr.sin_port = htons(1996);//端口
    bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));//Windows

首先創建一個sockaddr結構體,將各位清零(防止亂碼),然后設置協議族,ip地址,端口等,最后將這些信息綁定到最開始創建的句柄上,這樣一個套接字就完成了綁定

這里介紹一下inet_addr函數,這個函數是將一個ip字符串轉換成unsigned long,同樣也是歷史因素,想具體了解可以查看這里,不過zaiwindows下vs建議換成inetPton(),不過我就是任性啊,就不換,嘻嘻,在linux下這個函數可不可以替換我沒有驗證,如果有人驗證了可以在這篇文章下面留言

htons函數返回一個網絡字節數,看一下官方解釋,還有htonf()htonl()htonll()等函數,可以好好研究一下

#0x04    connent()函數

 connent函數和bind函數用法一樣,只不過connent是客戶端的程序,我給列一下函數原型,用法可以在下篇看到

int connect(
  _In_ SOCKET                s,
  _In_ const struct sockaddr *name,
  _In_ int                   namelen
);

函數原型,參數和bind一樣,所以用法也一樣,不多說。

#0x05 listen()函數

listen函數需要在connect之前,在bind函數之后的服務器端的函數,看名字就知道是用來監聽的函數,用法:

Windows/Linux:

listen(servSock, 20);

 

原型是:

int listen(
  _In_ SOCKET s,
  _In_ int    backlog
);

 

比較容易理解,第一個參數就是bind后的套接字,而第二個參數指的是等待連接隊列的最大長度,啥意思?

就是說socket有2個緩沖區,一個是發送緩沖區,一個是接受緩沖區,他們有固定的大小,超過了這個大小,數據不就接收不到了嘛,這對文件傳輸可以一個麻煩,所以這個等待連接隊列指的是可以等待多少個緩沖區大小的數據,這里了解到這里就可以了,具體應用可以查看后面的文件傳輸內容在深入研究

#0x06    參考連接

【1】http://c.biancheng.net/cpp/html/3032.html

【2】http://blog.csdn.net/y396397735/article/details/50655363

【3】http://blog.csdn.net/tennysonsky/article/details/45621341

【4】http://msdn.microsoft.com

 


免責聲明!

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



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