您的位置:首页 > 健康 > 养生 > C++ 封装 Socket 进行通信

C++ 封装 Socket 进行通信

2024/11/8 15:41:56 来源:https://blog.csdn.net/m0_52423355/article/details/142254253  浏览:    关键词: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

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com