套接字通信類的封裝

????????在掌握了基于TCP的套接字通信流程之后,為了方便使用,提高編碼效率,可以對通信操作進行封裝,本著有淺入深的原則,先基于C語言進行面向過程的函數封裝,然后再基于C++進行面向對象的類封裝。

1. 基于C語言的封裝

????????基于TCP的套接字通信分為兩部分:服務器端通信和客戶端通信。我們只要掌握了通信流程,封裝出對應的功能函數也就不在話下了,先來回顧一下通信流程:

服務器端

  • 創建用于監聽的套接字
  • 將用于監聽的套接字和本地的IP以及端口進行綁定
  • 啟動監聽
  • 等待并接受新的客戶端連接,連接建立得到用于通信的套接字和客戶端的IP、端口信息
  • 使用得到的通信的套接字和客戶端通信(接收和發送數據)
  • 通信結束,關閉套接字(監聽 + 通信)

客戶端

  • 創建用于通信的套接字
  • 使用服務器端綁定的IP和端口連接服務器
  • 使用通信的套接字和服務器通信(發送和接收數據)
  • 通信結束,關閉套接字(通信)

1.1 函數聲明

????????通過通信流程可以看出服務器和客戶端有些操作步驟是相同的,因此封裝的功能函數是可以共用的,相關的通信函數聲明如下:

 服務器 ///
int bindSocket(int lfd, unsigned short port);
int setListen(int lfd);
int acceptConn(int lfd, struct sockaddr_in *addr);客戶端 ///
int connectToHost(int fd, const char* ip, unsigned short port);/ 共用 
int createSocket();
int sendMsg(int fd, const char* msg);
int recvMsg(int fd, char* msg, int size);
int closeSocket(int fd);
int readn(int fd, char* buf, int size);
int writen(int fd, const char* msg, int size);

1.2 函數定義

// 創建監套接字
int createSocket()
{int fd = socket(AF_INET, SOCK_STREAM, 0);if(fd == -1){perror("socket");return -1;}printf("套接字創建成功, fd=%d\n", fd);return fd;
}// 綁定本地的IP和端口
int bindSocket(int lfd, unsigned short port)
{struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(port);saddr.sin_addr.s_addr = INADDR_ANY;  // 0 = 0.0.0.0int ret = bind(lfd, (struct sockaddr*)&saddr, sizeof(saddr));if(ret == -1){perror("bind");return -1;}printf("套接字綁定成功, ip: %s, port: %d\n",inet_ntoa(saddr.sin_addr), port);return ret;
}// 設置監聽
int setListen(int lfd)
{int ret = listen(lfd, 128);if(ret == -1){perror("listen");return -1;}printf("設置監聽成功...\n");return ret;
}// 阻塞并等待客戶端的連接
int acceptConn(int lfd, struct sockaddr_in *addr)
{int cfd = -1;if(addr == NULL){cfd = accept(lfd, NULL, NULL);}else{int addrlen = sizeof(struct sockaddr_in);cfd = accept(lfd, (struct sockaddr*)addr, &addrlen);}if(cfd == -1){perror("accept");return -1;}       printf("成功和客戶端建立連接...\n");return cfd; 
}// 接收數據
int recvMsg(int cfd, char** msg)
{if(msg == NULL || cfd <= 0){return -1;}// 接收數據// 1. 讀數據頭int len = 0;readn(cfd, (char*)&len, 4);len = ntohl(len);printf("數據塊大小: %d\n", len);// 根據讀出的長度分配內存char *buf = (char*)malloc(len+1);int ret = readn(cfd, buf, len);if(ret != len){return -1;}buf[len] = '\0';*msg = buf;return ret;
}// 發送數據
int sendMsg(int cfd, char* msg, int len)
{if(msg == NULL || len <= 0){return -1;}// 申請內存空間: 數據長度 + 包頭4字節(存儲數據長度)char* data = (char*)malloc(len+4);int bigLen = htonl(len);memcpy(data, &bigLen, 4);memcpy(data+4, msg, len);// 發送數據int ret = writen(cfd, data, len+4);return ret;
}// 連接服務器
int connectToHost(int fd, const char* ip, unsigned short port)
{// 2. 連接服務器IP portstruct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(port);inet_pton(AF_INET, ip, &saddr.sin_addr.s_addr);int ret = connect(fd, (struct sockaddr*)&saddr, sizeof(saddr));if(ret == -1){perror("connect");return -1;}printf("成功和服務器建立連接...\n");return ret;
}// 關閉套接字
int closeSocket(int fd)
{int ret = close(fd);if(ret == -1){perror("close");}return ret;
}// 接收指定的字節數
// 函數調用成功返回 size
int readn(int fd, char* buf, int size)
{int nread = 0;int left = size;char* p = buf;while(left > 0){if((nread = read(fd, p, left)) > 0){p += nread;left -= nread;}else if(nread == -1){return -1;}}return size;
}// 發送指定的字節數
// 函數調用成功返回 size
int writen(int fd, const char* msg, int size)
{int left = size;int nwrite = 0;const char* p = msg;while(left > 0){if((nwrite = write(fd, msg, left)) > 0){p += nwrite;left -= nwrite;}else if(nwrite == -1){return -1;}}return size;
}

2. 基于C++的封裝

????????編寫C++程序應當遵循面向對象三要素:封裝、繼承、多態。簡單地說就是封裝之后的類可以隱藏掉某些屬性使操作更簡單并且類的功能要單一,如果要代碼重用可以進行類之間的繼承,如果要讓函數的使用更加靈活可以使用多態。因此,我們需要封裝兩個類:客戶端類和服務器端的類。

2.1 版本1

根據面向對象的思想,整個通信過程不管是監聽還是通信的套接字都是可以封裝到類的內部并且將其隱藏掉,這樣相關操作函數的參數也就隨之減少了,使用者用起來也更簡便。

2.1.1 客戶端

class TcpClient
{
public:TcpClient();~TcpClient();// int connectToHost(int fd, const char* ip, unsigned short port);int connectToHost(string ip, unsigned short port);// int sendMsg(int fd, const char* msg);int sendMsg(string msg);// int recvMsg(int fd, char* msg, int size);string recvMsg();// int createSocket();// int closeSocket(int fd);private:// int readn(int fd, char* buf, int size);int readn(char* buf, int size);// int writen(int fd, const char* msg, int size);int writen(const char* msg, int size);private:int cfd;	// 通信的套接字
};

通過對客戶端的操作進行封裝,我們可以看到有如下的變化:

  1. 文件描述被隱藏了,封裝到了類的內部已經無法進行外部訪問
  2. 功能函數的參數變少了,因為類成員函數可以直接使用類內部的成員變量。
  3. 創建和銷毀套接字的函數去掉了,這兩個操作可以分別放到構造和析構函數內部進行處理。
  4. 在C++中可以適當的將char* 替換為 string 類,這樣操作字符串就更簡便一些。

2.1.2 服務器端

class TcpServer
{
public:TcpServer();~TcpServer();// int bindSocket(int lfd, unsigned short port) + int setListen(int lfd)int setListen(unsigned short port);// int acceptConn(int lfd, struct sockaddr_in *addr);int acceptConn(struct sockaddr_in *addr);// int sendMsg(int fd, const char* msg);int sendMsg(string msg);// int recvMsg(int fd, char* msg, int size);string recvMsg();// int createSocket();// int closeSocket(int fd);private:// int readn(int fd, char* buf, int size);int readn(char* buf, int size);// int writen(int fd, const char* msg, int size);int writen(const char* msg, int size);private:int lfd;	// 監聽的套接字int cfd;	// 通信的套接字
};

????????通過對服務器端的操作進行封裝,我們可以看到這個類和客戶端的類結構以及封裝思路是差不多的,并且兩個類的內部有些操作的重疊的:接收和發送通信數據的函數recvMsg()、sendMsg(),以及內部函數readn()、writen()。不僅如此服務器端的類設計成這樣樣子是有缺陷的:服務器端一般需要和多個客戶端建立連接,因此通信的套接字就需要有N個,但是在上面封裝的類里邊只有一個。

????????既然如此,我們如何解決服務器和客戶端的代碼冗余和服務器不能跟多客戶端通信的問題呢?

????????答:瘦身、減負。可以將服務器的通信功能去掉,只留下監聽并建立新連接一個功能。將客戶端類變成一個專門用于套接字通信的類即可。服務器端整個流程使用服務器類+通信類來處理;客戶端整個流程通過通信的類來處理。

2.2 版本2

????????根據對第一個版本的分析,可以對以上代碼做如下修改:

2.2.1 通信類

????????套接字通信類既可以在客戶端使用,也可以在服務器端使用,職責是接收和發送數據包。

類聲明

class TcpSocket
{
public:TcpSocket();TcpSocket(int socket);~TcpSocket();int connectToHost(string ip, unsigned short port);int sendMsg(string msg);string recvMsg();private:int readn(char* buf, int size);int writen(const char* msg, int size);private:int m_fd;	// 通信的套接字
};

類定義

TcpSocket::TcpSocket()
{m_fd = socket(AF_INET, SOCK_STREAM, 0);
}TcpSocket::TcpSocket(int socket)
{m_fd = socket;
}TcpSocket::~TcpSocket()
{if (m_fd > 0){close(m_fd);}
}int TcpSocket::connectToHost(string ip, unsigned short port)
{// 連接服務器IP portstruct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(port);inet_pton(AF_INET, ip.data(), &saddr.sin_addr.s_addr);int ret = connect(m_fd, (struct sockaddr*)&saddr, sizeof(saddr));if (ret == -1){perror("connect");return -1;}cout << "成功和服務器建立連接..." << endl;return ret;
}int TcpSocket::sendMsg(string msg)
{// 申請內存空間: 數據長度 + 包頭4字節(存儲數據長度)char* data = new char[msg.size() + 4];int bigLen = htonl(msg.size());memcpy(data, &bigLen, 4);memcpy(data + 4, msg.data(), msg.size());// 發送數據int ret = writen(data, msg.size() + 4);delete[]data;return ret;
}string TcpSocket::recvMsg()
{// 接收數據// 1. 讀數據頭int len = 0;readn((char*)&len, 4);len = ntohl(len);cout << "數據塊大小: " << len << endl;// 根據讀出的長度分配內存char* buf = new char[len + 1];int ret = readn(buf, len);if (ret != len){return string();}buf[len] = '\0';string retStr(buf);delete[]buf;return retStr;
}int TcpSocket::readn(char* buf, int size)
{int nread = 0;int left = size;char* p = buf;while (left > 0){if ((nread = read(m_fd, p, left)) > 0){p += nread;left -= nread;}else if (nread == -1){return -1;}}return size;
}int TcpSocket::writen(const char* msg, int size)
{int left = size;int nwrite = 0;const char* p = msg;while (left > 0){if ((nwrite = write(m_fd, msg, left)) > 0){p += nwrite;left -= nwrite;}else if (nwrite == -1){return -1;}}return size;
}

在第二個版本的套接字通信類中一共有兩個構造函數:

TcpSocket::TcpSocket()
{m_fd = socket(AF_INET, SOCK_STREAM, 0);
}TcpSocket::TcpSocket(int socket)
{m_fd = socket;
}
  • 其中無參構造一般在客戶端使用,通過這個套接字對象再和服務器進行連接,之后就可以通信了
  • 有參構造主要在服務器端使用,當服務器端得到了一個用于通信的套接字對象之后,就可以基于這個套接字直接通信,因此不需要再次進行連接操作。

2.2.2 服務器類

????????服務器類主要用于套接字通信的服務器端,并且沒有通信能力,當服務器和客戶端的新連接建立之后,需要通過TcpSocket類的帶參構造將通信的描述符包裝成一個通信對象,這樣就可以使用這個對象和客戶端通信了。

類聲明

class TcpServer
{
public:TcpServer();~TcpServer();int setListen(unsigned short port);TcpSocket* acceptConn(struct sockaddr_in* addr = nullptr);private:int m_fd;	// 監聽的套接字
};

類定義

TcpServer::TcpServer()
{m_fd = socket(AF_INET, SOCK_STREAM, 0);
}TcpServer::~TcpServer()
{close(m_fd);
}int TcpServer::setListen(unsigned short port)
{struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(port);saddr.sin_addr.s_addr = INADDR_ANY;  // 0 = 0.0.0.0int ret = bind(m_fd, (struct sockaddr*)&saddr, sizeof(saddr));if (ret == -1){perror("bind");return -1;}cout << "套接字綁定成功, ip: "<< inet_ntoa(saddr.sin_addr)<< ", port: " << port << endl;ret = listen(m_fd, 128);if (ret == -1){perror("listen");return -1;}cout << "設置監聽成功..." << endl;return ret;
}TcpSocket* TcpServer::acceptConn(sockaddr_in* addr)
{if (addr == NULL){return nullptr;}socklen_t addrlen = sizeof(struct sockaddr_in);int cfd = accept(m_fd, (struct sockaddr*)addr, &addrlen);if (cfd == -1){perror("accept");return nullptr;}printf("成功和客戶端建立連接...\n");return new TcpSocket(cfd);
}

????????通過調整可以發現,套接字服務器類功能更加單一了,這樣設計即解決了代碼冗余問題,還能使這兩個類更容易維護。

3. 測試代碼

3.1 客戶端

int main()
{// 1. 創建通信的套接字TcpSocket tcp;// 2. 連接服務器IP portint ret = tcp.connectToHost("192.168.237.131", 10000);if (ret == -1){return -1;}// 3. 通信int fd1 = open("english.txt", O_RDONLY);int length = 0;char tmp[100];memset(tmp, 0, sizeof(tmp));while ((length = read(fd1, tmp, sizeof(tmp))) > 0){// 發送數據tcp.sendMsg(string(tmp, length));cout << "send Msg: " << endl;cout << tmp << endl << endl << endl;memset(tmp, 0, sizeof(tmp));// 接收數據usleep(300);}sleep(10);return 0;
}

3.2 服務器端

struct SockInfo
{TcpServer* s;TcpSocket* tcp;struct sockaddr_in addr;
};void* working(void* arg)
{struct SockInfo* pinfo = static_cast<struct SockInfo*>(arg);// 連接建立成功, 打印客戶端的IP和端口信息char ip[32];printf("客戶端的IP: %s, 端口: %d\n",inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)),ntohs(pinfo->addr.sin_port));// 5. 通信while (1){printf("接收數據: .....\n");string msg = pinfo->tcp->recvMsg();if (!msg.empty()){cout << msg << endl << endl << endl;}else{break;}}delete pinfo->tcp;delete pinfo;return nullptr;
}int main()
{// 1. 創建監聽的套接字TcpServer s;// 2. 綁定本地的IP port并設置監聽s.setListen(10000);// 3. 阻塞并等待客戶端的連接while (1){SockInfo* info = new SockInfo;TcpSocket* tcp = s.acceptConn(&info->addr);if (tcp == nullptr){cout << "重試...." << endl;continue;}// 創建子線程pthread_t tid;info->s = &s;info->tcp = tcp;pthread_create(&tid, NULL, working, info);pthread_detach(tid);}return 0;
}

原文鏈接: https://subingwen.cn/linux/socket-class/

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/37070.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/37070.shtml
英文地址,請注明出處:http://en.pswp.cn/web/37070.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux基礎篇——學習Linux基本工具安裝教程視頻鏈接

本篇文章就是記錄一下學習Linux需要用到的基本工具的視頻教程鏈接&#xff0c;方便以后查看 VMware15.5安裝 安裝視頻教程&#xff1a;VMware15.5安裝教程 centos7.6安裝&#xff08;這個視頻教程真的很nice&#xff09; 視頻教程&#xff1a;centos7.6 虛擬機克隆、快照、…

ansible 模塊擴展

uri模塊 在Ansible中&#xff0c;uri模塊是一個用于發送HTTP、HTTPS、FTP等請求的模塊&#xff0c;可以用于獲取網頁內容、下載文件、上傳文件等。本質上&#xff0c;它是一個HTTP客戶端模塊。 使用uri模塊&#xff0c;需要指定一些參數來定義HTTP請求。下面是一些uri模塊的常…

學習平臺推薦_菜鳥教程官網

網址&#xff1a; 菜鳥教程 - 學的不僅是技術&#xff0c;更是夢想&#xff01;菜鳥教程(www.runoob.com)提供了編程的基礎技術教程, 介紹了HTML、CSS、Javascript、Python&#xff0c;Java&#xff0c;Ruby&#xff0c;C&#xff0c;PHP , MySQL等各種編程語言的基礎知識。 同…

Nginx-2

一、高級配置 1.1網頁狀態頁 基于nginx 模塊 ngx_http_stub_status_module 實現&#xff0c;在編譯安裝nginx的時候需要添加編譯參數 --with-http_stub_status_module&#xff0c;否則配置完成之后監測會是提示語法錯誤注意: 狀態頁顯示的是整個服務器的狀態,而非虛擬主機的狀…

opencv實現surface_matching記錄

1 說明 使用的cv版本為4.7.0 , surface_matching功能是附加在contrib中的,并未直接包含在opencv 4.7.0中,因此編譯的時候需要考慮contrib。 VS版本為2022, CMake版本為3.30-rc4. 2 編譯opencv(含contrib) 參見: Win10 下編譯 OpenCV 4.7.0詳細全過程,包含xfeatures2…

Open3D (C++) 點云邊界提取

邊界提取 一、算法原理1、詳細流程2、主要函數3、參考文獻二、代碼實現三、結果展示四、注意事項本文由CSDN點云俠原創,原文鏈接。如果你不是在點云俠的博客中看到該文章,那么此處便是不要臉的爬蟲。 一、算法原理 1、詳細流程 該算法完全復刻自PCL。 2、主要函數 /// \…

算法入門(上)

什么是算法&#xff1f; 算法&#xff08;Algorithm&#xff09;是解決特定問題求解步驟的描述&#xff0c;在計算機中表現為指令的有限序列&#xff0c;并且每條指令表示一個或多個操作。 給定一個問題&#xff0c;能夠解決這個問題的算法是有很多種的。算式中的問題是千奇百怪…

C++為什么將 0.1f 更改為 0 性能會降低 10 倍

一、浮點數與整數的表示差異 在計算機內部&#xff0c;浮點數和整數的表示方式截然不同。浮點數遵循IEEE 754標準&#xff0c;通過符號位、指數位和尾數位來存儲和表示數值&#xff0c;而整數則是直接的二進制表示。這種表示上的差異導致了它們在內存占用、處理速度以及精度上…

Debian/Ubuntu Linux安裝OBS

先決條件 建議使用 xserver-xorg 1.18.4 或更新版本&#xff0c;以避免 OBS 中某些功能&#xff08;例如全屏投影儀&#xff09;出現潛在的性能問題。在 Linux 上使用 OBS Studio 需要 OpenGL 3.3&#xff08;或更高版本&#xff09;支持。在終端中輸入以下內容來檢查系統支持…

Halcon測量助手

模糊測量:基于模糊邏輯 模糊邏輯&#xff1a;模仿人腦的不確定性概念判斷、推理思維方式&#xff0c;對于模型未知或不能確定的描述系統&#xff0c;以及強非線性、大滯后的控制對象&#xff0c;應用模糊集合和模糊規則進行推理&#xff0c;表達過渡性界限或定性知識經驗&…

MySQL基礎進階:編寫復雜查詢

編寫復雜查詢 1. 子查詢2. IN運算符3. 子查詢VS連接4. ALL關鍵字5. ANY關鍵字6. 相關子查詢7. EXISTS運算符8. SELECT子句中得子查詢9. FROM子句中得子查詢 1. 子查詢 子查詢&#xff1a; 任何一個充當另一個SQL語句的一部分的 SELECT 查詢語句都是子查詢&#xff0c;子查詢是…

GMSB文章八:微生物中介分析

歡迎大家關注全網生信學習者系列&#xff1a; WX公zhong號&#xff1a;生信學習者Xiao hong書&#xff1a;生信學習者知hu&#xff1a;生信學習者CDSN&#xff1a;生信學習者2 介紹 中介分析&#xff08;Mediation Analysis&#xff09;是一種統計方法&#xff0c;用于研究一…

C# Benchmark

創建控制臺項目&#xff08;或修改現有項目的Main方法代碼&#xff09;&#xff0c;Nget導入Benchmark0.13.12&#xff0c;創建測試類&#xff1a; public class StringBenchMark{int[] numbers;public StringBenchMark() {numbers Enumerable.Range(1, 20000).ToArray();}[Be…

大語言模型(LLMs)全面學習指南,初學者入門,一看就懂!

大語言模型&#xff08;LLMs&#xff09;作為人工智能&#xff08;AI&#xff09;領域的一項突破性發展&#xff0c;已經改變了自然語言處理&#xff08;NLP&#xff09;和機器學習&#xff08;ML&#xff09;應用的面貌。這些模型&#xff0c;包括OpenAI的GPT-4o和Google的gem…

楊冪跨界學術圈:內容營銷專家劉鑫煒帶你了解核心期刊的學術奧秘

近日&#xff0c;知名藝人楊冪在權威期刊《中國廣播電視學刊》上發表了一篇名為《淺談影視劇中演員創作習慣——以電視劇<哈爾濱一九四四>為例》的學術論文&#xff0c;此舉在學術界和娛樂圈均引起了廣泛關注。該期刊不僅享有極高的聲譽&#xff0c;還同時被北大中文核心…

數據庫-數據完整性-用戶自定義完整性實驗

NULL/NOT NULL 約束&#xff1a; 在每個字段后面可以加上 NULL 修飾符來指定該字段是否可以為空&#xff1b;或者加上 NOT NULL 修飾符來指定該字段必須填上數據。 DEFAULT約束說明 DEFAULT 約束用于向列中插入默認值。如果列中沒有規定其他的值&#xff0c;那么會將默認值添加…

發;flask的基本使用2

上一篇我們介紹了基本使用方法 flask使用 【 1 】基本使用 from flask import Flask# 1 實例化得到對象 app Flask(__name__)# 2 注冊路由--》寫視圖函數 app.route(/) def index():# 3 返回給前端字符串return hello worldif __name__ __main__:# 運行app&#xff0c;默認…

Conformal Prediction

1 A Gentle Introduction to Conformal Prediction and Distribution-Free Uncertainty Quantification 2 Language Models with Conformal Factuality Guarantees

【啟明智顯分享】樂鑫ESP32-S3R8方案2.8寸串口屏:高性能低功耗,WIFI/藍牙無線通信

近年來HMI已經成為大量應用聚焦的主題&#xff0c;在消費類產品通過創新的HMI設計帶來增強的連接性和更加身臨其境的用戶體驗之際&#xff0c;工業產品卻仍舊在采用物理接口。這些物理接口通常依賴小型顯示器或是簡單的LED&#xff0c;通過簡單的機電開關或按鈕來實現HMI交互。…

【人工智能】—葡萄牙酒店預訂信息多維度分析|預測是否取消預定算法模型大亂斗

引言 在當今數字化時代&#xff0c;數據驅動的決策在各個行業中變得越來越重要。酒店業&#xff0c;作為旅游和休閑服務的核心部分&#xff0c;正面臨前所未有的機遇和挑戰。隨著在線預訂平臺的興起&#xff0c;客戶行為數據的積累為酒店提供了洞察消費者需求和優化運營策略的…