当前位置: 首页 > news >正文

C++ 封装 Socket 进行通信

基于 C++ 的封装

文章目录

    • 基于 C++ 的封装
      • 通信 socket 的封装
      • 服务端的封装
      • 测试-客户端
      • 测试-服务端
      • 测试结果

通信 socket 的封装

此处封装 socket 的功能主要是用于通信,因此包括用于通信的文件套接字 m_fd,用于通信的函数 sendMsg 和 recvMsg;另外,在通信时,会出现 TCP 粘包问题,因此通过辅助函数 readn 和 writen 来解决该问题;

对于客户端和服务器,其中服务器的套接字有两种,用于监听和用于通信;而客户端的套接字功能只有通信,因此客户端也使用当前套接字。

由于是用于通信的 socket,因此客户端直接使用该套接字即可;因此在该类中定义了客户端连接服务器的函数 connectToHost

#ifndef _TCPSOCKET_H_
#define _TCPSOCKET_H_
#include <string>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
using namespace std;class Socket
{
public:Socket();Socket(int socket);~Socket();int connectHost(string ip, unsigned short port);string recvMsg();int sendMsg(string msg);private:int readn(char* msg, int size);int writen(const char* msg, int size);
private:int m_fd;
};#endif  // _TCPSOCKET_H_

实现:

#include "Socket.h"Socket::Socket()
{m_fd = socket(AF_INET, SOCK_STREAM, 0);
}Socket::Socket(int socket)
{m_fd = socket;
}Socket::~Socket()
{if (m_fd > 0) {close(m_fd);}
}// 知晓 ip 和 port 后,直接连接服务器,注意大端小端的转换!!!
int Socket::connectHost(string ip, unsigned short port)
{sockaddr_in addr;// string.data() 返回一个指向字符串内部字符串数组的指针addr.sin_family = AF_INET;inet_pton(AF_INET, ip.data(), (char*)&addr.sin_addr.s_addr);addr.sin_port = htons(port);int ret = connect(m_fd, (sockaddr*)&addr, sizeof(addr));if (ret == -1) {cout << "connect fail..." << endl;return -1;}return ret;
}// 接收时注意到信息最开始四个字节为信息大小,这可以解决粘包问题
string Socket::recvMsg()
{int len = 0;// 首先接收前四个字节的信息,即后面整条信息的长度read(m_fd, &len, 4);len = ntohl(len);cout << "信息大小为:" << len << endl;char* msg = new char[len + 1];int ret = readn(msg, len);if (ret != len) {cout << "recvMsg fail...\n" << endl;return string();}msg[len] = '\0';string res(msg);delete[] msg;return res;
}// 在信息前加上大小,以解决粘包问题
int Socket::sendMsg(string msg)
{int len = msg.size();char* buf = new char[len + 4];int bigLen = htonl(len);memcpy(buf, &bigLen, 4);memcpy(buf + 4, msg.data(), len);int ret = writen(buf, len + 4);if (ret != len) {cout << "send fail...\n" << endl;return -1;}delete[] buf;return ret;
}// 一次性接收大小为 size 的 msg 信息
int Socket::readn(char *msg, int size)
{char* p = msg;int count = size;while (count > 0) {int len = recv(m_fd, p, count, 0);if (len == -1) {cout << "recv fail...\n" << endl;return -1;} else if (len == 0) {continue;}count -= len;p += len;}return size;
}// 一次性将大小为 size 的信息 msg 发送出去
int Socket::writen(const char *msg, int size)
{const char* p = msg;int count = size;while (count > 0) {int len = send(m_fd, p, count, 0);if (len == 0) {continue;} else if (len == -1) {cout << "send fail...\n" << endl;return -1;}p += len;count -= len;}return size;
}

服务端的封装

服务端主要有两个功能,监听客户端请求和和客户端通信。通信的套接字在 Socket 中已经定义了,此处仅仅定义具有监听功能的套接字。

因此成员变量只有一个,即用于监听的套接字。而成员函数主要有两个,即将服务器 ip 和 port 绑定到套接字上的函数,以及创建通信套接字的函数。因此有如下定义:

#ifndef _TCPSERVER_H_
#define _TCPSERVER_H_
#include "Socket.h"class Server {
public:Server();~Server();// 绑定时只需要指定端口即可,ip 可以自动获取int BindAndListen(unsigned short port);// 和客户端建立连接后需要记录客户端的 addr,并获取用于通信的 socketSocket* acceptConn(sockaddr_in* addr);
private:int m_fd;
};#endif  // _TCPSERVER_H_

实现:

#include "Server.h"Server::Server()
{m_fd = socket(AF_INET, SOCK_STREAM, 0);
}Server::~Server()
{  if (m_fd > 0) {close(m_fd);}
}int Server::BindAndListen(unsigned short port)
{sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = INADDR_ANY;int ret = bind(m_fd, (sockaddr*)&addr, sizeof(addr));if (ret == -1) {cout << "bind fail...\n" << endl;return -1;}cout << "套接字绑定成功, ip 为" << inet_ntoa(addr.sin_addr) << endl;ret = listen(m_fd, 128);if (ret == -1) {cout << "listen fail...\n" << endl;return -1;}cout << "设置监听成功" << endl;return ret;
}Socket *Server::acceptConn(sockaddr_in *addr)
{if (addr == nullptr) {return nullptr;}socklen_t len = sizeof(addr);int cfd = accept(m_fd, (sockaddr*)addr, &len);if (cfd == -1) {cout << "accept fail...\n" << endl;return nullptr;}cout << "成功建立连接" << endl;return new Socket(cfd);
}

测试-客户端

客户端仅仅一个单一的功能,即通信;因此直接使用 Socket 即可;

  • 定义 Socket;
  • 读取文件到指定大小的缓存 tmp 中;
  • 使用 Socket 封装的函数发送;
#include <string>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <fcntl.h>
#include "Socket.h"
using namespace std;int main() {Socket sk;// 注意此处的 ip 改为自己的 ipint ret = sk.connectHost("127.0.0.1", 9898);if (ret == -1) {cout << "connect fail...\n" << endl;return -1;}int fd1 = open("LQ.txt", O_RDONLY);char tmp[128];int len = 0;memset(tmp, 0, sizeof(tmp));while ((len = read(fd1, tmp, sizeof(tmp))) > 0) {sk.sendMsg(string(tmp, len));cout << "send msg" << endl;cout << tmp << "\n\n\n" << endl;memset(tmp, 0, sizeof(tmp));usleep(300);}sleep(10);return 0;
}

测试-服务端

服务端用于接收客户端请求(监听);以及和客户端通信;此处要求服务端能处理多个客户端的请求,因此此处使用多线程进行处理;

  • 服务端首先定义服务端的对象 Server,该对象用于监听客户端请求;

  • 在设置好 bind 和 listen 后即可通过 accept 与客户端建立请求后进行通信;

  • 由于需要处理多个客户端,因此此处使用一个死循环来进行 accept 以建立和客户端的通信,并通过创建线程的方式来在线程能创建一个用于通信的套接字。

  • 该线程专门用于通信。因此需要创建一个函数,该函数负担了当前线程的任务,即与客户端进行通信。该函数若要和客户端进行通信,所需要的信息是客户端的信息,该信息是通过 accept 得到的,需要通过参数传递给函数;处理客户端的 addr,还需要的信息是用于通信的 socket,这也是通过 accept 获取的。由于要传递多个参数,因此将这两个进行封装,将其封装为结构体进行参数传递。

#include <string>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <fcntl.h>
#include <pthread.h>
#include "Server.h"
using namespace std;struct SockInfo {Socket* sock;sockaddr_in addr;
};void* working(void* arg) {SockInfo* info = static_cast<SockInfo*> (arg);char ip[32];printf("客户端的IP: %s, 端口: %d\n",inet_ntop(AF_INET, &info->addr.sin_addr.s_addr, ip, sizeof(ip)),ntohs(info->addr.sin_port));while (1) {cout << "接收数据" << endl;string msg = info->sock->recvMsg();if (!msg.empty()) {cout << msg << "\n\n\n";} else {break;}}delete info->sock;delete info;return nullptr;
}int main() {Server ss;int ret = ss.BindAndListen(9898);if (ret == -1) {cout << "bind and listen fail...\n" << endl;return -1;}while (1) {SockInfo* info = new SockInfo;Socket *sock = ss.acceptConn(&info->addr);info->sock = sock;pthread_t tid;pthread_create(&tid, nullptr, working, info);pthread_detach(tid);}return 0;
}

测试结果

g++ Socket.cpp Server.cpp server.cpp -o server -lpthread
g++ client.cpp Socket.cpp -o client

先启动服务端,后启动客户端:

./server
./client

即可顺利进行测试;若需要测试多线程功能,则仿造以上的客户端再写一个即可;

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Docker上安装mysql
  • 【Day14-单例设计模式动态代理】
  • Docker torchserve 部署模型流程
  • 数据分析-前期数据处理
  • 雷达液位计助力造纸行业精准测量
  • Spring security 动态权限管理(基于数据库)
  • 使用Python生成多种不同类型的Excel图表
  • 计算机毕业设计 毕业季一站式旅游服务定制平台的设计与实现 Java实战项目 附源码+文档+视频讲解
  • Ribbon (WPF)
  • 研1日记11
  • 302状态如何进行重定向
  • 深度估计智能化的应用
  • Prometheus优化指南:如何提升系统性能
  • windows@共享网络共享打印机@局域网内远程调用打印机打印
  • Kafka客户端核心参数详解
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • 【407天】跃迁之路——程序员高效学习方法论探索系列(实验阶段164-2018.03.19)...
  • CentOS从零开始部署Nodejs项目
  • Javascripit类型转换比较那点事儿,双等号(==)
  • JavaScript新鲜事·第5期
  • JavaScript学习总结——原型
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • magento2项目上线注意事项
  • Nginx 通过 Lua + Redis 实现动态封禁 IP
  • Phpstorm怎样批量删除空行?
  • Python学习之路13-记分
  • Swift 中的尾递归和蹦床
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • 包装类对象
  • 搭建gitbook 和 访问权限认证
  • 仿天猫超市收藏抛物线动画工具库
  • 前端技术周刊 2019-01-14:客户端存储
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 首页查询功能的一次实现过程
  • 推荐一个React的管理后台框架
  • 小而合理的前端理论:rscss和rsjs
  • NLPIR智能语义技术让大数据挖掘更简单
  • ​虚拟化系列介绍(十)
  • # 20155222 2016-2017-2 《Java程序设计》第5周学习总结
  • $(function(){})与(function($){....})(jQuery)的区别
  • ( 10 )MySQL中的外键
  • (2024)docker-compose实战 (8)部署LAMP项目(最终版)
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第5节(封闭类和Final方法)
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (二)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (四)js前端开发中设计模式之工厂方法模式
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (原創) 物件導向與老子思想 (OO)
  • (源码分析)springsecurity认证授权
  • (转) Android中ViewStub组件使用
  • (自用)仿写程序
  • ***利用Ms05002溢出找“肉鸡
  • .md即markdown文件的基本常用编写语法