C++|C++ Socket实现TCP与UDP网络编程
目录
- 前言
- TCP
- 1). 服务器
- 2). 客户端
- 3). TCP聊天小项目
- UDP
- 1). 服务器
- 2). 客户端
- 总结
前言 socket编程分为TCP和UDP两个模块,其中TCP是可靠的、安全的,常用于发送文件等,而UDP是不可靠的、不安全的,常用作视频通话等。
如下图:
文章图片
头文件与库:
#include#pragma comment(lib, "ws2_32.lib")
准备工作:
创建工程后,首先右键工程,选择属性
文章图片
然后选择 C/C++ - 预处理器 - 预处理器定义
文章图片
将字符串 _WINSOCK_DEPRECATED_NO_WARNINGS 添加到里面去,点击应用即可!
TCP 连接过程图:
文章图片
创建tcp服务器和客户端都是按照上图的步骤来操作的!
1). 服务器
初始化套接字库
对应图中socket()
WORD wVersion; WSADATA wsaData; int err; // 设置版本,可以理解为1.1wVersion = MAKEWORD(1, 1); // 例:MAKEWORD(a, b) --> b | a << 8 将a左移8位变成高位与b合并起来// 启动err = WSAStartup(wVersion, &wsaData);
创建tcp套接字
对应图中socket()
// AF_INET:ipv4AF_INET6:ipv6SOCKET 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 networkx86:小端网络传输: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#include #include #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:ipv4AF_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 networkx86:小端网络传输: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.1wVersion = 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#include #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#include #include #include #include #include #include #include #include #pragma comment(lib, "ws2_32.lib")SOCKET sockSrv; std::vector vec_sockConn; std::vector vec_sockaddr_in; std::vector 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 WINAPIThreadAccept(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()); }// 存储当前服务器与客户端 连接绑定的socketvec_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 WINAPIThreadRecv(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:ipv4AF_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 networkx86:小端网络传输: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 至 9if (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::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#include #include #include #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 WINAPIThreadRecv(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; }
运行效果:
文章图片
【C++|C++ Socket实现TCP与UDP网络编程】
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#include #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#include #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的小项目也只是自己琢磨搞出来的,不知掉具体项目会不会这样去写!
到此这篇关于C++ Socket实现TCP与UDP网络编程的文章就介绍到这了,更多相关C++ Socket实现TCP与UDP内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
推荐阅读
- C++算法设计之马踏棋盘的实现
- C++标准库实现WAV文件读写的操作
- C语言实现简单的猜数字游戏
- C++实现LeetCode(38.计数和读法)
- 10行Python代码就能实现的八种有趣功能详解
- 深入了解vue-router原理并实现一个小demo
- mybatis多表查询的实现(xml方式)
- vue实现登录滑动拼图验证
- 别再用 Redis List 实现消息队列了,Stream 专为队列而生
- Apache Pulsar 技术系列 - Pulsar事务实现原理