Windows下VScode實現(xiàn)簡單回聲服務(wù)的方法
1. 相關(guān)知識
1.1 什么是回聲服務(wù)
回聲服務(wù)端可以將客戶端傳來的信息,再原封不動地發(fā)送給客戶端,因而得名 epoch 服務(wù)。服務(wù)端 server 和 客戶端 client 基于 TCP 進(jìn)行通信。
1.2 服務(wù)端、客戶端如何交互
下圖給出了基于 TCP 的服務(wù)器端和客戶端的交互過程。
首先服務(wù)端創(chuàng)建 socket 套接字,之后調(diào)用 bind 函數(shù)分配服務(wù)端 socket 地址,調(diào)用 listen 函數(shù)使服務(wù)端進(jìn)入監(jiān)聽狀態(tài),同時維護(hù)一個半連接隊列。服務(wù)端之后會調(diào)用 accept 函數(shù),進(jìn)入阻塞狀態(tài)。accept 函數(shù)會從全連接的隊列中取出一個連接進(jìn)行處理。TCP 連接建立完成之后,服務(wù)端和客戶端即可通過 send 和 recv 發(fā)送和接收數(shù)據(jù)。
注意:服務(wù)端調(diào)用 listen 函數(shù)進(jìn)入等待連接狀態(tài)后,客戶端才能調(diào)用 connect 函數(shù)發(fā)起連接請求。

服務(wù)端和客戶端交互就是一種通信過程,它們基于 TCP 實現(xiàn) socket 通信。TCP 協(xié)議中有三次握手、四次揮手的協(xié)議內(nèi)容,如下圖所示。

服務(wù)端和客戶端通過三次握手建立連接,四次揮手?jǐn)嚅_連接。
具體到socket編程實現(xiàn),則是通過 listen 和 connect 函數(shù)實現(xiàn) TCP 連接的建立,通過 close 函數(shù)關(guān)閉 socket 套接字,實現(xiàn)TCP連接的斷開。
2. socket 編程
下面分別介紹客戶端和服務(wù)端的常用函數(shù)和具體實現(xiàn)過程。
2.1 服務(wù)端
服務(wù)端的實現(xiàn)過程如下圖所示。

下面給出實現(xiàn)基于TCP的服務(wù)端的常用函數(shù)。
1.首先需要對 Winsock 套接字庫進(jìn)行初始化,調(diào)用 WSAStartup 函數(shù)。
下面給出 WSAStartup 函數(shù)調(diào)用的基本格式,一般只需調(diào)用即可,無需了解參數(shù)含義。
#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.創(chuàng)建 socket 套接字
SOCKET socket(int af, int type, int protocol);
成功時返回套接字句柄,失敗返回 INVALID_SOCKET。
3.調(diào)用 bind 函數(shù),為套接字分配 IP 地址和端口號
int bind(SOCKET s, const struct sockaddr * name, int namelen);
成功時返回 0,失敗返回 SOCKET_ERROR。
4.調(diào)用 listen 函數(shù),監(jiān)聽客戶端連接
int listen(SOCKET s, int backlog);
成功時返回 0 ,失敗返回 SOCKET_ERROR 。
5.調(diào)用 accept 函數(shù),允許客戶端連接
SOCKET accept(SOCKET s, struct sockaddr * addr, int * addrlen);
成功時返回套接字句柄,失敗返回 INVALID_SOCKET 。
6.調(diào)用 send 函數(shù), 給連接的客戶端發(fā)送數(shù)據(jù)
int send(SOCKET s, const char * buf, int len, int flags):
成功時返回傳輸字節(jié)數(shù),失敗返回 SOCKET_ERROR 。
7.調(diào)用 recv 函數(shù),接收連接的客戶端發(fā)來的數(shù)據(jù)
int recv(SOCKET s, const char * buf, int len, int flags);
成功時返回接收字節(jié)數(shù),失敗返回 SOCKET_ERROR 。
8.調(diào)用 close 函數(shù),關(guān)閉套接字。
int closesocket(SOCKET s);
成功時返回 0 ,失敗時返回 SOCKET_ERROR 。
9.注銷 Winsock 相關(guān)庫
int WSACleanup(void);
成功時返回 0 ,失敗時返回 SOCKET_ERROR 。
2.2 客戶端
客戶端的實現(xiàn)過程如下圖所示。

下面給出實現(xiàn)基于TCP的客戶端的常用函數(shù)。
1.創(chuàng)建 socket 套接字
SOCKET socket(int af, int type, int protocol);
成功時返回套接字句柄,失敗返回 INVALID_SOCKET。
2.調(diào)用connect函數(shù),發(fā)起連接請求
int connect(SOCKET s, const struct sockaddr * name, int namelen);
成功時返回 0,失敗返回 SOCKET_ERROR。
3.調(diào)用 send 函數(shù), 給連接的服務(wù)端發(fā)送數(shù)據(jù)
int send(SOCKET s, const char * buf, int len, int flags):
成功時返回傳輸字節(jié)數(shù),失敗返回 SOCKET_ERROR 。
4.調(diào)用 recv 函數(shù),接收連接的服務(wù)端發(fā)來的數(shù)據(jù)
int recv(SOCKET s, const char * buf, int len, int flags);
成功時返回接收字節(jié)數(shù),失敗返回 SOCKET_ERROR 。
5.調(diào)用 close 函數(shù),斷開連接。
int closesocket(SOCKET s);
成功時返回 0 ,失敗時返回 SOCKET_ERROR 。
3. demo展示
3.1 服務(wù)端源代碼
回聲服務(wù)端的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);
}
注意:運行服務(wù)端代碼時,須加入命令行參數(shù)(端口號)。如代碼所示, IP 地址已經(jīng)綁定 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);
}
同樣,客戶端也需要加入命令行參數(shù) 127.0.0.1 9190運行??梢酝ㄟ^修改配置文件生成客戶端。
也可以通過cmd或者終端生成客戶端。cmd 方式如下:
首先通過 g++ 編譯器對 client.cpp 文件進(jìn)行編譯生成 .exe 文件。
之后在終端中,輸入 client.exe 127.0.0.1 9190 即可創(chuàng)建客戶端。
3.3 運行結(jié)果
服務(wù)端可以服務(wù) 5 個客戶端,即 accept 隊列長度為 5。
客戶端的運行結(jié)果如下,前5個客戶端均與服務(wù)端連接成功,可以收到“回聲”。第6次連接時,由于服務(wù)端斷開連接,所以產(chǎn)生連接錯誤。


服務(wù)端的運行結(jié)果如下圖所示。服務(wù)端可以連接5個客戶端,之后服務(wù)端將斷開連接。并顯示 “game over”。

參考鏈接
深入理解TCP協(xié)議與UDP協(xié)議的原理及區(qū)別
到此這篇關(guān)于Windows下VScode實現(xiàn)簡單回聲服務(wù)的文章就介紹到這了,更多相關(guān)VScode回聲服務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Windows 環(huán)境下使用 Qt 連接 MySQL
這篇文章主要介紹了Windows 環(huán)境下使用 Qt 連接 MySQL的相關(guān)資料,需要的朋友可以參考下2017-07-07
探討:將兩個鏈表非降序合并為一個鏈表并依然有序的實現(xiàn)方法
本篇文章是對將兩個鏈表非降序合并為一個鏈表并依然有序的實現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
C++中關(guān)于[]靜態(tài)數(shù)組和new分配的動態(tài)數(shù)組的區(qū)別分析
這篇文章主要介紹了C++中關(guān)于[]靜態(tài)數(shù)組和new分配的動態(tài)數(shù)組的區(qū)別分析,很重要的概念,需要的朋友可以參考下2014-08-08

