Socket基本操作的C++封装--以及Socket通信实践
Posted C_YCBX Py_YYDS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Socket基本操作的C++封装--以及Socket通信实践相关的知识,希望对你有一定的参考价值。
文章目录
接口类的实现(抽象类)
_public_socket.h
该头文件用于包含所有该系统平台socket所需要依赖的库。
- windows平台
#ifndef MY_TINY_STL__PUBLIC_SOCKET_H
#define MY_TINY_STL__PUBLIC_SOCKET_H
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll
#endif //MY_TINY_STL__PUBLIC_SOCKET_H
- Linux平台
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
TCP_INTERFACE.h(作用于win平台)
由于该接口由服务器端和客户端继承,而两者同样的函数成员也就是这些了,设计客户端和服务器端时就只需要考虑各自的套接字以及其余操作的成员函数,也不需要管理DLL的开关。
//
// Created by Alone on 2021/8/17.
//
#ifndef MY_TINY_STL_TCP_INTERFACE_H
#define MY_TINY_STL_TCP_INTERFACE_H
#include "_public_socket.h"
class TCP_INTERFACE {
public:
TCP_INTERFACE(){
//初始化 DLL
WSADATA wsaData;
WSAStartup( MAKEWORD(2, 2), &wsaData);
}
//返回值小于等于0时发生错误
virtual int Send(const void*buf,const int buflen) = 0;
virtual int Recv(void*buf,const int buflen) = 0;
//closesocket返回值不为0则发生错误
virtual bool Close() = 0;
~TCP_INTERFACE(){
WSACleanup();
}
};
#endif //MY_TINY_STL_TCP_INTERFACE_H
服务器端封装
TCP_SOCKET_SERVER.h
//
// Created by Alone on 2021/8/16.
//
#ifndef MY_TINY_STL_TCP_SOCKET_SERVER_H
#define MY_TINY_STL_TCP_SOCKET_SERVER_H
#include "TCP_INTERFACE.h"
class TCP_SOCKET_SERVER: public TCP_INTERFACE{
public:
TCP_SOCKET_SERVER();
~TCP_SOCKET_SERVER();
bool Bind(int port);
bool Listen();
bool Accept();
int Send(const void*buf,const int buflen);
int Recv(void* buf,const int buflen);
bool Close();
private:
//绑定本地端口的套接字servSock以及用关于构建通信对象的clntSock
//建立服务后的serve一般不会进行改变,所以close只提供为clntSock关闭
SOCKET servSock;
SOCKET clntSock;
sockaddr_in sockAddr;
};
#endif //MY_TINY_STL_TCP_SOCKET_SERVER_H
TCP_SOCKET_SERVER.cpp
//
// Created by Alone on 2021/8/16.
//
#include "TCP_SOCKET_SERVER.h"
//初始化操作两个socket为0表示还未分配套接字
TCP_SOCKET_SERVER::TCP_SOCKET_SERVER(): servSock(0), clntSock(0) {
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
}
//析构函数所有关闭socket
TCP_SOCKET_SERVER::~TCP_SOCKET_SERVER() {
if(clntSock!=0)closesocket(clntSock);
if(servSock!=0)closesocket(servSock);
}
//绑定操作
bool TCP_SOCKET_SERVER::Bind(int port) {
servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
sockAddr.sin_family = PF_INET; //使用IPv4地址
//INADDR_ANY代表本地0.0.0.0地址的整形数据
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); //具体的IP地址
sockAddr.sin_port = htons(port); //端口
if(bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR))!=0){
closesocket(servSock);
servSock = 0;
return false;
}
return true;
}
//置于监听状态
bool TCP_SOCKET_SERVER::Listen() {
if(servSock==0)
return false;
if(listen(servSock,20)!=0){
closesocket(servSock);
servSock = 0;
return false;
}
return true;
}
//利用套接字的监听串口,接收客户端的请求,建立新的套接字进行存储信息
bool TCP_SOCKET_SERVER::Accept() {
SOCKADDR t;int nSize = sizeof(SOCKADDR);
//后面两个参数为可选(可以填入0,0)
clntSock = accept(servSock,&t,&nSize);
if(clntSock<=0){clntSock=0; return false};
return true;
}
//返回的是发送到缓冲区的字节长度,小于等于0时表示出错
int TCP_SOCKET_SERVER::Send(const void *buf, const int buflen) {
return send(clntSock,(const char*)buf,buflen,0);
}
//返回已经接收的字节长度,同上
int TCP_SOCKET_SERVER::Recv(void *buf, const int buflen) {
return recv(clntSock,(char*)buf,buflen,0);
}
//closesocket返回值不等于0则出错
bool TCP_SOCKET_SERVER::Close() {
if(clntSock==0)
return false;
if( closesocket(clntSock)!=0){
return false;
}clntSock = 0;
return true;
}
客户端的封装
TCP_SOCKET_CLIENT.h
增加了利用域名查询ip地址的成员函数gethostbyname(),挺好玩的!
//
// Created by Alone on 2021/8/17.
//
#ifndef PRACTICE_TCP_SOCKET_CLIENT_H
#define PRACTICE_TCP_SOCKET_CLIENT_H
#include "TCP_INTERFACE.h"
#include <iostream>
class TCP_SOCKET_CLIENT: public TCP_INTERFACE {
public:
TCP_SOCKET_CLIENT();
~TCP_SOCKET_CLIENT();
bool Connect(const char* IPAdrr,u_short port);
//用于利用URL(域名)查询IP地址
void Gethostbyname(const char*URL);
//接口必须实现的函数
int Send(const void* buf,const int bufSize);
int Recv(void* buf,const int bufSize);
bool Close();
private:
//由于一般客户端只需要一个套接字实现连接,然后还需要一个socketadrr_in连接内容的赋值
SOCKET clntSock;
sockaddr_in sockAddr;
};
#endif //PRACTICE_TCP_SOCKET_CLIENT_H
TCP_SOCKET_CLIENT.cpp
//
// Created by Alone on 2021/8/17.
//
#include "TCP_SOCKET_CLIENT.h"
//初始化
TCP_SOCKET_CLIENT::TCP_SOCKET_CLIENT():clntSock(0) {
memset(&sockAddr,0,sizeof sockAddr);
}
//关闭套接字操作
bool TCP_SOCKET_CLIENT::Close() {
if(clntSock==0)
return false;
if(closesocket(clntSock)!=0)
return false;
clntSock = 0;
return true;
}
//连接服务器操作
bool TCP_SOCKET_CLIENT::Connect(const char *IPAdrr, u_short port) {
clntSock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr(IPAdrr);
sockAddr.sin_port = htons(port);
if(connect(clntSock,(SOCKADDR*)&sockAddr,sizeof(sockAddr))!=0){
closesocket(clntSock);
clntSock = 0;
return false;
}
return true;
}
//发送信息操作
int TCP_SOCKET_CLIENT::Send(const void *buf, const int bufSize) {
return send(clntSock,(const char*)buf,bufSize,0);
}
//接收信息操作
int TCP_SOCKET_CLIENT::Recv(void *buf, const int bufSize) {
return recv(clntSock,(char*)buf,bufSize,0);
}
//根据域名获取ip地址等信息
void TCP_SOCKET_CLIENT::Gethostbyname(const char* URL) {
hostent *host = gethostbyname(URL);
if(!host){
std::cout<<"Get IP address error!\\n";
return;
}
//打印本命
std::cout<<URL<<std::endl;
//别名
for(int i=0; host->h_aliases[i]; i++){
printf("Aliases %d: %s\\n", i+1, host->h_aliases[i]);
}
//地址类型
printf("Address type: %s\\n", (host->h_addrtype==AF_INET) ? "AF_INET": "AF_INET6");
//IP地址,其中inet_ntoa()函数是将网络字节序转为本地的字节序,方便打印看懂
for(int i=0; host->h_addr_list[i]; i++){
printf("IP addr %d: %s\\n", i+1, inet_ntoa( *(struct in_addr*)host->h_addr_list[i] ) );
}
}
//析构时需要确保所有东西已经关闭
TCP_SOCKET_CLIENT::~TCP_SOCKET_CLIENT() {
if(clntSock!=0)
closesocket(clntSock);
}
实例一:回声程序通信
服务器回声程序
绑定本地1234端口,进入监听状态等待请求,如果通信对象关闭了通信,也不慌,重新goto到等待请求得到新的通信套接字
#include <iostream>
#include "TCP_SOCKET_SERVER.h"
#define BUF_SIZE 1000
using namespace std;
int main() {
TCP_SOCKET_SERVER a;
a.Bind(1234);
a.Listen();
restart:
a.Accept();
while (1) {
char *x = new char[BUF_SIZE];
memset(x, 0, BUF_SIZE);
int size = a.Recv(x, BUF_SIZE);
if (size <= 0)
break;
if (a.Send(x, size) <= 0)
break;
}
cout << "connect is over.Waiting for a new connection!\\n";
goto restart;
}
客户端通信程序
为保持持续通信,一旦客户端拒绝了请求,那么弹出循环重新连接,并设置连接超时操作。
#include "TCP_SOCKET_CLIENT.h"
#define BUF_SIZE 100
int main(){
TCP_SOCKET_CLIENT t;
const char* to = "127.0.0.1";
restart:
if(!t.Connect(to,1234)){
std::cout<<"connected timeout!";
return 0;
}
while(1){
std::cout<<"\\nInput your message:\\n";
char buf[BUF_SIZE] = {0};
std::cin.getline(buf,99);
int size = t.Send(buf,BUF_SIZE);
if(size<=0)
break;
memset(buf,0,sizeof buf);
if(t.Recv(buf,size)<=0)
break;
printf("received from %s is:\\n",to);
std::cout<<buf;
}
std::cout<<"The Server is disconnected,and socket has been cleaned up,socket connection has been re-established\\n";
goto restart;
return 0;
}
回声效果
客户端收到的结果
服务器端一直运行着,只要不关闭,但每次只能和一个客户端进行通信,通信完后重新等待连接。
实例二:文件操作,传送图片(掌握重复传包)
分析待传图片
看看这百万大小的字节,一次肯定是传不完的,所以我们需要发送端不断的续传,直到传送完毕。
发送端程序
#include "TCP_SOCKET_CLIENT.h"
#include <fstream>
int main(){
TCP_SOCKET_CLIENT t;
const char* to = "127.0.0.1";
restart:
if(!t.Connect(to,1234)){
std::cout<<"connected timeout!";
return 0;
}
//图片写入buf(这几百万字节大小,得亏是new动态分配
std::ifstream img("D:/DesktopBackground/L-69.png",std::ios::in|std::ios::binary);
//设置文件指针用于求文件内容长度
img.seekg(0,std::ios::end);
int len = img.tellg();
img.seekg(0,std::ios::beg);
if(len>0){printf("read OK\\n");}
else {printf("file is empty!");return 0;}
//填补buf
char * buf = new char[len];
img.read(buf,len);
//发送数据到服务器,一次肯定发送不完,所以多次
int sum = 0;
while(sum<len){
int sendlen = t.Send(buf,len);
if(sendlen<=0){
printf("Send Erro!");
return 0;
}
sum += sendlen;
}
printf("Send OK!");
return 0;
}
接收端程序
#include <iostream>
#include "TCP_SOCKET_SERVER.h"
#include <fstream>
#define BUF_SIZE 100
using namespace std;
python之路-socke开发