封装Socket编程接口
一、Socket编程接口与TCP/UDP的关系
Socket是网路通信接口,介于传输层和应用层之间,其封装了传输层的TCP/UDP协议以及网络层的IP协议,允许开发者通过调用编程接口选择使用TCP或UDP协议来实现不同的通信需求。
TCP协议特点:
- 面向连接:通过三次握手建立连接(第一次握手:客户端调用connect函数向服务端申请建立连接;第二次握手:服务端处于监听状态,接收客户端的连接;第三次握手:客户端连接服务端成功,connect函数返回成功),这种机制保证了通信双方状态同步,但是增加了延迟和资源开销
- 可靠传输:保证数据传输的可靠性和顺序性,如果数据丢失,发送方会重新发送,如果数据顺序打乱,会按数据序列号重新排序
- 面向字节流:将数据当作连续的字节流,没有明确的数据边界,多个数据包可能被合并或者拆分,需要自定义数据边界,解决粘包问题
- 流量与拥塞控制:通过滑动窗口机制动态调整发送速率,避免接收方缓冲区溢出;采用拥塞控制算法(如慢启动、快速重传)适应网络状况。例如,网络拥塞时自动降低发送速率
- 适用场景:网页浏览、文件传输
UDP协议特点:
- 无连接:直接发送数据包,无需建立连接(类似于发短信,无需确认对方是否在线)
- 不可靠传输:不保证数据传输的可靠性,数据可能丢失、重复或乱序,且不进行重传。例如视频通话时偶尔丢帧不影响体验,如果数据重传会导致卡顿
- 面向数据报:每个数据包(数据报)都有固定报头,独立传输,不会合并和拆分
- 无流量与拥塞控制:发送方以恒定速率发送数据,可能加剧网络拥塞或导致丢包。例如,直播中网络波动时,UDP不会降低码率,可能造成画面模糊
- 适用场景:实时视频,在线游戏
二、封装Socket编程接口
利用模板方法模式封装Socket编程TCP协议的接口(UDP协议接口尚未实现)
Gitee链接:encapsulated_socket · 周不才/cpp_linux study - 码云 - 开源中国
源代码:
Socket.hpp(Log.hpp和InetAddr.hpp见码云)
// 封装socket编程接口
#pragma once
#include <iostream>
#include <string>
#include <memory>//提供智能指针
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp" //日志文件
#include "InetAddr.hpp"//IP地址:点分十进制和in_addr格式转换
namespace socket_ns
{
using namespace log_ns;
static const int gbacklog=8;//监听队列最大数量
class Socket;
// 自定义错误码
enum
{
SOCKET_ERROR = 1,
BIND_ERROR,
LISTENERROR
};
// 抽象类,父类
class Socket
{
public:
// 创建套接字
virtual void CreateSocket()=0;
//套接字绑定地址
virtual void BindSocket(uint16_t port)=0;
//服务器设置为监听模式
virtual void BeginListen(int backlog=gbacklog)=0;
//接收客户端连接
virtual std::shared_ptr<Socket> Accept(InetAddr* clientaddr)=0;
//客户端请求连接
virtual bool Connect(const std::string& ip, uint16_t port)=0;
//关闭套接字
virtual void CloseSocket()=0;
//获取套接字文件描述符
virtual int GetSockfd()=0;
//发送数据
virtual ssize_t Send(const std::string& in)=0;
//接收数据
virtual ssize_t Receive(std::string* out)=0;
public:
//服务端开始监听
void StartListening(uint16_t port,int backlog=gbacklog)
{
//参数说明
//port是端口号
CreateSocket();//创建套接字
BindSocket(port);//套接字绑定地址
BeginListen(backlog);//服务端设置为监听状态
}
//客户端开始连接
bool StartConnecting(const std::string& ip, uint16_t port)
{
//参数说明
//ip和port是请求连接服务端的ip地址和端口号
CreateSocket();//创建套接字
return Connect(ip,port);//客户端请求连接服务端
}
};
// TCP子类
class TcpSocket : public Socket
{
private:
int _sockfd; // 套接字文件描述符
public:
//无参构造函数
TcpSocket()
{}
//有参构造函数
TcpSocket(int sockfd)
:_sockfd(sockfd)
{}
public:
//创建套接字
virtual void CreateSocket() override
{
_sockfd = socket(AF_INET, SOCK_STREAM, 0); // IPv4协议,TCP协议
if (_sockfd < 0)
{
LOG(FATAL,"socket create error\n");
exit(SOCKET_ERROR);
}
LOG(INFO,"socket create success\n");
}
//绑定套接字
virtual void BindSocket(uint16_t port) override
{
//参数说明
//port是端口号,绑定套接字需要端口号
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(_sockfd,(struct sockaddr*)&local,sizeof(local))<0)
{
LOG(FATAL,"socket bind error\n");
exit(BIND_ERROR);
}
LOG(INFO,"socket bind success\n");
}
//服务器设置为监听模式
virtual void BeginListen(int backlog=gbacklog) override
{
//参数说明
//backlog是监听队列最大数量,默认为gbacklog
if(listen(_sockfd,backlog)<0)
{
LOG(FATAL,"listen error\n");
exit(LISTENERROR);
}
LOG(INFO,"listen success\n");
}
//接收客户端连接
virtual std::shared_ptr<Socket> Accept(InetAddr* clientaddr) override
{
//参数说明
//clientaddr是输出型参数,用于带出连接的客户端的地址信息
struct sockaddr_in client;
memset(&client,0,sizeof(client));
socklen_t len=sizeof(client);
int sockfd=accept(_sockfd,(struct sockaddr*)&client,&len);
if(sockfd<0)
{
LOG(WARNING,"accept error\n");
return nullptr;
}
*clientaddr=InetAddr(client);
LOG(INFO,"get a new link, ip: %s, sockfd: %d\n",clientaddr->AddrStr().c_str(),sockfd);
return std::make_shared<TcpSocket>(sockfd);//用连接成功后获取到的监听套接字创建子类TcpSokcet的对象并使用智能指针管理
}
//客户端请求连接
virtual bool Connect(const std::string& ip, uint16_t port) override
{
//参数说明
//ip和port是请求连接的服务端的ip地址和端口号
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(port);
inet_pton(AF_INET,ip.c_str(),&server.sin_addr.s_addr);
if(connect(_sockfd,(struct sockaddr*)&server,sizeof(server))<0)
return false;
else
return true;
}
//关闭套接字
virtual void CloseSocket() override
{
if(_sockfd>0)
close(_sockfd);
}
//获取套接字文件描述符
virtual int GetSockfd() override
{
return _sockfd;
}
//发送数据
virtual ssize_t Send(const std::string& in) override
{
//参数说明
//in是要发送的数据
return write(_sockfd,in.c_str(),in.size());
}
//接收数据
virtual ssize_t Receive(std::string* out) override
{
//参数说明
//out是输出型参数,用于存储读取到的数据
char buffer[1024];//数据缓冲区,存储读取到的数据
ssize_t n=read(_sockfd,buffer,sizeof(buffer));
if(n>0)
{
buffer[n]=0;//数据缓冲区末尾设置为0,防止读取到残留数据
*out+=buffer;
}
return n;
}
};
}