热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C++简单的TCP/IP服务端与客户端(附完整代码)

一.TCP服务端(一)创建一个TCP服务端可大致分为以下5个步骤:1.初始化环境2.创建监听套接字3.监听套接字与IP地址及端口绑定4.监听套接字5.等待客户端连接1.初始化环境W
一. TCP服务端

(一)创建一个TCP服务端

可大致分为以下5个步骤:

1.初始化环境
2.创建监听套接字
3.监听套接字与IP地址及端口绑定
4.监听套接字
5.等待客户端连接

1. 初始化环境

WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

WSAStartup()函数的调用指明Windows Sockets API的版本号及获得特定Windows Sockets实现的细节,应用程序或DLL只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数。
该函数执行成功后返回0。

2. 创建监听套接字

m_listenSocket = socket(AF_INET, SOCK_STREAM, 0));

AF_INET:指定使用IPV4协议簇
SOCK_STREAM:字节流,数据有保障的传输
0:不希望指定协议
该函数执行成功返回一个新的套接字,否则返回:INVALID_SOCKET。

3. 监听套接字与IP地址及端口绑定

sockaddr_in sockadd = { 0, };
sockadd.sin_family = AF_INET;//IPV4协议簇
sockadd.sin_port = htons(m_uPort);//监听端口
sockadd.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//监听本机任意IP
bind(m_listenSocket, (struct sockaddr*) & sockadd, sizeof(sockadd));

m_listenSocket:已经创建好的监听套接字
INADDR_ANY:如果机器存在多网卡,客户端连接任意一个IP地址均可建立通信
执行失败时返回: SOCKET_ERROR

4. 监听套接字

listen(m_listenSocket, 1);

m_listenSocket:绑定IP地址及端口的监听地址
1:指定最大允许同时连接的客户端数

5. 等待客户端连接

m_clientSocket = accept(m_listenSocket, (struct sockaddr*) & addr, &addrlen);

提取套接字m_listenSocket上挂起连接队列的第一个连接,然后返回新套接字m_clientSocket,如过要与客户端通信就需要通过m_clientSocket来发送或接收数据
如果暂时没有客户端连接过来,该函数将阻塞在此处,直到新连接到来才会返回。

(二)数据收发

1. 接收数据

const int iBufSize = 1024;
char recvBuf[iBufSize] = { 0, };
auto iRecvSize = recv(m_clientSocket, recvBuf, iBufSize, 0);//若不支持C++11及以上,auto改为int

m_clientSocket:收发数据时,均需要用到accept()返回的客户端套接字
recvBuf:接收数据缓冲区,收到的数据就保存在该数组中
iBufSize:recvBuf数组的字节长度,最多接收到的数据长度,该缓冲区也只能接收这么多,否则会溢出,造成程序异常
0:将数据从TCP从输入队列中剪切到recvBuf,且不做其他操作
返回值:如果没有发生错误,recv将返回接收到的字节数,recvBuf参数指向的缓冲区将包含接收到的数据。如果连接已经正常关闭,则返回值为零。否则,将返回一个SOCKET_ERROR值
如果套接字m_clientSocket上没有可用的传入数据,则recv调用阻塞并等待数据并不会返回,除非定义了其他阻塞规则,此处并没有指定

2. 发送数据

std::string strMsg = "hello";
send(m_clientSocket, strMsg.c_str(), strMsg.length(), 0);

m_clientSocket:收发数据时,均需要用到accept()返回的客户端套接字
strMsg.c_str():发送数据首地址
strMsg.length():数据长度
0:无特别指定调用
返回值:如果没有发生错误,send返回发送的字节总数,它可能小于len参数中请求发送的字节数。否则,将返回SOCKET_ERROR的值

(三)服务端代码

CTcpServer.h

//CTcpServer.h
#pragma once
#include
#include
#pragma comment(lib,"ws2_32")//Standard socket API.
class CTcpServer
{
public:
CTcpServer(std::string strIp, unsigned int uPort);
virtual ~CTcpServer();
//初始化网络服务端
bool InitServer();
//发送数据
bool SendMsg(const std::string& strMsg);
//接收数据并打印
bool RecvMsg();
private:
unsigned int m_uPort;//监听端口
std::string m_strIp;//用于监听本机指定IP地址
SOCKET m_listenSocket = NULL;//监听套接字
SOCKET m_clientSocket = NULL;//客户端套接字
};

CTcpServer.cpp

//CTcpServer.cpp
#include
#include "CTcpServer.h"
CTcpServer::CTcpServer(std::string strIp, unsigned int uPort) :
m_strIp(strIp),
m_uPort(uPort)
{
}
CTcpServer::~CTcpServer()
{
if (m_clientSocket)
{
closesocket(m_clientSocket);
m_clientSocket = NULL;
}
if (m_listenSocket)
{
closesocket(m_listenSocket);
m_listenSocket = NULL;
}
WSACleanup();
}
bool CTcpServer::InitServer()
{
WSADATA wsaData;
//1. 初始化环境
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
std::cout << "Init Windows Socket Failed!\n";
return false;
}
//2. 创建监听套接字
if ((m_listenSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
{
std::cout << "Create socket failed!\n";
return false;
}
//协议
sockaddr_in sockadd = { 0, };
sockadd.sin_family = AF_INET;//IPV4协议簇
sockadd.sin_port = htons(m_uPort);//监听端口
sockadd.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//监听本机任意IP
//3. 监听套接字与IP地址及端口绑定
if (bind(m_listenSocket, (struct sockaddr*) & sockadd, sizeof(sockadd)) == SOCKET_ERROR)
{
closesocket(m_listenSocket);
m_listenSocket = INVALID_SOCKET;
std::cout << "Socket bind failed!\n";
return false;
}
//4. 监听套接字
if (listen(m_listenSocket, 1) == SOCKET_ERROR)
{
closesocket(m_listenSocket);
m_listenSocket = INVALID_SOCKET;
std::cout << "Socket listen failed!\n";
return false;
}
sockaddr_in addr = { 0, };
int addrlen = sizeof(addr);
//5. 等待客户端连接
m_clientSocket = accept(m_listenSocket, (struct sockaddr*) & addr, &addrlen);
if (m_clientSocket == SOCKET_ERROR)
{
closesocket(m_clientSocket);
m_clientSocket = INVALID_SOCKET;
std::cout << "Socket accept failed!\n";
return false;
}
return true;
}
bool CTcpServer::SendMsg(const std::string& strMsg)
{
if (!m_clientSocket) return false;
if (send(m_clientSocket, strMsg.c_str(), strMsg.length(), 0) != INVALID_SOCKET)
{
std::cout << "发送成功:" << strMsg << "\n";
return true;
}
else
{
std::cout << "发送失败!\n";
return false;
}
}
bool CTcpServer::RecvMsg()
{
if (!m_clientSocket) return false;
const int iBufSize = 1024;
char recvBuf[iBufSize] = { 0, };
auto iRecvSize = recv(m_clientSocket, recvBuf, iBufSize, 0);//若不支持C++11及以上,auto改为int
if (iRecvSize <= 0)
{
std::cout << "接收失败!\n";
return false;
}
else
{
std::cout << "接收成功:" << recvBuf << "\n";
return true;
}
}

#include
#include "CTcpServer.h"
int main()
{
{
CTcpServer tcpServer("127.0.0.1", 6005);
if (!tcpServer.InitServer())
getchar();
for (int i = 0; i != 3; ++i)
{
//接收数据成功后,向客户端返回"answer"
if (tcpServer.RecvMsg())
{
std::string strAnswerMsg{ "answer" };
tcpServer.SendMsg(strAnswerMsg);
}
}
}//接收3次数据后出此大括号,tcpServer对象被析构,客户端连接被关闭

return 0;
}
二. TCP客户端

创建一个TCP客户端

1. 初始化环境
2. 创建一个新的套接字
3. 建立连接

客户端的创建比较简单,只需要三步,创建好了之后就可以进行数据的收发了
短链接:客户端与服务端建立连接之后,进行短暂的收发数据之后即断开连接(closesocket())称为短链接
长连接:两者建立连接之后需要长时间保持该连接的成为长连接,在不需要数据交互时可以每隔一定时间其中一方发送一个心跳报文(内容可以是约定的任意值,比如字符串“heartBeat”)到另一方,另一方收到心跳报文后立即恢复一个应答(同样是约定的任意值),发送方在一定次数没有收到应答 或者 接收方一定次数没有收到心跳报文时,均可判定对方断线,此时可以关闭套接字或其他业务逻辑

还是直接上代码吧

CTcpClient.h

//CTcpClient.h
#pragma once
#include
#include
#pragma comment(lib,"ws2_32")//Standard socket API.
class CTcpClient
{
public:
CTcpClient(std::string strServerIp, unsigned uServerPort);
virtual ~CTcpClient();
//建立连接
bool InitConnect();
//发送数据
bool SendMsg(const std::string& strMsg);
//接收数据并打印
bool RecvMsg();
private:
SOCKET m_socket = INVALID_SOCKET;
std::string m_strServerIp;//服务端监听IP地址
unsigned int m_uServerPort = -1;//服务端监听端口
struct addrinfo* m_servAddrInfo = NULL;//服务端地址结构链表
};

CTcpClient.cpp

//CTcpClient.cpp
#include
#include
#include
#include
#include "CTcpClient.h"
CTcpClient::CTcpClient(std::string strServerIp, unsigned uServerPort) :
m_strServerIp(strServerIp),
m_uServerPort(uServerPort)
{
}
CTcpClient::~CTcpClient()
{
if (m_socket != INVALID_SOCKET)
closesocket(m_socket);
WSACleanup();
freeaddrinfo(m_servAddrInfo);
}
bool CTcpClient::InitConnect()
{
WSADATA wsaData;
//1. 初始化环境
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
std::cout << "Init Windows Socket Failed!\n";
return false;
}
addrinfo hints = { 0, };//协议无关(IPV4 or IPV6)
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
std::stringstream ssPort;
ssPort << m_uServerPort;
//获取服务端地址结构
if (getaddrinfo(m_strServerIp.c_str(), ssPort.str().c_str(), &hints, &m_servAddrInfo) != 0)
{
std::cout << "Get server addrInfo failed!\n";
return false;
}
if (m_socket != INVALID_SOCKET)
closesocket(m_socket);
m_socket = INVALID_SOCKET;
//2. 创建一个新的套接字
if ((m_socket = socket(m_servAddrInfo->ai_family, m_servAddrInfo->ai_socktype, m_servAddrInfo->ai_protocol)) == SOCKET_ERROR)
return false;
//3. 建立连接
int iResult = connect(m_socket, m_servAddrInfo->ai_addr, (int)m_servAddrInfo->ai_addrlen);
if (iResult == SOCKET_ERROR)
{
iResult = WSAGetLastError();

if (iResult != WSAEWOULDBLOCK)
{
std::cout << "Connect server failed!\n";
closesocket(m_socket);
m_socket = INVALID_SOCKET;
return false;
}
}
std::cout << "Connect Server succeed!\n";
return true;
}
bool CTcpClient::SendMsg(const std::string& strMsg)
{
if (!m_socket) return false;
if (send(m_socket, strMsg.c_str(), strMsg.length(), 0) != INVALID_SOCKET)
{
std::cout << "发送成功:" << strMsg << "\n";
return true;
}
else
{
std::cout << "发送失败!\n";
return false;
}
}
bool CTcpClient::RecvMsg()
{
if (!m_socket) return false;
const int iBufSize = 1024;
char recvBuf[iBufSize] = { 0, };
auto iRecvSize = recv(m_socket, recvBuf, iBufSize, 0);//若不支持C++11及以上,auto改为int
if (iRecvSize <= 0)
{
std::cout << "接收失败!\n";
return false;
}
else
{
std::cout << "接收成功:" << recvBuf << "\n";
return true;
}
}

#include
#include "CTcpClient.h"
int main()
{
{
CTcpClient tcpClient("127.0.0.1", 6005);
if (!tcpClient.InitConnect())
getchar();
std::string strAskMsg{ "ask" };
for (int i = 0; i != 3; ++i)
{
if (tcpClient.SendMsg(strAskMsg))
{
tcpClient.RecvMsg();
}
}
}

getchar();

return 0;
}

运行截图

《C++简单的TCP/IP服务端与客户端(附完整代码)》
有任何问题,欢迎留言


推荐阅读
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 现在学vb6还靠得住么?语言只是工具,关键是思想。程序=算法+数据结构。除了汇编,其他语言都靠不住。随着时代的进步,很多语言跟不上开发的要求。从面向过程到面向对象,与其说是思想的进步,不如说是为了适应高速开发。除了底层汇编语言,还有那些能适应高速开发的语言。每种语言都是很有趣的。 ... [详细]
  • Linux的uucico命令使用方法及工作模式介绍
    本文介绍了Linux的uucico命令的使用方法和工作模式,包括主动模式和附属模式。uucico是用来处理uucp或uux送到队列的文件传输工具,具有操作简单快捷、实用性强的特点。文章还介绍了uucico命令的参数及其说明,包括-c或--quiet、-C或--ifwork、-D或--nodetach、-e或--loop、-f或--force、-i或--stdin、-I--config、-l或--prompt等。通过本文的学习,读者可以更好地掌握Linux的uucico命令的使用方法。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 本文介绍了一道经典的状态压缩题目——关灯问题2,并提供了解决该问题的算法思路。通过使用二进制表示灯的状态,并枚举所有可能的状态,可以求解出最少按按钮的次数,从而将所有灯关掉。本文还对状压和位运算进行了解释,并指出了该方法的适用性和局限性。 ... [详细]
  • BZOJ1233 干草堆单调队列优化DP
    本文介绍了一个关于干草堆摆放的问题,通过使用单调队列来优化DP算法,求解最多可以叠几层干草堆。具体的解题思路和转移方程在文章中进行了详细说明,并给出了相应的代码示例。 ... [详细]
  • 本文详细介绍了PHP中与URL处理相关的三个函数:http_build_query、parse_str和查询字符串的解析。通过示例和语法说明,讲解了这些函数的使用方法和作用,帮助读者更好地理解和应用。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
author-avatar
wang静的天空
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有