前言
socket編程分為TCP和UDP兩個模塊,其中TCP是可靠的、安全的,常用于發送檔案等,而UDP是不可靠的、不安全的,常用作視頻通話等,
如下圖:

頭檔案與庫:
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
準備作業:
創建工程后,首先右鍵工程,選擇屬性

然后選擇 C/C++ - 前處理器 - 前處理器定義

將字串 _WINSOCK_DEPRECATED_NO_WARNINGS 添加到里面去,點擊應用即可!
TCP
連接程序圖:

創建tcp服務器和客戶端都是按照上圖的步驟來操作的!
1). 服務器
-
初始化套接字庫
對應圖中socket()WORD wVersion; WSADATA wsaData; int err; // 設定版本,可以理解為1.1 wVersion = MAKEWORD(1, 1); // 例:MAKEWORD(a, b) --> b | a << 8 將a左移8位變成高位與b合并起來 // 啟動 err = WSAStartup(wVersion, &wsaData); -
創建tcp套接字
對應圖中socket()// AF_INET:ipv4 AF_INET6:ipv6 SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0); -
系結到本機
對應圖中bind()// 準備系結資訊 SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 設定系結網卡 addrSrv.sin_family = AF_INET; // 設定系結網路模式 addrSrv.sin_port = htons(6000); // 設定系結埠 // hton: host to network x86:小端 網路傳輸:htons大端 // 系結到本機 int retVal = bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR)); -
監聽
對應圖中listen()// 同時能接收10個鏈接,主要看引數二的設定個數 listen(sockSrv, 10); -
接收連接請求,回傳針對客戶端的套接字
對應圖中accept()SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrCli, &len); -
發送資料
對應圖中write()sprintf_s(sendBuf, "hello client!\n"); int iSend = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0); -
接收資料
對應圖中read()recv(sockConn, recvBuf, 100, 0); -
關閉套接字
對應圖中close()closesocket(sockConn); -
清理套接字庫
WSACleanup();
具體實作代碼:
#include <iostream>
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
int main(void) {
// 1.初始化套接字庫
WORD wVersion;
WSADATA wsaData;
int err;
// 設定版本,可以理解為1.1
wVersion = MAKEWORD(1, 1); // 例:MAKEWORD(a, b) --> b | a << 8 將a左移8位變成高位與b合并起來
// 啟動
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
return err;
}
// 檢查:網路低位不等于1 || 網路高位不等于1
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
// 清理套接字庫
WSACleanup();
return -1;
}
// 2.創建tcp套接字 // AF_INET:ipv4 AF_INET6:ipv6
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
// 準備系結資訊
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 設定系結網卡
addrSrv.sin_family = AF_INET; // 設定系結網路模式
addrSrv.sin_port = htons(6000); // 設定系結埠
// hton: host to network x86:小端 網路傳輸:htons大端
// 3.系結到本機
int retVal = bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
if (retVal == SOCKET_ERROR) {
printf("Failed bind:%d\n", WSAGetLastError());
return -1;
}
// 4.監聽,同時能接收10個鏈接
if (listen(sockSrv, 10) == SOCKET_ERROR) {
printf("Listen failed:%d", WSAGetLastError());
return -1;
}
std::cout << "Server start at port: 6000" << std::endl;
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
char recvBuf[100];
char sendBuf[100];
while (1) {
// 5.接收連接請求,回傳針對客戶端的套接字
SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrCli, &len);
if (sockConn == SOCKET_ERROR) {
//printf("Accept failed:%d", WSAGetLastError());
std::cout << "Accept failed: " << WSAGetLastError() << std::endl;
break;
}
//printf("Accept client IP:[%s]\n", inet_ntoa(addrCli.sin_addr));
std::cout << "Accept client IP: " << inet_ntoa(addrCli.sin_addr) << std::endl;
// 6.發送資料
sprintf_s(sendBuf, "hello client!\n");
int iSend = send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
if (iSend == SOCKET_ERROR) {
std::cout << "send failed!\n";
break;
}
// 7.接收資料
recv(sockConn, recvBuf, 100, 0);
std::cout << recvBuf << std::endl;
// 關閉套接字
closesocket(sockConn);
}
// 8.關閉套接字
closesocket(sockSrv);
// 9.清理套接字庫
WSACleanup();
return 0;
}
2). 客戶端
-
初始化套接字庫
對應圖中socket()WORD wVersion; WSADATA wsaData; int err; // 可以理解為1.1 wVersion = MAKEWORD(1, 1); // 例:MAKEWORD(a, b) --> b | a << 8 將a左移8位變成高位與b合并起來 // 啟動 err = WSAStartup(wVersion, &wsaData); // 創建TCP套接字 SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0); -
連接服務器
對應圖中connect()// 連接服務器 int err_log = connect(sockCli, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR)); -
發送資料到服務器
對應圖中write()char sendBuf[] = "你好,服務器,我是客戶端!"; send(sockCli, sendBuf, strlen(sendBuf) + 1, 0); -
接收服務器的資料
對應圖中read()char recvBuf[100]; recv(sockCli, recvBuf, sizeof(recvBuf), 0); -
關閉套接字并清除套接字庫
對應圖中close()closesocket(sockCli); WSACleanup();
具體實作代碼:
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
int main(void) {
// 1.初始化套接字庫
WORD wVersion;
WSADATA wsaData;
int err;
// 可以理解為1.1
wVersion = MAKEWORD(1, 1); // 例:MAKEWORD(a, b) --> b | a << 8 將a左移8位變成高位與b合并起來
// 啟動
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
return err;
}
// 檢查:網路地位不等于1 || 網路高位不等于1
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
// 清理套接字庫
WSACleanup();
return -1;
}
// 創建TCP套接字
SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 服務器地址
addrSrv.sin_port = htons(6000); // 埠號
addrSrv.sin_family = AF_INET; // 地址型別(ipv4)
// 2.連接服務器
int err_log = connect(sockCli, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
if (err_log == 0) {
printf("連接服務器成功!\n");
} else {
printf("連接服務器失敗!\n");
return -1;
}
char recvBuf[100];
char sendBuf[] = "你好,服務器,我是客戶端!";
// 3.發送資料到服務器
send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
// 4.接收服務器的資料
recv(sockCli, recvBuf, sizeof(recvBuf), 0);
std::cout << recvBuf << std::endl;
// 5.關閉套接字并清除套接字庫
closesocket(sockCli);
WSACleanup();
system("pause");
return 0;
}
運行效果:

3). TCP聊天小專案
下面是根據上面的代碼修改的一個聊天小專案(使用到了多執行緒)
只有一個服務器,服務器一直開啟等待客戶端連接;
客戶都安可以開啟多個,且可以一直連續的與服務器進行發送接收訊息;
服務器給客戶端發送資料,得通過1 - 9來區分到底給那個客戶端發送訊息,例如給第二個客戶端發送訊息:2你好,客戶端
客戶端那邊接收到的資料是:你好,客戶端
服務器代碼:
#include <iostream>
#include <WinSock2.h>
#include <stdio.h>
#include <Windows.h>
#include <process.h>
#include <vector>
#include <conio.h>
#include <string.h>
#include <string>
#pragma comment(lib, "ws2_32.lib")
SOCKET sockSrv;
std::vector<SOCKET> vec_sockConn;
std::vector<SOCKADDR_IN> vec_sockaddr_in;
std::vector<int> vec_sockIndex;
// 這個結構體用作執行緒引數
typedef struct SERVER_CLIENT {
SOCKET server;
SOCKADDR_IN client;
int clientIndex;
}SC;
// 判斷有沒有斷開連接
bool IsSocketClosed(SOCKET clientSocket) {
bool ret = false;
HANDLE closeEvent = WSACreateEvent();
WSAEventSelect(clientSocket, closeEvent, FD_CLOSE);
DWORD dwRet = WaitForSingleObject(closeEvent, 0);
if (dwRet == WSA_WAIT_EVENT_0)
ret = true;
else if (dwRet == WSA_WAIT_TIMEOUT)
ret = false;
WSACloseEvent(closeEvent);
return ret;
}
// 接收請求
unsigned int WINAPI ThreadAccept(LPVOID p) {
static int i = 0;
while (1) {
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
// 5.接收連接請求,回傳針對客戶端的套接字
SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrCli, &len);
if (sockConn == SOCKET_ERROR) {
printf("Accept failed:%d", WSAGetLastError());
}
// 存盤當前服務器與客戶端 連接系結的socket
vec_sockIndex.emplace_back(i++);
vec_sockaddr_in.emplace_back(addrCli);
vec_sockConn.emplace_back(sockConn);
printf("\033[0;%d;40m客戶端[%d]上線\033[0m\n", 31, i);
}
return 0;
}
unsigned int WINAPI _ThreadRecv(LPVOID p) {
char recvBuf[100];
memset(recvBuf, 0, 100);
SC _sc = *(SC *)p;
while (1) {
Sleep(20);
if (IsSocketClosed(_sc.server) == true) {
printf("客戶端 [%d] 斷開連接!\n", _sc.clientIndex + 1);
break;
}
// 接收資料
recv(_sc.server, recvBuf, 100, 0);
if (strlen(recvBuf) == 0) {
continue;
}
printf("接收到客戶端 [%d] 的訊息:%s\n", _sc.clientIndex + 1, recvBuf);
memset(recvBuf, 0, 100);
}
return 0;
}
unsigned int WINAPI ThreadRecv(LPVOID p) {
static int index = 0;
while (1) {
// 還沒有客戶端與服務器進行連接
if (vec_sockConn.size() == 0) {
continue;
}
// 接收執行緒已經開啟和客戶端個數相等
if (vec_sockConn.size() == index) {
continue;
}
SC sc;
sc.server = vec_sockConn.at(index);
sc.client = vec_sockaddr_in.at(index);
sc.clientIndex = vec_sockIndex.at(index);
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, _ThreadRecv, (void *)&sc, 0, NULL);
index++;
Sleep(20);
}
return 0;
}
int main(void) {
// 1.初始化套接字庫
WORD wVersion;
WSADATA wsaData;
int err;
// 設定版本,可以理解為1.1
wVersion = MAKEWORD(1, 1); // 例:MAKEWORD(a, b) --> b | a << 8 將a左移8位變成高位與b合并起來
// 啟動
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
return err;
}
// 檢查:網路低位不等于1 || 網路高位不等于1
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
// 清理套接字庫
WSACleanup();
return -1;
}
// 2.創建tcp套接字 // AF_INET:ipv4 AF_INET6:ipv6
sockSrv = socket(AF_INET, SOCK_STREAM, 0);
// 準備系結資訊
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 設定系結網卡
addrSrv.sin_family = AF_INET; // 設定系結網路模式
addrSrv.sin_port = htons(6000); // 設定系結埠
// hton: host to network x86:小端 網路傳輸:htons大端
// 3.系結到本機
int retVal = bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
if (retVal == SOCKET_ERROR) {
printf("Failed bind:%d\n", WSAGetLastError());
return -1;
}
// 4.監聽,同時接收10個鏈接
if (listen(sockSrv, 10) == SOCKET_ERROR) {
printf("Listen failed:%d", WSAGetLastError());
return -1;
}
std::cout << "Server start at port: 6000" << std::endl;
// 執行緒句柄 // 創建執行緒
HANDLE hThread_1 = (HANDLE)_beginthreadex(NULL, 0, ThreadAccept, NULL, 0, NULL);
HANDLE hThread_2 = (HANDLE)_beginthreadex(NULL, 0, ThreadRecv, NULL, 0, NULL);
//uiInit();
//editPrint(0, ">");
char sendBuf[100];
while (1) {
//printf("請輸入發送內容:");
char c = getchar(); // 輸入發送給誰
scanf_s("%s", sendBuf, 100); // 輸入發送的內容
if (strlen(sendBuf) == 0) {
printf("輸入內容為慷訓者超長!\n");
}
// 1 至 9
if (c < '1' || c > '9' || vec_sockConn.size() == 0 || c - '0' >= vec_sockConn.size() + 1) {
while ((c = getchar()) != '\n'); // 清空輸入緩沖區
memset(sendBuf, 0, 100);
printf("輸入內容不符合規則!\n");
continue;
}
// 發送資料
int index = --c - '0'; // 因為下標是從零開始的,所以c要先自減
int iSend = send(vec_sockConn.at(index) , sendBuf, strlen(sendBuf) + 1, 0);
if (iSend == SOCKET_ERROR) {
std::cout << "send failed!\n";
break;
}
memset(sendBuf, 0, 100);
while ((c = getchar()) != '\n'); // 清空輸入緩沖區
}
// 關閉套接字
std::vector<SOCKET>::iterator it = vec_sockConn.begin();
for (; it != vec_sockConn.end(); it++) {
closesocket((SOCKET)(*it));
}
WaitForSingleObject(hThread_1, INFINITE);
WaitForSingleObject(hThread_2, INFINITE);
CloseHandle(hThread_1);
CloseHandle(hThread_2);
// 7.關閉套接字
closesocket(sockSrv);
// 8.清理套接字庫
WSACleanup();
return 0;
}
客戶端:
#include <iostream>
#include <WinSock2.h>
#include <process.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
SOCKET sockCli;
// 判斷有沒有斷開連接
bool IsSocketClosed(SOCKET clientSocket) {
bool ret = false;
HANDLE closeEvent = WSACreateEvent();
WSAEventSelect(clientSocket, closeEvent, FD_CLOSE);
DWORD dwRet = WaitForSingleObject(closeEvent, 0);
if (dwRet == WSA_WAIT_EVENT_0)
ret = true;
else if (dwRet == WSA_WAIT_TIMEOUT)
ret = false;
WSACloseEvent(closeEvent);
return ret;
}
unsigned int WINAPI ThreadRecv(LPVOID p) {
char recvBuf[100];
memset(recvBuf, 0, 100);
while (1) {
Sleep(20);
if (IsSocketClosed(sockCli) == true) {
printf("服務器 斷開連接!\n");
break;
}
// 接收服務器的資料
recv(sockCli, recvBuf, sizeof(recvBuf), 0);
if (strlen(recvBuf) == 0) continue;
std::cout << recvBuf << std::endl;
memset(recvBuf, 0, 100);
}
return 0;
}
int main(void) {
// 1.初始化套接字庫
WORD wVersion;
WSADATA wsaData;
int err;
// 可以理解為1.1
wVersion = MAKEWORD(1, 1); // 例:MAKEWORD(a, b) --> b | a << 8 將a左移8位變成高位與b合并起來
// 啟動
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
return err;
}
// 檢查:網路地位不等于1 || 網路高位不等于1
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
// 清理套接字庫
WSACleanup();
return -1;
}
// 創建TCP套接字
sockCli = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 服務器地址
addrSrv.sin_port = htons(6000); // 埠號
addrSrv.sin_family = AF_INET; // 地址型別(ipv4)
// 連接服務器
int err_log = connect(sockCli, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
if (err_log == 0) {
printf("連接服務器成功!\n");
} else {
printf("連接服務器失敗!\n");
return -1;
}
// 執行緒句柄 // 創建執行緒
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadRecv, NULL, 0, NULL);
char sendBuf[100];
while (1) {
//printf("請輸入發送內容:");
scanf_s("%s", sendBuf, 100);
// 發送資料到服務器
send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
memset(sendBuf, 0, 100);
char c;
while ((c = getchar()) != '\n');
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
// 關閉套接字并清除套接字庫
closesocket(sockCli);
WSACleanup();
system("pause");
return 0;
}
運行效果:

UDP
UDP就比較簡單了,步驟比tcp要少一些,
連接程序圖:

1). 服務器
-
初始化套接字庫
WORD wVersion; WSADATA wsaData; int err; wVersion = MAKEWORD(1, 1); -
創建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0); -
系結
// SOCKADDR_IN addrSrv; 省略了定義和賦值 bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR)); -
接收資料
char recvBuf[100]; recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR *)&addrCli, &len); -
發送資料
char sendBuf[] = "hello Client,I'm Server!\n"; sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR *)&addrCli, len); -
關閉
closesocket(sockSrv); WSACleanup();
具體實作代碼:
#include <WinSock2.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
int main(void) {
// 初始化套接字庫
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
WSACleanup();
return -1;
}
// 創建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6001);
// 系結到本機6001埠
bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
// 接收請求,處理請求
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
char sendBuf[] = "hello Client,I'm Server!\n";
char recvBuf[100];
std::cout << "start UDP server with port 6001" << std::endl;
while (1) {
// 接收資料
recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR *)&addrCli, &len);
std::cout << "Recv:" << recvBuf << std::endl;
// 發送資料
sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR *)&addrCli, len);
std::cout << "Send:" << sendBuf << std::endl;
}
closesocket(sockSrv);
WSACleanup();
return 0;
}
2). 客戶端
-
初始化套接字庫
WORD wVersion; WSADATA wsaData; int err; wVersion = MAKEWORD(1, 1); -
創建UDP套接字
SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0); SOCKADDR_IN addrSrv; -
接收資料
char recvBuf[100]; recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR *)&addrCli, &len); -
發送資料
char sendBuf[] = "hello Client,I'm Server!\n"; sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR *)&addrSrv, len); -
關閉
closesocket(sockSrv); WSACleanup();
具體實作代碼:
#include <WinSock2.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
int main(void) {
// 初始化套接字庫
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
if (err != 0) {
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
WSACleanup();
return -1;
}
// 創建UDP套接字
SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6001);
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
char sendBuf[] = "hello, I'm Client!\n";
char recvBuf[100];
std::cout << "send to Server: " << sendBuf << std::endl;
sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR *)&addrSrv, len);
recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR *)&addrCli, &len);
std::cout << "recv from: " << recvBuf << std::endl;
closesocket(sockCli);
WSACleanup();
system("pause");
return 0;
}
運行效果:

總結
socket的具體細節用法我不太清楚,現階段也只是熟悉TCP的一些簡單操作,UDP的話也還不是太懂,不懂的是不知道在具體專案中該如何進行使用它們,
那個TCP的小專案也只是自己琢磨搞出來的,不知掉具體專案會不會這樣去寫!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/413345.html
標籤:其他
下一篇:Nginx優化與防盜鏈
