该模块就是对我们基本的套接字操作进行一个封装,方便我们在使用的时候直接调用,减少重复的编程工作!
1、成员变量设计
成员变量很简单,因为涉及到对套接字的操作,所以只需要一个文件描述符 _sockfd 即可!
2、成员函数设计
成员函数比较简单,无非就是对套接字操作的封装,如下所示:
const static int MAXBACKLOG = 1024;
class Socket
{
private:
int _sockfd;
public:
Socket();
Socket(int sockfd);
~Socket();
// 1. 创建套接字
bool Create();
// 2. 绑定套接字信息
bool Bind(const std::string& ip, uint16_t port);
// 3. 监听套接字
bool Listen(int backlog = MAXBACKLOG);
// 4. 获取新连接
int Accept();
// 5. 客户端发起请求
bool Connect(const std::string& ip, uint16_t port);
// 6. 接收数据
ssize_t Send(void* buffer, size_t size, int flag = 0);
// 7. 发送数据
ssize_t Recv(void* buffer, size_t size, int flag = 0);
// 8. 关闭套接字
void Close();
// 9. 创建一个服务端链接的接口
bool create_server(uint16_t port, const std::string& ip = "0.0.0.0", bool isBlock = false)
// 10. 创建一个客户端连接的接口
bool create_client(uint16_t port, const std::string& ip);
// 11. 设置套接字选项 -- 开启地址端口复用
void reuse_addr();
// 12. 设置套接字阻塞属性 -- 设置为非阻塞
void set_nonblock();
};3、完整的代码实现
就是对套接字操作的简单封装,并不难!
const static int MAXBACKLOG = 1024;
class Socket
{
private:
int _sockfd;
public:
Socket() : _sockfd(-1) {}
Socket(int sockfd) : _sockfd(sockfd) {}
~Socket() { Close(); };
// 1. 创建套接字
bool Create()
{
_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // IPPROTO_TCP指定的是TCP协议
if(_sockfd < 0)
{
ELOG("create socket error!!");
return false;
}
return true;
}
// 2. 绑定套接字信息
bool Bind(const std::string& ip, uint16_t port)
{
// 填充信息
struct sockaddr_in local;
memset(&local, 0, sizeof(struct sockaddr_in));
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip.c_str());
// 绑定信息
int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(struct sockaddr_in));
if(n < 0)
{
ELOG("bind error!!");
return false;
}
return true;
}
// 3. 监听套接字
bool Listen(int backlog = MAXBACKLOG)
{
int n = listen(_sockfd, backlog);
if(n < 0)
{
ELOG("listen error!!");
return false;
}
return true;
}
// 4. 获取新连接
int Accept()
{
// 这里不关心获取到的新连接的属性
int newfd = accept(_sockfd, nullptr, nullptr);
if(newfd < 0)
{
ELOG("accept error!!");
return -1;
}
return newfd;
}
// 5. 客户端发起请求
bool Connect(const std::string& ip, uint16_t port)
{
// 填充信息
struct sockaddr_in client;
memset(&client, 0, sizeof client);
client.sin_family = AF_INET;
client.sin_port = htons(port);
client.sin_addr.s_addr = inet_addr(ip.c_str());
// 发起连接
int n = connect(_sockfd, (struct sockaddr*)&client, sizeof(struct sockaddr_in));
if(n < 0)
{
ELOG("connect server error!!");
return false;
}
return true;
}
// 6. 接收数据
ssize_t Send(const void* buffer, size_t size, int flag = 0)
{
ssize_t n = send(_sockfd, buffer, size, flag);
if(n <= 0)
{
// EAGAIN 表示当前socket的发送缓冲区满了,在非阻塞的情况下才会有这个错误
// EINTR 表示当前socket的阻塞等待,被信号打断了
if(errno == EAGAIN || errno == EINTR)
return 0;
ELOG("send error");
return -1;
}
return n; // 返回实际发送的数据长度
}
// 非等待式发送数据
ssize_t send_with_noblock(const void* buffer, size_t size)
{
if(size == 0)
return 0;
return Send(buffer, size, MSG_DONTWAIT); // MSG_DONTWAIT 表示当前接收为非阻塞
}
// 7. 发送数据
ssize_t Recv(void* buffer, size_t size, int flag = 0)
{
ssize_t n = recv(_sockfd, buffer, size, flag);
if(n <= 0)
{
// EAGAIN 表示当前socket的接收缓冲区中没有数据了,在非阻塞的情况下才会有这个错误
// EINTR 表示当前socket的阻塞等待,被信号打断了
if(errno == EAGAIN || errno == EINTR)
return 0;
ELOG("recv error");
return -1;
}
return n; // 返回实际发送的数据长度
}
// 非等待式接收数据
ssize_t recv_with_noblock(void* buffer, size_t size)
{
return Recv(buffer, size, MSG_DONTWAIT); // MSG_DONTWAIT 表示当前接收为非阻塞
}
// 8. 关闭套接字
void Close()
{
if(_sockfd != -1)
{
close(_sockfd);
_sockfd = -1;
}
}
// 9. 创建一个服务端链接的接口
bool create_server(uint16_t port, const std::string& ip = "0.0.0.0", bool isNonBlock = false)
{
// 1. 创建套接字 2. 绑定地址 3. 开始监听 4. 设置非阻塞 5. 启动地址重用
if(Create() == false)
return false;
if(Bind(ip, port) == false)
return false;
if(Listen() == false)
return false;
if(isNonBlock)
set_nonblock();
reuse_addr();
return true;
}
// 10. 创建一个客户端连接的接口
bool create_client(uint16_t port, const std::string& ip)
{
// 1. 创建套接字 2. 发起连接
if(Create() == false)
return false;
if(Connect(ip, port) == false)
return false;
return true;
}
// 11. 设置套接字选项 -- 开启地址端口复用
void reuse_addr()
{
// 设置地址复用
int val = 1;
setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, sizeof(int));
// 设置端口复用
val = 1;
setsockopt(_sockfd, SOL_SOCKET, SO_REUSEPORT, (void*)&val, sizeof(int));
}
// 12. 设置套接字阻塞属性 -- 设置为非阻塞
void set_nonblock()
{
int old = fcntl(_sockfd, F_GETFL, 0);
fcntl(_sockfd, F_SETFL, old | O_NONBLOCK);
}
};4、测试样例
创建一个服务端和客户端,这里只做简单的收发数据:
// 服务端
#include "../source/server.hpp"
int main()
{
Socket server;
server.create_server(8080);
while(true)
{
int newfd = server.Accept();
if(newfd != -1)
{
Socket client(newfd);
char buf[1024] = {0};
client.Recv(buf, 1023);
DLOG("%s", buf);
sleep(1);
std::string str = "hello lirendada!";
client.Send(str.c_str(), str.size());
client.Close();
}
sleep(1);
}
server.Close();
return 0;
} 客户端代码:
// 客户端
#include "../source/server.hpp"
int main()
{
Socket cli_sock;
cli_sock.create_client(8080, "127.0.0.1");
for (int i = 0; i < 5; i++)
{
std::string str = "hha";
cli_sock.Send(str.c_str(), str.size());
char buf[1024] = {0};
cli_sock.Recv(buf, 1023);
DLOG("%s", buf);
sleep(1);
}
while(1) sleep(1);
return 0;
}