學習socket的時候看的是Linux的教程,在Windows中寫,發現有一些不一樣,比如在關閉socket的時候用
“close()”
,運作的時候就會彈出如下錯誤,後來發現要用
“closesocket()”
。
還有Linux中的
"fork()"
複制并開啟程序,而Windows中則需要
“HANDLE threadaccept = CreateThread(NULL, 0, do_service, &connSock, 0, NULL);”
。要有讀取和發送資訊等等。
為此,我找到了如下文章,放于此處,以便日後查閱。
01 加載socket庫
從現在開始,程式要分為windows版和linux版,linux版就是加載一堆頭檔案,windows呢就是加載dll後聲明一些函數,我們一點點開始,先看windows,想直接看linux的可以跳過windows
windows:
#include <stdio.h>
#include <winsock2.h>//socket頭檔案
#pragma comment (lib,"ws2_32.lib")//加載socket
int main()
{
WSADATA wsaData;//生成句柄
WSAStartup(MAKEWORD(2, 2), &wsaData);//初始化
/*codding*/
WSACleanup();//結束調用
return 0;
}
這就是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()函數:
官方解釋是:The WSACleanup function terminates use of the Winsock 2 DLL(Ws2_32.dll).意思就是不再使用Ws2_32.dll庫了。
到此,socket庫的調用就說完了,具體還有很多很多沒有講,讀者可以自查資料深入研究,最後一節我會給使用到的連結,目前就會使用就行。
Linux:
linux下比較易于了解,主要就是包含一堆頭檔案就行,demo如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main()
{
/*codding*/
}
前三個是C++基礎檔案,不做贅述,第四個unisted.h是linux下特有的頭檔案,主要包含了read,write等函數,後三個主要是socket需要的庫檔案。
02 socket函數
前面講了一大推分量很小的東西,現在才開始真正的socket程式設計,這裡要明白一個概念,什麼是句柄,這個概念在本案例中可以初步認為就是一個int(顯然這是錯誤的,目前先這麼了解,因為在linux中并沒有句柄)。首先需要調用socket函數,分為windows和linux版本
windows:
Linux:
其中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()函數:
03 bind()函數
值得一提的是,bind函數一般用在伺服器上,也就是服務端,稍後會講解用戶端需要用什麼函數,先看一下bind使用:
windows:
== Linux:==
以上都是執行個體,參數可能不是很準确,是以看一下函數原型不就知道該怎麼寫了嘛
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,同樣也是曆史因素,想具體了解可以檢視這裡,不過在windows下vs建議換成inetPton(),不過我就是任性啊,就不換,嘻嘻,在linux下這個函數可不可以替換我沒有驗證,如果有人驗證了可以在這篇文章下面留言
htons函數傳回一個網絡位元組數,看一下官方解釋,還有htonf()htonl()htonll()等函數,可以好好研究一下
04 connent()函數
connent函數和bind函數用法一樣,隻不過connent是用戶端的程式,我給列一下函數原型,用法可以在下篇看到
int connect(
_In_ SOCKET s,
_In_ const struct sockaddr *name,
_In_ int namelen
);
函數原型,參數和bind一樣,是以用法也一樣,不多說。
05 listen()函數
listen函數需要在connect之前,在bind函數之後的伺服器端的函數,看名字就知道是用來監聽的函數,用法:
Windows/Linux:
原型是:
int listen(
_In_ SOCKET s,
_In_ int backlog
);
比較容易了解,第一個參數就是bind後的套接字,而第二個參數指的是
等待連接配接隊列的最大長度
,啥意思?
就是說socket有2個緩沖區,一個是發送緩沖區,一個是接受緩沖區,他們有固定的大小,超過了這個大小,資料不就接收不到了嘛,這對檔案傳輸可以一個麻煩,是以這個等待連接配接隊列指的是可以等待多少個緩沖區大小的資料,這裡了解到這裡就可以了,具體應用可以檢視後面的檔案傳輸内容在深入研究
參考連接配接:
【1】https://www.cnblogs.com/elve960520/articles/8361614.html
【2】http://c.biancheng.net/cpp/html/3032.html
【3】http://blog.csdn.net/y396397735/article/details/50655363
【4】http://blog.csdn.net/tennysonsky/article/details/45621341
【5】http://msdn.microsoft.com