精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于C/C++面向對象的方式封裝socket通信類

lilihe92 ? 來源: 最后一個bug ? 2023-12-26 09:57 ? 次閱讀

正文

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

1. 基于 C 語言的封裝

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

服務器端

創建用于監聽的套接字

將用于監聽的套接字和本地的 IP 以及端口進行綁定

啟動監聽

等待并接受新的客戶端連接,連接建立得到用于通信的套接字和客戶端的 IP、端口信息

使用得到的通信的套接字和客戶端通信(接收和發送數據)

通信結束,關閉套接字(監聽 + 通信)

客戶端

創建用于通信的套接字

使用服務器端綁定的 IP 和端口連接服務器

使用通信的套接字和服務器通信(發送和接收數據)

通信結束,關閉套接字(通信)

1.1 函數聲明

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

///////////////////////////////////////////////////
////////////////////服務器///////////////////////
///////////////////////////////////////////////////
intbindSocket(intlfd,unsignedshortport);
intsetListen(intlfd);
intacceptConn(intlfd,structsockaddr_in*addr);

///////////////////////////////////////////////////
////////////////////客戶端///////////////////////
///////////////////////////////////////////////////
intconnectToHost(intfd,constchar*ip,unsignedshortport);

///////////////////////////////////////////////////
/////////////////////共用////////////////////////
///////////////////////////////////////////////////
intcreateSocket();
intsendMsg(intfd,constchar*msg);
intrecvMsg(intfd,char*msg,intsize);
intcloseSocket(intfd);
intreadn(intfd,char*buf,intsize);
intwriten(intfd,constchar*msg,intsize);

關于函數 readn() 和 writen() 的作用請參考TCP數據粘包的處理

1.2 函數定義

//創建監套接字
intcreateSocket()
{
intfd=socket(AF_INET,SOCK_STREAM,0);
if(fd==-1)
{
perror("socket");
return-1;
}
printf("套接字創建成功,fd=%d
",fd);
returnfd;
}

//綁定本地的IP和端口
intbindSocket(intlfd,unsignedshortport)
{
structsockaddr_insaddr;
saddr.sin_family=AF_INET;
saddr.sin_port=htons(port);
saddr.sin_addr.s_addr=INADDR_ANY;//0=0.0.0.0
intret=bind(lfd,(structsockaddr*)&saddr,sizeof(saddr));
if(ret==-1)
{
perror("bind");
return-1;
}
printf("套接字綁定成功,ip:%s,port:%d
",
inet_ntoa(saddr.sin_addr),port);
returnret;
}

//設置監聽
intsetListen(intlfd)
{
intret=listen(lfd,128);
if(ret==-1)
{
perror("listen");
return-1;
}
printf("設置監聽成功...
");
returnret;
}

//阻塞并等待客戶端的連接
intacceptConn(intlfd,structsockaddr_in*addr)
{
intcfd=-1;
if(addr==NULL)
{
cfd=accept(lfd,NULL,NULL);
}
else
{
intaddrlen=sizeof(structsockaddr_in);
cfd=accept(lfd,(structsockaddr*)addr,&addrlen);
}
if(cfd==-1)
{
perror("accept");
return-1;
}
printf("成功和客戶端建立連接...
");
returncfd;
}

//接收數據
intrecvMsg(intcfd,char**msg)
{
if(msg==NULL||cfd<=?0)
????{
????????return?-1;
????}
????//?接收數據
????//?1.?讀數據頭
????int?len?=?0;
????readn(cfd,?(char*)&len,?4);
????len?=?ntohl(len);
????printf("數據塊大小:?%d
",?len);

????//?根據讀出的長度分配內存
????char?*buf?=?(char*)malloc(len+1);
????int?ret?=?readn(cfd,?buf,?len);
????if(ret?!=?len)
????{
????????return?-1;
????}
????buf[len]?=?'?';
????*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?port
????struct?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("成功和服務器建立連接...
");
????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;
}
elseif(nread==-1)
{
return-1;
}
}
returnsize;
}

//發送指定的字節數
//函數調用成功返回size
intwriten(intfd,constchar*msg,intsize)
{
intleft=size;
intnwrite=0;
constchar*p=msg;

while(left>0)
{
if((nwrite=write(fd,msg,left))>0)
{
p+=nwrite;
left-=nwrite;
}
elseif(nwrite==-1)
{
return-1;
}
}
returnsize;
}

2. 基于 C++ 的封裝

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

2.1 版本 1

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

2.1.1 客戶端

classTcpClient
{
public:
TcpClient();
~TcpClient();
//intconnectToHost(intfd,constchar*ip,unsignedshortport);
intconnectToHost(stringip,unsignedshortport);

//intsendMsg(intfd,constchar*msg);
intsendMsg(stringmsg);
//intrecvMsg(intfd,char*msg,intsize);
stringrecvMsg();

//intcreateSocket();
//intcloseSocket(intfd);

private:
//intreadn(intfd,char*buf,intsize);
intreadn(char*buf,intsize);
//intwriten(intfd,constchar*msg,intsize);
intwriten(constchar*msg,intsize);

private:
intcfd;//通信的套接字
};

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

文件描述被隱藏了,封裝到了類的內部已經無法進行外部訪問

功能函數的參數變少了,因為類成員函數可以直接使用類內部的成員變量。

創建和銷毀套接字的函數去掉了,這兩個操作可以分別放到構造和析構函數內部進行處理。

在 C++ 中可以適當的將 char* 替換為 string 類,這樣操作字符串就更簡便一些。

2.1.2 服務器端

classTcpServer
{
public:
TcpServer();
~TcpServer();

//intbindSocket(intlfd,unsignedshortport)+intsetListen(intlfd)
intsetListen(unsignedshortport);
//intacceptConn(intlfd,structsockaddr_in*addr);
intacceptConn(structsockaddr_in*addr);

//intsendMsg(intfd,constchar*msg);
intsendMsg(stringmsg);
//intrecvMsg(intfd,char*msg,intsize);
stringrecvMsg();

//intcreateSocket();
//intcloseSocket(intfd);

private:
//intreadn(intfd,char*buf,intsize);
intreadn(char*buf,intsize);
//intwriten(intfd,constchar*msg,intsize);
intwriten(constchar*msg,intsize);

private:
intlfd;//監聽的套接字
intcfd;//通信的套接字
};

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

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

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

2.2 版本 2

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

2.2.1 通信類

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

類聲明

classTcpSocket
{
public:
TcpSocket();
TcpSocket(intsocket);
~TcpSocket();
intconnectToHost(stringip,unsignedshortport);
intsendMsg(stringmsg);
stringrecvMsg();

private:
intreadn(char*buf,intsize);
intwriten(constchar*msg,intsize);

private:
intm_fd;//通信的套接字
};

類定義

TcpSocket::TcpSocket()
{
m_fd=socket(AF_INET,SOCK_STREAM,0);
}

TcpSocket::TcpSocket(intsocket)
{
m_fd=socket;
}

TcpSocket::~TcpSocket()
{
if(m_fd>0)
{
close(m_fd);
}
}

intTcpSocket::connectToHost(stringip,unsignedshortport)
{
//連接服務器IPport
structsockaddr_insaddr;
saddr.sin_family=AF_INET;
saddr.sin_port=htons(port);
inet_pton(AF_INET,ip.data(),&saddr.sin_addr.s_addr);
intret=connect(m_fd,(structsockaddr*)&saddr,sizeof(saddr));
if(ret==-1)
{
perror("connect");
return-1;
}
cout<0)
{
if((nread=read(m_fd,p,left))>0)
{
p+=nread;
left-=nread;
}
elseif(nread==-1)
{
return-1;
}
}
returnsize;
}

intTcpSocket::writen(constchar*msg,intsize)
{
intleft=size;
intnwrite=0;
constchar*p=msg;

while(left>0)
{
if((nwrite=write(m_fd,msg,left))>0)
{
p+=nwrite;
left-=nwrite;
}
elseif(nwrite==-1)
{
return-1;
}
}
returnsize;
}

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

TcpSocket::TcpSocket()
{
m_fd=socket(AF_INET,SOCK_STREAM,0);
}

TcpSocket::TcpSocket(intsocket)
{
m_fd=socket;
}

其中無參構造一般在客戶端使用,通過這個套接字對象再和服務器進行連接,之后就可以通信了

有參構造主要在服務器端使用,當服務器端得到了一個用于通信的套接字對象之后,就可以基于這個套接字直接通信,因此不需要再次進行連接操作。

2.2.2 服務器類

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

類聲明

classTcpServer
{
public:
TcpServer();
~TcpServer();
intsetListen(unsignedshortport);
TcpSocket*acceptConn(structsockaddr_in*addr=nullptr);

private:
intm_fd;//監聽的套接字
};

類定義

TcpServer::TcpServer()
{
m_fd=socket(AF_INET,SOCK_STREAM,0);
}

TcpServer::~TcpServer()
{
close(m_fd);
}

intTcpServer::setListen(unsignedshortport)
{
structsockaddr_insaddr;
saddr.sin_family=AF_INET;
saddr.sin_port=htons(port);
saddr.sin_addr.s_addr=INADDR_ANY;//0=0.0.0.0
intret=bind(m_fd,(structsockaddr*)&saddr,sizeof(saddr));
if(ret==-1)
{
perror("bind");
return-1;
}
cout<

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

3. 測試代碼

3.1 客戶端

intmain()
{
//1.創建通信的套接字
TcpSockettcp;

//2.連接服務器IPport
intret=tcp.connectToHost("192.168.237.131",10000);
if(ret==-1)
{
return-1;
}

//3.通信
intfd1=open("english.txt",O_RDONLY);
intlength=0;
chartmp[100];
memset(tmp,0,sizeof(tmp));
while((length=read(fd1,tmp,sizeof(tmp)))>0)
{
//發送數據
tcp.sendMsg(string(tmp,length));

cout<

3.2 服務器端

structSockInfo
{
TcpServer*s;
TcpSocket*tcp;
structsockaddr_inaddr;
};

void*working(void*arg)
{
structSockInfo*pinfo=static_cast(arg);
//連接建立成功,打印客戶端的IP和端口信息
charip[32];
printf("客戶端的IP:%s,端口:%d
",
inet_ntop(AF_INET,&pinfo->addr.sin_addr.s_addr,ip,sizeof(ip)),
ntohs(pinfo->addr.sin_port));

//5.通信
while(1)
{
printf("接收數據:.....
");
stringmsg=pinfo->tcp->recvMsg();
if(!msg.empty())
{
cout<tcp;
deletepinfo;
returnnullptr;
}

intmain()
{
//1.創建監聽的套接字
TcpServers;
//2.綁定本地的IPport并設置監聽
s.setListen(10000);
//3.阻塞并等待客戶端的連接
while(1)
{
SockInfo*info=newSockInfo;
TcpSocket*tcp=s.acceptConn(&info->addr);
if(tcp==nullptr)
{
cout<s=&s;
info->tcp=tcp;

pthread_create(&tid,NULL,working,info);
pthread_detach(tid);
}

return0;
}

審核編輯:湯梓紅
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 封裝
    +關注

    關注

    125

    文章

    7592

    瀏覽量

    142138
  • C語言
    +關注

    關注

    180

    文章

    7575

    瀏覽量

    134024
  • C++
    C++
    +關注

    關注

    21

    文章

    2085

    瀏覽量

    73301
  • 面向對象
    +關注

    關注

    0

    文章

    64

    瀏覽量

    9961
收藏 人收藏

    評論

    相關推薦

    C語言實現面向對象方式 C++中的class的運行原理

    這里主要介紹下在C語言中是如何實現的面向對象。知道了C語言實現面向對象
    發表于 10-21 09:00 ?1033次閱讀

    基于C/C++面向對象方式封裝socket通信流程簡析

    在掌握了基于 TCP 的套接字通信流程之后,為了方便使用,提高編碼效率,可以對通信操作進行封裝,本著有淺入深的原則,先基于 C 語言進行面向
    的頭像 發表于 12-26 10:00 ?1455次閱讀

    C++筆記004:C++通俗點說—— C結構體復習

    往往是兩個字“對象”!因為C語言是面向過程的,而C++特色是面向對象!所以大多數書籍最開始就在說
    發表于 03-05 12:53

    STM32 C++代碼封裝初探相關資料推薦

    、抽象化。C++是一種天然支持面向對象編程的語言,在C語言的基礎上,C++不僅提供了class關鍵字和
    發表于 02-11 06:05

    C++ 面向對象多線程編程下載

    C++ 面向對象多線程編程下載
    發表于 04-08 02:14 ?70次下載

    面向對象程序設計—C++語言描述_PDF版

    電子發燒友網站提供《面向對象程序設計—C++語言描述_PDF版.txt》資料免費下載
    發表于 09-24 15:44 ?0次下載

    面向對象的程序設計(C++

    面向對象的程序設計(C++).面向對象的基本思想 C++
    發表于 03-22 14:40 ?0次下載

    C++實驗:對象——普通數學題計算

    C++實驗 對象——普通數學題計算
    發表于 12-30 14:50 ?0次下載

    C++課程資料詳細資料合集包括了:面向對象程序設計與C++,算法,函數等

    本文檔的主要內容詳細介紹的是C++課程資料資料合集包括了:面向對象程序設計與C++,算法,函數,概述, C++語言基礎,構造數據類型,數據類
    發表于 07-09 08:00 ?18次下載
    <b class='flag-5'>C++</b>課程資料詳細資料合集包括了:<b class='flag-5'>面向</b><b class='flag-5'>對象</b>程序設計與<b class='flag-5'>C++</b>,算法,函數等

    C++語言和面向對象程序設計教程

    C++語言和面向對象程序設計代表了旨在使計算機問題解更加符合人的思維活動,是軟件開發方法的一場革命;面向對象建模和
    發表于 03-02 08:00 ?6次下載

    STM32 C++編程系列二:STM32 C++代碼封裝初探

    、抽象化。C++是一種天然支持面向對象編程的語言,在C語言的基礎上,C++不僅提供了class關鍵字和
    發表于 12-08 11:06 ?13次下載
    STM32 <b class='flag-5'>C++</b>編程系列二:STM32 <b class='flag-5'>C++</b>代碼<b class='flag-5'>封裝</b>初探

    C語言是怎么面向對象編程

    在嵌入式開發中,C/C++語言是使用最普及的,在C++11版本之前,它們的語法是比較相似的,只不過C++提供了面向
    的頭像 發表于 02-14 13:57 ?1503次閱讀
    <b class='flag-5'>C</b>語言是怎么<b class='flag-5'>面向</b><b class='flag-5'>對象</b>編程

    C/C++面向對象編程思想1

    C++作為一門在C和Java之間的語言,其既可以使用C語言中的高效指針,又繼承了Java中的面向對象編程思想,在去年編程語言排行榜上更是首次
    的頭像 發表于 03-30 15:14 ?554次閱讀
    <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>之<b class='flag-5'>面向</b><b class='flag-5'>對象</b>編程思想1

    C/C++面向對象編程思想2

    C++作為一門在C和Java之間的語言,其既可以使用C語言中的高效指針,又繼承了Java中的面向對象編程思想,在去年編程語言排行榜上更是首次
    的頭像 發表于 03-30 15:14 ?501次閱讀
    <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>之<b class='flag-5'>面向</b><b class='flag-5'>對象</b>編程思想2

    C/C++面向對象編程思想3

    C++作為一門在C和Java之間的語言,其既可以使用C語言中的高效指針,又繼承了Java中的面向對象編程思想,在去年編程語言排行榜上更是首次
    的頭像 發表于 03-30 15:16 ?475次閱讀
    <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>之<b class='flag-5'>面向</b><b class='flag-5'>對象</b>編程思想3