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