Windows下VScode實現簡單回聲服務的方法
1. 相關知識
1.1 什麼是回聲服務
回聲服務端可以將客戶端傳來的信息,再原封不動地發送給客戶端,因而得名 epoch 服務。服務端 server 和 客戶端 client 基於 TCP 進行通信。
1.2 服務端、客戶端如何交互
下圖給出瞭基於 TCP 的服務器端和客戶端的交互過程。
首先服務端創建 socket 套接字,之後調用 bind 函數分配服務端 socket 地址,調用 listen 函數使服務端進入監聽狀態,同時維護一個半連接隊列。服務端之後會調用 accept 函數,進入阻塞狀態。accept 函數會從全連接的隊列中取出一個連接進行處理。TCP 連接建立完成之後,服務端和客戶端即可通過 send 和 recv 發送和接收數據。
註意:服務端調用 listen 函數進入等待連接狀態後,客戶端才能調用 connect 函數發起連接請求。
服務端和客戶端交互就是一種通信過程,它們基於 TCP 實現 socket 通信。TCP 協議中有三次握手、四次揮手的協議內容,如下圖所示。
服務端和客戶端通過三次握手建立連接,四次揮手斷開連接。
具體到socket編程實現,則是通過 listen 和 connect 函數實現 TCP 連接的建立,通過 close 函數關閉 socket 套接字,實現TCP連接的斷開。
2. socket 編程
下面分別介紹客戶端和服務端的常用函數和具體實現過程。
2.1 服務端
服務端的實現過程如下圖所示。
下面給出實現基於TCP的服務端的常用函數。
1.首先需要對 Winsock 套接字庫進行初始化,調用 WSAStartup 函數。
下面給出 WSAStartup 函數調用的基本格式,一般隻需調用即可,無需瞭解參數含義。
#include <winsock2.h> int main(int argc, char* argv[]) { WSADATA wsaData; if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0) ErrorHandling("WSAStartup() error!"); return 0; }
成功時返回 0 ,失敗返回非零的錯誤代碼值。
2.創建 socket 套接字
SOCKET socket(int af, int type, int protocol);
成功時返回套接字句柄,失敗返回 INVALID_SOCKET。
3.調用 bind 函數,為套接字分配 IP 地址和端口號
int bind(SOCKET s, const struct sockaddr * name, int namelen);
成功時返回 0,失敗返回 SOCKET_ERROR。
4.調用 listen 函數,監聽客戶端連接
int listen(SOCKET s, int backlog);
成功時返回 0 ,失敗返回 SOCKET_ERROR 。
5.調用 accept 函數,允許客戶端連接
SOCKET accept(SOCKET s, struct sockaddr * addr, int * addrlen);
成功時返回套接字句柄,失敗返回 INVALID_SOCKET 。
6.調用 send 函數, 給連接的客戶端發送數據
int send(SOCKET s, const char * buf, int len, int flags):
成功時返回傳輸字節數,失敗返回 SOCKET_ERROR 。
7.調用 recv 函數,接收連接的客戶端發來的數據
int recv(SOCKET s, const char * buf, int len, int flags);
成功時返回接收字節數,失敗返回 SOCKET_ERROR 。
8.調用 close 函數,關閉套接字。
int closesocket(SOCKET s);
成功時返回 0 ,失敗時返回 SOCKET_ERROR 。
9.註銷 Winsock 相關庫
int WSACleanup(void);
成功時返回 0 ,失敗時返回 SOCKET_ERROR 。
2.2 客戶端
客戶端的實現過程如下圖所示。
下面給出實現基於TCP的客戶端的常用函數。
1.創建 socket 套接字
SOCKET socket(int af, int type, int protocol);
成功時返回套接字句柄,失敗返回 INVALID_SOCKET。
2.調用connect函數,發起連接請求
int connect(SOCKET s, const struct sockaddr * name, int namelen);
成功時返回 0,失敗返回 SOCKET_ERROR。
3.調用 send 函數, 給連接的服務端發送數據
int send(SOCKET s, const char * buf, int len, int flags):
成功時返回傳輸字節數,失敗返回 SOCKET_ERROR 。
4.調用 recv 函數,接收連接的服務端發來的數據
int recv(SOCKET s, const char * buf, int len, int flags);
成功時返回接收字節數,失敗返回 SOCKET_ERROR 。
5.調用 close 函數,斷開連接。
int closesocket(SOCKET s);
成功時返回 0 ,失敗時返回 SOCKET_ERROR 。
3. demo展示
3.1 服務端源代碼
回聲服務端的C++代碼
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib") #define BUF_SIZE 1024 void ErrorHandling(char *message); int main(int argc, char *argv[]) { WSADATA wsaData; SOCKET hServSock, hClntSock; char message[BUF_SIZE]; int strLen, i; SOCKADDR_IN servAdr, clntAdr; int clntAdrSize; if (argc != 2) { printf("Usage : %s <port>\n", argv[0]); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error!"); hServSock = socket(PF_INET, SOCK_STREAM, 0); if (hServSock == INVALID_SOCKET) ErrorHandling("socket() error"); memset(&servAdr, 0, sizeof(servAdr)); servAdr.sin_family = AF_INET; /*servAdr.sin_addr.s_addr = htonl(INADDR_ANY);*/ servAdr.sin_addr.s_addr = inet_addr("127.0.0.1"); servAdr.sin_port = htons(atoi(argv[1])); if (bind(hServSock, (SOCKADDR *)&servAdr, sizeof(servAdr)) == SOCKET_ERROR) ErrorHandling("bind() error"); if (listen(hServSock, 5) == SOCKET_ERROR) ErrorHandling("listen() error"); clntAdrSize = sizeof(clntAdr); for (i = 0; i < 5; i++) { hClntSock = accept(hServSock, (SOCKADDR *)&clntAdr, &clntAdrSize); if (hClntSock == -1) ErrorHandling("accept() error"); else printf("Connected client %d \n", i + 1); while ((strLen = recv(hClntSock, message, BUF_SIZE, 0)) != 0) send(hClntSock, message, strLen, 0); closesocket(hClntSock); } closesocket(hServSock); printf("game over"); WSACleanup(); return 0; } void ErrorHandling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
註意:運行服務端代碼時,須加入命令行參數(端口號)。如代碼所示, IP 地址已經綁定 127.0.0.1。配置 tasks.json 如下所示。
{ "version": "2.0.0", "tasks": [ { "type": "shell", "label": "C/C++: g++.exe build active file", "command": "E:\\mingw64\\bin\\g++.exe", "args": [ "-g", "${file}", "-lws2_32", "-o", "${fileDirname}\\${fileBasenameNoExtension}.exe" ], "options": { "cwd": "${workspaceFolder}" }, "problemMatcher": ["$gcc"], "group": { "kind": "build", "isDefault": true } } ] }
配置信息 launch.json 如下 。
{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "(gdb) 啟動", "type": "cppdbg", "request": "launch", "program": "${fileDirname}/${fileBasenameNoExtension}.exe", "args": ["9190"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "miDebuggerPath": "E:\\mingw64\\bin\\gdb.exe", "setupCommands": [ { "description": "為 gdb 啟用整齊打印", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] }
3.2 客戶端源代碼
回聲客戶端的C++代碼
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #define BUF_SIZE 1024 void ErrorHandling(char *message); int main(int argc, char *argv[]) { WSADATA wsaData; SOCKET hSocket; char message[BUF_SIZE]; int strLen; SOCKADDR_IN servAdr; if (argc != 3) { printf("Usage : %s <IP> <port>\n", argv[0]); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error!"); hSocket = socket(PF_INET, SOCK_STREAM, 0); if (hSocket == INVALID_SOCKET) ErrorHandling("socket() error"); printf("%s\n", argv[0]); printf("%s\n", argv[1]); printf("%s\n", argv[2]); memset(&servAdr, 0, sizeof(servAdr)); servAdr.sin_family = AF_INET; servAdr.sin_addr.s_addr = inet_addr(argv[1]); servAdr.sin_port = htons(atoi(argv[2])); if (connect(hSocket, (SOCKADDR *)&servAdr, sizeof(servAdr)) == SOCKET_ERROR) ErrorHandling("connect() error!"); else puts("Connected..........."); while (1) { fputs("Input message(Q to quit): ", stdout); fgets(message, BUF_SIZE, stdin); if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) break; send(hSocket, message, strlen(message), 0); strLen = recv(hSocket, message, BUF_SIZE - 1, 0); printf("Message from server: %s", message); } closesocket(hSocket); WSACleanup(); return 0; } void ErrorHandling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
同樣,客戶端也需要加入命令行參數 127.0.0.1 9190
運行。可以通過修改配置文件生成客戶端。
也可以通過cmd或者終端生成客戶端。cmd 方式如下:
首先通過 g++ 編譯器對 client.cpp 文件進行編譯生成 .exe 文件。
之後在終端中,輸入 client.exe 127.0.0.1 9190
即可創建客戶端。
3.3 運行結果
服務端可以服務 5 個客戶端,即 accept 隊列長度為 5。
客戶端的運行結果如下,前5個客戶端均與服務端連接成功,可以收到“回聲”。第6次連接時,由於服務端斷開連接,所以產生連接錯誤。
服務端的運行結果如下圖所示。服務端可以連接5個客戶端,之後服務端將斷開連接。並顯示 “game over”。
參考鏈接
深入理解TCP協議與UDP協議的原理及區別
VScode官方文檔
到此這篇關於Windows下VScode實現簡單回聲服務的文章就介紹到這瞭,更多相關VScode回聲服務內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!