windows socket网络编程一:最简单的服务器和客户端搭建
文章目錄
- 簡(jiǎn)介
- 服務(wù)器
- 網(wǎng)絡(luò)版本
- 1、打開網(wǎng)絡(luò)庫(kù)
- 2、校驗(yàn)版本
- 3、創(chuàng)建socket
- 4、綁定地址和端口
- 5、監(jiān)聽(tīng)
- 6、接受鏈接
- 7、與客戶端收發(fā)消息
- 客戶端
- 1、打開網(wǎng)絡(luò)庫(kù)
- 2、校驗(yàn)版本
- 3、創(chuàng)建socket
- 4、連接服務(wù)器
- 5、與客戶端收發(fā)消息
- 類比
- 運(yùn)行結(jié)果
- 源碼鏈接
- 遇到的問(wèn)題
- 頭文件沖突
簡(jiǎn)介
socket又是什么?
將網(wǎng)絡(luò)底層復(fù)雜的協(xié)議體系,執(zhí)行流程,進(jìn)行了封裝后就是SOCKET了,也就是說(shuō),SOCKET是我們調(diào)用協(xié)議進(jìn)行通信的操作接口,將復(fù)雜的協(xié)議過(guò)程與我們編程人員分開,我們直接操作一個(gè)簡(jiǎn)單SOCKET就行了,對(duì)于底層的協(xié)議 過(guò)程細(xì)節(jié),我們可以完全不用知道。
在編譯器轉(zhuǎn)定義后就是一個(gè)unsigned int,目測(cè)當(dāng)作id使用。
最簡(jiǎn)單的客戶端、服務(wù)器通信流程大致如下:
收發(fā)信息可以進(jìn)行很多次。
服務(wù)器
網(wǎng)絡(luò)版本
我們使用新版本第二版
網(wǎng)絡(luò)版本一:
#include <winsock.h> #pragma comment(lib, "wsock32.lib")網(wǎng)絡(luò)版本二:
#include <WinSock2.h> #pragma comment(lib, "ws2_32.lib")注:不管是64編譯環(huán)境還是32編譯環(huán)境,都是用這個(gè)ws2_32.lib,并沒(méi)有ws2_64.lib。
1、打開網(wǎng)絡(luò)庫(kù)
int WSAAPI WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData );功能:打開網(wǎng)絡(luò)庫(kù)/啟動(dòng)網(wǎng)絡(luò)庫(kù)(函數(shù)名字分析W—windows、S—socket、A—Asynchronous 異步)
參數(shù):
- wVersionRequested — 調(diào)用者可以使用的Windows套接字規(guī)范的最高版本。高位字節(jié)指定次版本號(hào);低位字節(jié)指定主版本號(hào)。(支持版本:1.0、1.1、2.0、2.1、2.2)
- lpWSAData — 接收Windows套接字實(shí)現(xiàn)的詳細(xì)信息。struct WSAData {WORD wVersion; // 我們要使用的版本WORD wHighVersion; // 系統(tǒng)能提供給我們最高的版本unsigned short iMaxSockets; // 返回可用的socket的數(shù)量,二版本之后就沒(méi)用了unsigned short iMaxUdpDg; // UDP數(shù)據(jù)報(bào)信息的大小,2版本之后就沒(méi)用了char *lpVendorInfo; // 供應(yīng)商特定的信息,2版本之后就沒(méi)用了char szDescription[WSADESCRIPTION_LEN + 1]; // 當(dāng)前庫(kù)的描述信息char szSystemStatus[WSASYS_STATUS_LEN + 1];
}
程序調(diào)試截圖:
返回值:
- 如果成功,返回零。
- 如果失敗,返回錯(cuò)誤碼。
錯(cuò)誤碼宏展開直翻原因通俗解釋原因 WSASYSNOTREADY 10091 底層網(wǎng)絡(luò)子系統(tǒng)尚未準(zhǔn)備好進(jìn)行網(wǎng)絡(luò)通信 系統(tǒng)配置問(wèn)題,重啟下電腦,檢查ws2_32庫(kù)是否存在,或者是否在環(huán)境配置目錄下 WSAVERNOTSUPPORTED 10092 此特定Windows套接字實(shí)現(xiàn)不提供所請(qǐng)求的Windows套接字支持的版本。 要使用的版本不支持 WSAEPROCLIM 10067 已達(dá)到對(duì)Windows套接字實(shí)現(xiàn)支持的任務(wù)數(shù)量的限制 Windows Sockets實(shí)現(xiàn)可能限制同時(shí)使用它的應(yīng)用程序的數(shù)量 WSAEINPROGRESS 10036 正在阻止Windows Sockets 1.1操作。 當(dāng)前函數(shù)運(yùn)行期間,由于某些原因造成阻塞,會(huì)返回在這個(gè)錯(cuò)誤碼,其他操作均禁止 WSAEFAULT 10014 lpWSAData參數(shù)不是有效指針 參數(shù)寫錯(cuò)了
代碼(錯(cuò)誤處理就簡(jiǎn)單點(diǎn)了):
// 開啟網(wǎng)絡(luò)庫(kù) WORD wVersionRequird = MAKEWORD(2, 2); // MAKEWORD(主版本,副版本) WSADATA wdScokMsg; switch (WSAStartup(wVersionRequird, &wdScokMsg)) { case WSASYSNOTREADY:printf("重啟電腦試試,或者檢查網(wǎng)絡(luò)庫(kù)\n");return -1;case WSAVERNOTSUPPORTED:printf("請(qǐng)更新網(wǎng)絡(luò)庫(kù)\n");return -1;case WSAEPROCLIM:printf("請(qǐng)嘗試關(guān)掉不必要的軟件,以為當(dāng)前網(wǎng)絡(luò)運(yùn)行提供充足的資源\n");return -1;case WSAEINPROGRESS:printf("請(qǐng)重新啟動(dòng)\n");return -1; }雖然我們有正確的代碼,但是我們也可以測(cè)試一下不正確的版本號(hào)會(huì)發(fā)生什么:
| 主版本號(hào)為0 | 返回錯(cuò)誤碼WSAVERNOTSUPPORTED,不支持該版本 |
| 有對(duì)應(yīng)的主版本,沒(méi)有對(duì)應(yīng)的副版本 | 沒(méi)問(wèn)題,得到該主版本的最大副版本 1.1 2.2并使用 |
| 超過(guò)最大主版本 | 沒(méi)問(wèn)題,使用系統(tǒng)能提供的最大的版本 2.2 |
2、校驗(yàn)版本
這一步也許不是必須的,不過(guò)網(wǎng)絡(luò)版本還是比較重要的,需要確定。
// 校驗(yàn)版本 if (2 != HIBYTE(wdScokMsg.wVersion) || 2 != LOBYTE(wdScokMsg.wVersion)) {printf("版本不存在\n");WSACleanup();return -1; }3、創(chuàng)建socket
SOCKET WSAAPI socket(int af,int type,int protocol );功能:創(chuàng)建一個(gè)SOCKET
參數(shù):
- af — 地址族
常用地址族宏展開含義 AF_INET 2 IPv4 AF_INET6 23 IPv6 AF_IRDA 26 紅外 AF_BTH 32 藍(lán)牙 - type — 套接字類型
套接字類型宏展開含義 SOCK_STREAM 1 一種套接字類型,提供帶有OOB數(shù)據(jù)傳輸機(jī)制的順序,可靠,雙向,基于連接的字節(jié)流。 此套接字類型使用傳輸控制協(xié)議(TCP)作為Internet地址系列(AF_INET或AF_INET6)。 SOCK_DGRAM 2 一種支持?jǐn)?shù)據(jù)報(bào)的套接字類型,它是固定(通常很小)最大長(zhǎng)度的無(wú)連接,不可靠的緩沖區(qū)。 此套接字類型使用用戶數(shù)據(jù)報(bào)協(xié)議(UDP)作為Internet地址系列(AF_INET或AF_INET6)。 SOCK_RAW 3 一種套接字類型,提供允許應(yīng)用程序操作下一個(gè)上層協(xié)議頭的原始套接字。 要操作IPv4標(biāo)頭,必須在套接字上設(shè)置IP_HDRINCL套接字選項(xiàng)。 要操作IPv6標(biāo)頭,必須在套接字上設(shè)置IPV6_HDRINCL套接字選項(xiàng)。 SOCK_RDM 4 一種套接字類型,提供可靠的消息數(shù)據(jù)報(bào)。 這種類型的一個(gè)示例是Windows中的實(shí)用通用多播(PGM)多播協(xié)議實(shí)現(xiàn),通常稱為可靠多播節(jié)目。僅在安裝了可靠多播協(xié)議時(shí)才支持此類型值。 SOCK_SEQPACKET 5 一種套接字類型,提供基于數(shù)據(jù)報(bào)的偽流數(shù)據(jù)包。 - protocol — 協(xié)議類型
協(xié)議類型含義 IPPROTO_TCP 傳輸控制協(xié)議(TCP)。 當(dāng)af參數(shù)為AF_INET或AF_INET6且類型參數(shù)為SOCK_STREAM時(shí),這是一個(gè)可能的值。 IPPROTO_UDP 用戶數(shù)據(jù)報(bào)協(xié)議(UDP)。 當(dāng)af參數(shù)為AF_INET或AF_INET6且類型參數(shù)為SOCK_DGRAM時(shí),這是一個(gè)可能的值。 IPPROTO_ICMP Internet控制消息協(xié)議(ICMP)。 當(dāng)af參數(shù)為AF_UNSPEC,AF_INET或AF_INET6且類型參數(shù)為SOCK_RAW或未指定時(shí),這是一個(gè)可能的值。 IPPROTO_IGMP Internet組管理協(xié)議(IGMP)。 當(dāng)af參數(shù)為AF_UNSPEC,AF_INET或AF_INET6且類型參數(shù)為SOCK_RAW或未指定時(shí),這是一個(gè)可能的值。 IPPROTO_RM 用于可靠多播的PGM協(xié)議。 當(dāng)af參數(shù)為AF_INET且類型參數(shù)為SOCK_RDM時(shí),這是一個(gè)可能的值。 在針對(duì)Windows Vista及更高版本發(fā)布的Windows SDK上,此協(xié)議也稱為IPPROTO_PGM。僅在安裝了可靠多播協(xié)議時(shí)才支持此協(xié)議值。 0 調(diào)用者不希望指定協(xié)議,服務(wù)提供商將選擇要使用的協(xié)議。
通過(guò)上面對(duì)參數(shù)的描述來(lái)看我們?nèi)齻€(gè)參數(shù)需要配合使用,比如說(shuō)我們使用tcp通信需要基于字節(jié)流,而不是數(shù)據(jù)包,不能亂用。
返回值:
- 如果成功,返回socket。
- 如果失敗,返回INVALID_SOCKET。
代碼:
// 創(chuàng)建服務(wù)器socket(監(jiān)聽(tīng)套接字) SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == socketServer) {printf("創(chuàng)建socket失敗 error:%d\n", WSAGetLastError());WSACleanup();return -1; }我們測(cè)試一下基于數(shù)據(jù)包的tcp(SOCKET socketServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_TCP);)會(huì)有什么問(wèn)題:
4、綁定地址和端口
int WSAAPI bind(SOCKET s,const sockaddr *name,int namelen );功能:給socket綁定具體地址(定位到電腦)與端口號(hào)(定位到具體應(yīng)用)
參數(shù):
- s — socket
- name — 地址和端口typedef struct sockaddr { #if (_WIN32_WINNT < 0x0600)u_short sa_family; #elseADDRESS_FAMILY sa_family; // Address family. #endif //(_WIN32_WINNT < 0x0600)CHAR sa_data[14]; // Up to 14 bytes of direct address. } SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;// 和sockaddr相同 便于我們賦值 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; // 地址CHAR sin_zero[8]; } SOCKADDR_IN, *PSOCKADDR_IN;
- namelen — 參數(shù)二的大小
其中端口號(hào)理論上取值范圍0~65535。實(shí)際上介于0~1023,為系統(tǒng)保留占用端口號(hào),(如:21端口分配給FTP(文件傳輸協(xié)議)服務(wù)、25端口分配給SMTP(簡(jiǎn)單郵件傳輸協(xié)議)服務(wù)、80端口分配給HTTP服務(wù))所以我們使用其他的端口號(hào)。
因?yàn)橥粋€(gè)端口號(hào)不能同時(shí)被兩個(gè)程序使用,萬(wàn)一我們想要的端口號(hào)被占用了,想知道是什么程序占用的,可以使用cmd查看:
- 打開運(yùn)行cmd輸入netstat -ano,查看被使用的所有端口
- netstat -aon | findstr “12345”,檢查我們要使用的端口號(hào)是否被使用了
返回值:
- 如果成功,返回0。
- 如果失敗,返回SOCKET_ERROR。
代碼:
// 綁定地址 SOCKADDR_IN sockAddress; sockAddress.sin_family = AF_INET; sockAddress.sin_addr.s_addr = inet_addr("127.0.0.1"); // INADDR_ANY --- 任何地址都可以 sockAddress.sin_port = htons(6666); if (SOCKET_ERROR == bind(socketServer, (struct sockaddr*)&sockAddress, sizeof(sockAddress))) {printf("bind 失敗 error:%d\n", WSAGetLastError());closesocket(socketServer);WSACleanup();return -1; }我們測(cè)試一下已經(jīng)被占用的端口:
5、監(jiān)聽(tīng)
int WSAAPI listen(SOCKET s,int backlog );功能:將套接字置于正在偵聽(tīng)傳入連接的狀態(tài)
參數(shù):
- s — socket
- backlog — 掛起的連接隊(duì)列的最大長(zhǎng)度。如果設(shè)置為SOMAXCONN,則負(fù)責(zé)套接字s的基礎(chǔ)服務(wù)提供商將積壓設(shè)置為最大合理值。
返回值:
- 如果成功,返回0。
- 如果失敗,返回SOCKET_ERROR。
代碼:
// 開始監(jiān)聽(tīng) if (SOCKET_ERROR == listen(socketServer, SOMAXCONN)) {printf("listen 失敗 error:%d\n", WSAGetLastError());closesocket(socketServer);WSACleanup();return -1; }我們測(cè)試一下未bind的情況:
6、接受鏈接
SOCKET WSAAPI accept(SOCKET s,sockaddr *addr,int *addrlen );作用:允許在套接字上進(jìn)行傳入連接嘗試。listen監(jiān)聽(tīng)客戶端來(lái)的鏈接,accept將客戶端的信息綁定到一個(gè)socket上,也就是給客戶端創(chuàng)建一個(gè)socket,通過(guò)返回值返回給我們客戶端的socket。(一次只能創(chuàng)建一個(gè),有幾個(gè)客戶端鏈接,就要調(diào)用幾次。)
參數(shù):
- s — socket
- addr — 客戶端地址和端口
- addrlen — 參數(shù)二的大小
注:如果參數(shù)二和參數(shù)三都填NULL,那就不是直接得不到客戶端信息。可以通過(guò)函數(shù)得到客戶端信息getpeername(newSocket, (struct sockaddr*)&sockClient, &nLen);。對(duì)應(yīng)的得到本地服務(wù)器信息getsockname(sSocket, (sockaddr*)&addr, &nLen);。
返回值:
- 如果成功,返回客戶端socket。
- 如果失敗,返回INVALID_SOCKET。
代碼:
// 接受鏈接 SOCKADDR_IN sockClient; int nLen = sizeof(sockClient); SOCKET socketClient = accept(socketServer, (struct sockaddr*)&sockClient, &nLen); if (INVALID_SOCKET == socketClient) {printf("accept 失敗 error:%d\n", WSAGetLastError());closesocket(socketServer);WSACleanup();return -1; } printf("客戶端連接成功\n");7、與客戶端收發(fā)消息
int WSAAPI recv(SOCKET s,char *buf,int len,int flags );功能:得到指定目標(biāo)(參數(shù)1)發(fā)來(lái)的消息
參數(shù):
- s — socket
- buf — 消息數(shù)據(jù)的存儲(chǔ)空間(網(wǎng)絡(luò)傳輸?shù)米畲髥卧狹TU為1500字節(jié),也就是客戶端發(fā)過(guò)來(lái)得數(shù)據(jù),一次最大就是1500字節(jié),當(dāng)然可以減去報(bào)文的40字節(jié),這是協(xié)議規(guī)定,這個(gè)數(shù)值也是根據(jù)很多情況,總結(jié)出來(lái)得最優(yōu)值。)
- len — buf參數(shù)指向的緩沖區(qū)的長(zhǎng)度(以字節(jié)為單位)
- flags — 數(shù)據(jù)的讀取方式
值含義 MSG_PEEK 窺視傳入的數(shù)據(jù)。 數(shù)據(jù)將復(fù)制到緩沖區(qū)中,但不會(huì)從輸入隊(duì)列中刪除。 MSG_OOB 處理帶外(OOB)數(shù)據(jù)。就是傳輸一段數(shù)據(jù),在外帶一個(gè)額外的特殊數(shù)據(jù)。不建議被使用,實(shí)在不行send兩次。 MSG_WAITALL 當(dāng)滿足事件之一(調(diào)用方提供的緩沖區(qū)已滿、連接已關(guān)閉、該請(qǐng)求已被取消或發(fā)生錯(cuò)誤)就讀取數(shù)據(jù),并從輸入隊(duì)列中刪除。 0 有數(shù)據(jù)就讀取數(shù)據(jù),并從輸入隊(duì)列中刪除。
返回值:
- 如果成功,返回接收到的字節(jié)數(shù)。
- 如果已正常關(guān)閉連接,返回值為零。
- 如果失敗,返回SOCKET_ERROR。
功能:向目標(biāo)發(fā)送數(shù)據(jù)
參數(shù):
- s — socket
- buf — 要發(fā)送的數(shù)據(jù)
- len — buf參數(shù)指向的緩沖區(qū)的長(zhǎng)度(以字節(jié)為單位)
- flags — 數(shù)據(jù)的發(fā)送方式
值含義 MSG_OOB 與上面對(duì)應(yīng)處理帶外(OOB)數(shù)據(jù)。 MSG_DONTROUTE 指定數(shù)據(jù)不應(yīng)受路由限制。 Windows套接字服務(wù)提供程序可以選擇忽略此標(biāo)志。 0 正常發(fā)送
返回值:
- 如果成功,返回已發(fā)送的字節(jié)數(shù)。
- 如果失敗,返回SOCKET_ERROR。
代碼:
// 與客戶端收發(fā)消息 while (1) {// 緩沖區(qū)char szRecvBuffer[1500];char szSendBuffer[1500];int result = recv(socketClient, szRecvBuffer, sizeof(szRecvBuffer), 0);if (0 == result) // 客戶端正常關(guān)閉{printf("客戶端正常下線\n");break; // 因?yàn)檫@個(gè)例子只接受一次客戶端請(qǐng)求所以服務(wù)器關(guān)閉}else if (SOCKET_ERROR == result) // recv出錯(cuò){printf("recv 失敗 error:%d\n", WSAGetLastError());break;}else // 給客戶端發(fā)消息{// 接收到客戶端消息 printf("Client Data : %s \n", szRecvBuffer);// 給客戶回信scanf_s("%s", szSendBuffer, 1500);getchar();if (SOCKET_ERROR == send(socketClient, szSendBuffer, strlen(szSendBuffer) + 1, 0)){printf("send 失敗 error:%d\n", WSAGetLastError());break;}} }客戶端
1、打開網(wǎng)絡(luò)庫(kù)
與服務(wù)器類似
2、校驗(yàn)版本
與服務(wù)器類似
3、創(chuàng)建socket
與服務(wù)器類似
4、連接服務(wù)器
int WSAAPI connect(SOCKET s,const sockaddr *name,int namelen );功能:連接服務(wù)器
參數(shù):
- s — socket
- name — 服務(wù)器ip地址端口號(hào)結(jié)構(gòu)體
- namelen — 參數(shù)二的大小
返回值:
- 如果成功,返回0。
- 如果失敗,返回SOCKET_ERROR。
代碼:
// 連接服務(wù)器 SOCKADDR_IN sockAddress; sockAddress.sin_family = AF_INET; sockAddress.sin_addr.s_addr = inet_addr("127.0.0.1"); sockAddress.sin_port = htons(6666); if (SOCKET_ERROR == connect(socketClient, (struct sockaddr*)&sockAddress, sizeof(sockAddress))) {printf("connect 失敗 error:%d\n", WSAGetLastError());closesocket(socketClient);WSACleanup();return -1; }5、與客戶端收發(fā)消息
與服務(wù)器類似
類比
我們可以把操作socket類比于操作file:
| 建立socket socket | 聲明File* |
| 連接服務(wù)器 connect | 打開文件fopen |
| 向服務(wù)器發(fā)送數(shù)據(jù) send | 寫文件fwrite |
| 接收服務(wù)器數(shù)據(jù) recv | 讀文件fread |
| 關(guān)閉socket closesocket | 關(guān)閉文件fclose |
運(yùn)行結(jié)果
源碼鏈接
百度云鏈接:https://pan.baidu.com/s/1xBOiSADlAG2gO1TC6BBO_A
提取碼:sxbd
遇到的問(wèn)題
頭文件沖突
代碼:
#include <Windows.h> #include <WinSock2.h>int main() {return 0; }報(bào)錯(cuò):
解決方法:
總結(jié)
以上是生活随笔為你收集整理的windows socket网络编程一:最简单的服务器和客户端搭建的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: from app import db I
- 下一篇: 安装体验黑苹果系统