將底層複雜的協議體系,執行流程,進行了封裝,封裝完的結果,就是一個SOCKET,也就是說,SOCKET是咱們調用協議進行通訊的操做接口數組
數據類型:SOCKET 轉定義:unsigned int服務器
在系統裏每個socket對應着==惟一的一個整數==,好比23,對應着socket的協議等信息,在通訊中,就使用這些整數進行通訊,系統會自動去找這些整數所對應的協議
每一個客戶端有一個socket,服務器有一個socket,通訊時就是經過socket,來表示和誰傳遞信息網絡
/* 函數原型 */ SOCKET socket( int af, /*地址的類型*/ int type, /*套接字類型*/ int protocol /*協議類型*/ );
地址類型 | 形式 |
---|---|
==AF_INET== | 192.168.1.103(IPV4,4字節,32位地址) |
AF_INET6 | 2001:0:3238:DFE1:63::FEFB(IPV6,16字節,128位地址) |
AF_BTH | 6B:2D:BC:A9:8C:12(藍牙) |
AF_IRDA | 紅外 |
通訊地址不止只有IP地址
類型 | 用處 |
---|---|
==SOCK_STREAM== | 提供帶有OOB數據傳輸機制的順序,可靠,雙向,基於鏈接的字節流。 使用傳輸控制協議(TCP)做爲Internet地址系列(AF_INET或AF_INET6) |
SOCK_DGRAM | 支持數據報的套接字類型,它是固定(一般很小)最大長度的無鏈接,不可靠的緩衝區。使用用戶數據報協議(UDP)做爲Internet地址系列(AF_INET或AF_INET6) |
SOCK_RAW | 提供容許應用程序操做下一個上層協議頭的原始套接字。 要操做IPv4標頭,必須在套接字上設置IP_HDRINCL套接字選項。 要操做IPv6標頭,必須在套接字上設置IPV6_HDRINCL套接字選項 |
SOCK_RDW | 提供可靠的消息數據報。 這種類型的一個示例是Windows中的實用通用多播(PGM)多播協議實現,一般稱爲可靠多播節目 |
SOCK_SEQPACKET | 提供基於數據報的僞流數據包 |
協議類型 | 用處 |
---|---|
IPPROTO_TCP | 傳輸控制協議(TCP) |
IPPROTO_UDP | 用戶數據報協議(UDP) |
IPPROTO_ICMP | Internet控制消息協議(ICMP) |
IPPROTO_IGMP | Internet組管理協議(IGMP) |
IPPROTO_RM | 用於可靠多播的PGM協議 |
==填寫0==表明系統自動幫咱們選擇協議類型socket
== 參數一、二、3是要相互配套使用的==,不能隨便填,使用不一樣的協議就要添加不一樣的參數
if (INVALID_SOCKET == socketServer) { int a = WSAGetLastError( ); WSACleanup(); return 0; }
SOCKET socketListen = socket(AF_INET,SOCK_STREAM,0); if(INVALID_SOCKET == socketListen) { int a = WSAGetLastError( ); WSACleanup(); return 0; }
做用:給socket綁定端口號與地址tcp
端口號函數
int bind ( SOCKET s, /*服務器建立的socket*/ const sockaddr *addr, /*綁定的端口和具體地址*/ int namelen /*sizeof(sockaddr)*/ );
定義一個==SOCKADDR_IN==數據類型,是一個結構體:測試
typedef struct sockaddr_in { #if(_WIN32_WINNT < 0x0600) short sin_family; /* 地址類型 */ #else //(_WIN32_WINNT < 0x0600) ADDRESS_FAMILY sin_family; #endif //(_WIN32_WINNT < 0x0600) USHORT sin_port; /* 端口號 */ IN_ADDR sin_addr; /* IP地址 */ CHAR sin_zero[8]; } SOCKADDR_IN, *PSOCKADDR_IN;
其中IN_ADDR sin_addr; 又是一個結構體
typedef struct in_addr { union { struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b; struct { USHORT s_w1,s_w2; } S_un_w; ULONG S_addr; } S_un; #define s_addr S_un.S_addr /* can be used for most tcp & ip code */ #define s_host S_un.S_un_b.s_b2 // host on imp #define s_net S_un.S_un_b.s_b1 // network #define s_imp S_un.S_un_w.s_w2 // imp #define s_impno S_un.S_un_b.s_b4 // imp # #define s_lh S_un.S_un_b.s_b3 // logical host } IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;
127.0.0.1 本地迴環地址,用於本地網絡測試,數據不出計算機端口號:0~65535spa
0~1013:系統保留佔用端口號調試
si.sin_port = ==htons(12345)==;
si.sin_addr.S_un.S_addr = ==inet_addr("127.0.0.1")==;
si.sin_family = ==AF_INET==;
sizeof(sockadd)code
if (SOCKET_ERROR == bind(socketListen,(struct sockaddr*)&sockAddress,sizeof(sockAddress))) { printf("bind fail!"); //int nError = ::WSAGetLastError(); //關閉庫 closesocket(socketListen); WSACleanup(); return -1; }
struct SOCKADDR_IN si; si.sin_family = AF_INET; /* 地址協議 */ si.sin_port = htons(12345); /* 端口號 */ si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); /*IP地址,點分十進制*/ if (SOCKET_ERROR == bind(socketListen,(struct sockaddr*)&sockAddress,sizeof(sockAddress))) { printf("bind fail!"); //int nError = ::WSAGetLastError(); //關閉庫 closesocket(socketListen); WSACleanup(); return -1; }
做用:將socket置於偵聽傳入鏈接的狀態(服務器能夠接受客戶端連接了)
int WSAAPI listen ( SOCKET s, /*服務器端的socket*/ int backlog /*掛起鏈接隊列的最大長度*/ );
好比有100個用戶連接請求,可是系統一次只能處理20個,那麼剩下的80個不能不理人家,因此係統就建立個隊列記錄這些暫時不能處理,過一下子處理的連接請求,依前後順序處理,那這個隊列到底多大?就是這個參數設置,好比2,那麼就容許兩個新連接排隊的。這個確定不能無限大,那內存不夠了。
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN)) { int a = WSAGetLastError(); WSACleanup(); return 0; }
if (SOCKET_ERROR == listen(socketListen,2)) { printf("listen fail!"); //關閉庫 closesocket(socketListen); WSACleanup(); return -1; }
在服務器端上建立一個新的socket,將客戶端的信息和新的socket綁定在一個,一次只能建立一個
SOCKET WSAAPI accept ( SOCKET s, /*服務器的socket*/ sockaddr *addr, /*返回客戶端地址端口信息結構體*/ int *addrlen /*返回參數2的類型大小*/ );
理解:經過服務器端的socket,讀取客戶端的信息
/*直接經過函數獲得客戶端的端口號、IP地址*/ getpeername(newSocket,(struct sockaddr *)&sockClient,&nLen); /*獲得本地服務器信息*/ getsockname(sSocket,(struct sockaddr *)&addr,&nLen);
若是參數二、3都填==NULL==,那麼就是不直接獲得客戶端的地址和端口號
if (INVALID_SOCKET == socketClient) { int a = WSAGetLastError(); closesocket(socketServer); WSACleanup(); }
accept() 返回一個新的套接字來和客戶端通訊,addr保存了客戶端的IP地址和端口號,而s是服務器端的套接字,注意區分。接下來和客戶端通訊時,要使用這個新生成的套接字,而不是原來服務器端的套接字。
SOCKET newSocket; newSocket = accept(socketListen, NULL, NULL); if (INVALID_SOCKET == newSocket) { printf("listen fail!" ); //關閉庫 closesocket(socketServer); WSACleanup(); }
沒有客戶端連接就一直卡着
數據的接收都是協議自己在作,也就是socket底層在操做,系統有一段緩衝區,儲存着接收到的數據。recv的做用,就是經過socket找到了這個緩衝區,把數據複製放到本身的數組中
int recv ( SOCKET s, /*客戶端的socket,每一個客戶端對應惟一的socket*/ char *buf, /*數據緩衝區*/ int len, /*數據長度*/ int flags /*讀取方式*/ );
解釋:由於網絡傳輸的最大單元是1500字節,這是協議規定的
通常是==參數2的字節-1==,把「0」字符串結尾保留下來
讀取方式 | 做用 |
---|---|
==0== | 從系統緩衝區讀到buf緩衝區,將系統緩衝區的數據刪掉,讀出來就刪 |
MSG_PEEK | 數據複製到buf緩衝區,可是數據不從系統緩衝區刪除 |
MSG_OOB | 傳輸一段數據,再外帶加一個額外的特殊數據 |
MSG_WAITTALL | 直到系統緩衝區字節數知足參數3的數目,纔開始讀取 |
char szRecvBuffer[1500] = {0}; /* 字符數組 */ int nReturnValue = recv(newSocket, szRecvBuffer, sizeof(szRecvBuffer)-1, 0); if (0 == nReturnValue) { //客戶端正常關閉 服務端釋放Socket continue ; } else if (SOCKET_ERROR == nReturnValue) { //網絡中斷 printf("客戶端中斷鏈接"); continue; } else { //接收到客戶端消息 printf("Client Data : %s \n",szRecvBuffer); }
int WSAAPI send ( SOCKET s, /*客戶端socket*/ const char *buf, /*發送的字符數組*/ int len, /*發送長度*/ int flags /*發送方式*/ );
發送的時候,協議要進行包裝,加上協議信息(包頭),鏈路層14字節,ip頭20字節,tcp頭20字節,數據結尾還要有狀態確認,加起來也幾十個字節,因此不能寫1500個字節,最多1400字節(或者1024)。
系統會分片處理,分兩個包,假設2000字節的包,1400+包頭=1500,600+包頭=700。那麼系統就要分包->打包->發送,客戶端接收到要拆包->組合數據。
發送方式 | 做用 |
---|---|
==0== | 默認 |
MSG_OOB | 傳輸一段數據,再外帶加一個額外的特殊數據 |
MSG_DONTROUTE | 數據不該受路由限制 |
if (SOCKET_ERROR == send(socketClient, "abcd", sizeof("abcd"), 0)) { //出錯了 int a = WSAGetLastError(); //根據實際狀況處理 }
char szSendBuffer[1024]; /*發送字符數組*/ send(newSocket, "repeat over", strlen(szSendBuffer)+1, 0);
做用:客戶端連接服務器端,將本機的一個指定的socket鏈接到一個指定地址的服務器socket上去
理解:connect將在本機和指定服務器間創建一個鏈接。但實際上,connect操做並不引起網絡設備傳送任何的數據到對端。它所 作的操做只是經過路由規則和路由表等一些信息,在struct socket結構中填入一些有關對端服務器的信息。這樣,之後向對端發送數據報時,就不須要每次進行路由查詢等操做以肯定對端地址信息和本地發送接口,應用程序也就不須要每次傳入對端地址信息
int WSAAPI connect ( SOCKET s, /*客戶端建立的連接服務器的socket*/ const sockaddr *name, /*服務器IP地址端口號結構體*/ int namelen /*sizeof(sockaddr)*/ );
if (SOCKET_ERROR == connect(socketServer, (struct sockaddr *)&serverMsg, sizeof(serverMsg))) { int a = WSAGetLastError(); closesocket(socketServer); WSACleanup(); }
struct sockaddr_in serverMsg; serverMsg.sin_family = AF_INET; /*地址類型*/ serverMsg.sin_port = htons(12345); /*服務器端口*/ serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); /*服務器IP*/ connect(socketServer, (struct sockaddr *)&serverMsg, sizeof(serverMsg)); if (SOCKET_ERROR == connect(socketServer, (struct sockaddr *)&serverMsg, sizeof(serverMsg))) { int a = WSAGetLastError(); closesocket(socketServer); WSACleanup(); }
因爲accept() ,recv()是阻塞的,作其中一件事,另一件事就作不了。
咱們能夠主動和系統要有請求的socket
答:由於服務器接收、發送數據都是經過綁定客戶端信息的socket進行的,不是經過服務器socket,服務器socket只是接受客戶端請求的連接,而且把客戶端的信息綁定到一個新的socket上,之後的通訊都是經過這個socket,因此服務器有響應就是有新的請求連接
答:客戶端所建立的socket只是本機和指定服務器間創建一個鏈接,socket結構中填入一些有關對端服務器的信息。這樣,之後向對端發送數據報時,就不須要每次進行路由查詢等操做以肯定對端地址信息和本地發送接口,能夠理解爲客戶端的所建立的socket其實就是和服務器數據交換的socket,與服務器端最開始建立的socket不一樣。