Socket模块
- 一、Socket模块是什么?
- 二、代码实现
- 1.成员变量
- 2.构造、析构函数
- 3.获取套接字文件描述符
- 4.创建套接字
- 5.绑定地址信息
- 6.开始监听连接请求
- 7.向服务器发起连接
- 8.获取新连接
- 9.接收数据
- 10.非阻塞接收数据
- 11.发送数据
- 12.非阻塞发送数据
- 13.关闭套接字
- 14.创建一个服务端连接
- 15.创建一个客户端连接
- 16.设置套接字选项——开启地址端口重用
- 17. 设置套接字阻塞属性——设置为非阻塞
- 18.测试代码
- 19.整体源代码
一、Socket模块是什么?
Socket模块是对套接字操作封装的⼀个模块,主要实现的socket的各项操作。
二、代码实现
1.成员变量
这行代码表示在类(class)的私有(private)部分声明了一个整型变量 _sockfd,用于存储某个类的套接字文件描述符。在类的私有部分声明的成员只能在该类的成员函数内部访问,外部无法直接访问。
private:
int _sockfd;
2.构造、析构函数
这段代码展示了一个名为Socket
的类的构造函数和析构函数的定义:
-
Socket() : _sockfd(-1) {}
: 这是一个无参数的构造函数,用于初始化Socket
类的对象。在这个构造函数中,通过初始化列表将_sockfd
成员变量设置为-1,表示套接字文件描述符的初始值为-1。 -
Socket(int sockfd) : _sockfd(sockfd) {}
: 这是一个带有整型参数的构造函数,用于初始化Socket
类的对象并指定套接字文件描述符的值。在这个构造函数中,通过初始化列表将_sockfd
成员变量设置为传入的参数sockfd
的值。 -
~Socket() { Close(); }
: 这是Socket
类的析构函数,用于释放资源和清理工作。在析构函数中调用了Close()
函数,该函数应该是Socket
类的一个成员函数,用于关闭套接字。通过在析构函数中调用Close()
函数,确保在对象被销毁时及时关闭相关资源,避免资源泄漏。
public:
Socket() : _sockfd(-1) {}
Socket(int sockfd) : _sockfd(sockfd) {}
~Socket() { Close(); }
3.获取套接字文件描述符
这段代码定义了一个名为get_fd()
的成员函数,用于获取类中私有成员变量_sockfd
的值(套接字文件描述符)。该函数返回整型值,表示获取到的套接字文件描述符。
通过定义这样的成员函数,可以在类外部获取Socket
类对象的套接字文件描述符,同时保持_sockfd
作为私有成员的封装性。这样的设计方式遵循了面向对象编程的封装原则,将类的数据隐藏起来,只允许通过成员函数来进行访问和操作,从而提高了代码的安全性和可维护性。
// 获取套接字文件描述符
int get_fd() { return _sockfd; }
4.创建套接字
// 创建套接字
bool Create()
{
// 调用socket函数创建套接字
// int socket(int domain, int type, int protocol)
_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 检查套接字创建是否成功
if (_sockfd < 0)
{
// 套接字创建失败时输出日志信息
INF_LOG("Socket creation failed");
return false;
}
// 套接字创建成功
return true;
}
5.绑定地址信息
// 绑定地址和端口
bool Bind(const std::string &ip, uint16_t port)
{
// 创建一个 sockaddr_in 结构体并设置相关参数
struct sockaddr_in addr;
addr.sin_family = AF_INET; // 设置地址族为IPv4
// 将端口号转换为网络字节顺序
addr.sin_port = htons(port);
// 将IP地址转换为网络字节顺序并填入结构体中
addr.sin_addr.s_addr = inet_addr(ip.c_str());
// 计算地址结构体的长度
socklen_t len = sizeof(struct sockaddr_in);
// 调用bind函数将套接字和地址绑定
int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
if (ret < 0)
{
// 绑定失败时输出错误日志信息
ERR_LOG("BIND ADDRESS FAILED!");
return false;
}
// 绑定成功
return true;
}
6.开始监听连接请求
// 开始监听连接请求
bool Listen(int backlog = MAX_LISTEN)
{
// 调用listen函数开始监听连接请求
int ret = listen(_sockfd, backlog);
if (ret < 0)
{
// 监听失败时输出错误日志信息
ERR_LOG("SOCKET LISTEN FAILED!");
return false;
}
// 监听成功
return true;
}
7.向服务器发起连接
// 向服务器发起连接
bool Connection(const std::string &ip, uint16_t port)
{
// 创建一个 sockaddr_in 结构体并设置相关参数
struct sockaddr_in addr;
addr.sin_family = AF_INET; // 设置地址族为IPv4
// 将端口号转换为网络字节顺序
addr.sin_port = htons(port);
// 将IP地址转换为网络字节顺序并填入结构体中
addr.sin_addr.s_addr = inet_addr(ip.c_str());
// 计算地址结构体的长度
socklen_t len = sizeof(struct sockaddr_in);
// 调用connect函数发起连接请求
int ret = connect(_sockfd, (struct sockaddr *)&addr, len);
if (ret < 0)
{
// 连接失败时输出错误日志信息
ERR_LOG("CONNECT SERVER FAILED!");
return false;
}
// 连接成功
return true;
}
8.获取新连接
// 获取新连接
int Accept()
{
// 调用accept函数接受新的连接
// int accept(int sockfd, struct sockaddr *addr, socklen_t *len);
int newfd = accept(_sockfd, NULL, NULL);
if (newfd < 0)
{
// 接受连接失败时输出错误日志信息
ERR_LOG("SOCKET ACCEPT FAILED!");
return -1;
}
// 返回新的文件描述符
return newfd;
}
9.接收数据
// 接收数据
ssize_t Recv(void *buf, size_t len, int flag = 0)
{
// 调用recv函数接收数据
ssize_t s = recv(_sockfd, buf, len, flag);
if (s <= 0)
{
if (errno == EINTR || errno == EAGAIN)
{
// 如果是由于被信号中断或者暂时没有数据可读造成的接收失败,则记录日志并返回0
INF_LOG("Recv has not ready!");
return 0;
}
// 其他接收失败的情况,记录错误日志信息并返回-1
ERR_LOG("read failed!");
return -1;
}
// 返回实际接收的数据长度
return s;
}
10.非阻塞接收数据
// 非阻塞接收数据
ssize_t NonBlockRecv(void *buf, size_t len)
{
// 调用Recv函数,设置MSG_DONTWAIT标志表示非阻塞接收
return Recv(buf, len, MSG_DONTWAIT);
}
11.发送数据
// 发送数据
ssize_t Send(const void *buf, size_t len, int flag = 0)
{
// 调用send函数发送数据
// ssize_t send(int sockfd, void *data, size_t len, int flag);
ssize_t ret = send(_sockfd, buf, len, flag);
if (ret < 0)
{
// 发送失败时记录错误日志信息并返回-1
ERR_LOG("SOCKET SEND FAILED!!");
return -1;
}
// 返回实际发送的数据长度
return ret;
}
12.非阻塞发送数据
// 非阻塞发送数据
ssize_t NonBlockSend(void *buf, size_t len)
{
// 调用Send函数,设置MSG_DONTWAIT标志表示非阻塞发送
return Send(buf, len, MSG_DONTWAIT);
}
13.关闭套接字
// 关闭套接字
void Close()
{
// 检查套接字是否有效,如果有效则关闭套接字
if (_sockfd != -1)
close(_sockfd);
// 将套接字文件描述符设置为无效值
_sockfd = -1;
}
14.创建一个服务端连接
// 创建一个服务端连接
bool CreateServer(uint16_t port, const std::string &ip = DEFAULT_IP, bool block_flag = false)
{
// 1. 创建套接字
// 2. 绑定地址
// 3. 开始监听
// 4. 设置非阻塞
// 5. 启动地址重用
// 如果创建套接字失败,则返回false
if (Create() == false) return false;
// 如果需要设置为非阻塞模式,则调用NonBlock函数
if (block_flag) NonBlock();
// 绑定地址,如果绑定失败则返回false
if (Bind(ip, port) == false) return false;
// 开始监听,如果监听失败则返回false
if (Listen() == false) return false;
// 启动地址重用
ReuseAddress();
return true;
}
15.创建一个客户端连接
// 创建一个客户端连接
bool CreateClient(uint16_t port, const std::string &ip)
{
// 1. 创建套接字
// 2. 指向连接服务器
// 如果创建套接字失败,则返回false
if (Create() == false)
return false;
// 连接服务器,如果连接失败则返回false
if (Connection(ip, port) == false)
return false;
return true;
}
16.设置套接字选项——开启地址端口重用
// 设置套接字选项——开启地址端口重用
void ReuseAddress()
{
// 使用setsockopt函数设置SO_REUSEADDR和SO_REUSEPORT选项开启地址和端口重用
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));
}
17. 设置套接字阻塞属性——设置为非阻塞
// 设置套接字阻塞属性——设置为非阻塞
void NonBlock()
{
// 使用fcntl函数获取当前套接字的属性,并设置为非阻塞模式
//int fcntl(int fd,int cmd,.../* arg */);
int flag = fcntl(_sockfd, F_GETFL, 0);
fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);
}
18.测试代码
这段代码是一个简单的基于Socket的服务器端程序,它创建一个服务器Socket并监听指定端口(8500)。然后在一个无限循环中接受客户端连接,接收客户端发送的数据,并将数据原样发送回客户端,最后关闭与客户端的连接。注意:需要打开两个终端分别运行服务器端和客户端
//服务器端
#include"../source/server.hpp"
int main(){
Socket lst_sock;
lst_sock.CreateServer(8500);
while(1){
int newfd =lst_sock.Accept();
if(newfd <0){
continue;
}
Socket cli_sock(newfd);
char buf[1024]={0};
int ret= cli_sock.Recv(buf, 1023);
if(ret < 0){
cli_sock.Close();
continue;
}
cli_sock.Send(buf, ret);
cli_sock.Close();
}
lst_sock.Close();
return 0;
}
//客户端口
#include"../source/server.hpp"
int main()
{
Socket cli_sock;
cli_sock.CreateClient(8500,"127.0.0.1");
std::string str="hello have a good day~";
cli_sock.Send(str.c_str(),str.size());
char buf[1024]={0};
cli_sock.Recv(buf,1023);
DBG_LOG("%s",buf);
return 0;
}
测试结果:
19.整体源代码
// Socket //
#define MAX_LISTEN 1024
#define DEFAULT_IP "0.0.0.0"
class Socket
{
private:
int _sockfd;
public:
Socket() : _sockfd(-1) {}
Socket(int sockfd) : _sockfd(sockfd) {}
~Socket() { Close(); }
// 获取套接字文件描述符
int get_fd() { return _sockfd; }
// 创建套接字
bool Create()
{
// 调用socket函数创建套接字
// int socket(int domain, int type, int protocol)
_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 检查套接字创建是否成功
if (_sockfd < 0)
{
// 套接字创建失败时输出日志信息
INF_LOG("Socket creation failed");
return false;
}
// 套接字创建成功
return true;
}
// 绑定地址和端口
bool Bind(const std::string &ip, uint16_t port)
{
// 创建一个 sockaddr_in 结构体并设置相关参数
struct sockaddr_in addr;
addr.sin_family = AF_INET; // 设置地址族为IPv4
// 将端口号转换为网络字节顺序
addr.sin_port = htons(port);
// 将IP地址转换为网络字节顺序并填入结构体中
addr.sin_addr.s_addr = inet_addr(ip.c_str());
// 计算地址结构体的长度
socklen_t len = sizeof(struct sockaddr_in);
// 调用bind函数将套接字和地址绑定
int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
if (ret < 0)
{
// 绑定失败时输出错误日志信息
ERR_LOG("BIND ADDRESS FAILED!");
return false;
}
// 绑定成功
return true;
}
// 开始监听连接请求
bool Listen(int backlog = MAX_LISTEN)
{
// 调用listen函数开始监听连接请求
int ret = listen(_sockfd, backlog);
if (ret < 0)
{
// 监听失败时输出错误日志信息
ERR_LOG("SOCKET LISTEN FAILED!");
return false;
}
// 监听成功
return true;
}
// 向服务器发起连接
bool Connection(const std::string &ip, uint16_t port)
{
// 创建一个 sockaddr_in 结构体并设置相关参数
struct sockaddr_in addr;
addr.sin_family = AF_INET; // 设置地址族为IPv4
// 将端口号转换为网络字节顺序
addr.sin_port = htons(port);
// 将IP地址转换为网络字节顺序并填入结构体中
addr.sin_addr.s_addr = inet_addr(ip.c_str());
// 计算地址结构体的长度
socklen_t len = sizeof(struct sockaddr_in);
// 调用connect函数发起连接请求
int ret = connect(_sockfd, (struct sockaddr *)&addr, len);
if (ret < 0)
{
// 连接失败时输出错误日志信息
ERR_LOG("CONNECT SERVER FAILED!");
return false;
}
// 连接成功
return true;
}
// 获取新连接
int Accept()
{
// 调用accept函数接受新的连接
// int accept(int sockfd, struct sockaddr *addr, socklen_t *len);
int newfd = accept(_sockfd, NULL, NULL);
if (newfd < 0)
{
// 接受连接失败时输出错误日志信息
ERR_LOG("SOCKET ACCEPT FAILED!");
return -1;
}
// 返回新的文件描述符
return newfd;
}
// 接收数据
ssize_t Recv(void *buf, size_t len, int flag = 0)
{
// 调用recv函数接收数据
ssize_t s = recv(_sockfd, buf, len, flag);
if (s <= 0)
{
if (errno == EINTR || errno == EAGAIN)
{
// 如果是由于被信号中断或者暂时没有数据可读造成的接收失败,则记录日志并返回0
INF_LOG("Recv has not ready!");
return 0;
}
// 其他接收失败的情况,记录错误日志信息并返回-1
ERR_LOG("read failed!");
return -1;
}
// 返回实际接收的数据长度
return s;
}
// 非阻塞接收数据
ssize_t NonBlockRecv(void *buf, size_t len)
{
// 调用Recv函数,设置MSG_DONTWAIT标志表示非阻塞接收
return Recv(buf, len, MSG_DONTWAIT);
}
// 发送数据
ssize_t Send(const void *buf, size_t len, int flag = 0)
{
// 调用send函数发送数据
// ssize_t send(int sockfd, void *data, size_t len, int flag);
ssize_t ret = send(_sockfd, buf, len, flag);
if (ret < 0)
{
// 发送失败时记录错误日志信息并返回-1
ERR_LOG("SOCKET SEND FAILED!!");
return -1;
}
// 返回实际发送的数据长度
return ret;
}
// 非阻塞发送数据
ssize_t NonBlockSend(void *buf, size_t len)
{
// 调用Send函数,设置MSG_DONTWAIT标志表示非阻塞发送
return Send(buf, len, MSG_DONTWAIT);
}
// 关闭套接字
void Close()
{
// 检查套接字是否有效,如果有效则关闭套接字
if (_sockfd != -1)
close(_sockfd);
// 将套接字文件描述符设置为无效值
_sockfd = -1;
}
// 创建一个服务端连接
bool CreateServer(uint16_t port, const std::string &ip = DEFAULT_IP, bool block_flag = false)
{
// 1. 创建套接字
// 2. 绑定地址
// 3. 开始监听
// 4. 设置非阻塞
// 5. 启动地址重用
// 如果创建套接字失败,则返回false
if (Create() == false) return false;
// 如果需要设置为非阻塞模式,则调用NonBlock函数
if (block_flag) NonBlock();
// 绑定地址,如果绑定失败则返回false
if (Bind(ip, port) == false) return false;
// 开始监听,如果监听失败则返回false
if (Listen() == false) return false;
// 启动地址重用
ReuseAddress();
return true;
}
// 创建一个客户端连接
bool CreateClient(uint16_t port, const std::string &ip)
{
// 1. 创建套接字
// 2. 指向连接服务器
// 如果创建套接字失败,则返回false
if (Create() == false)
return false;
// 连接服务器,如果连接失败则返回false
if (Connection(ip, port) == false)
return false;
return true;
}
// 设置套接字选项——开启地址端口重用
void ReuseAddress()
{
// 使用setsockopt函数设置SO_REUSEADDR和SO_REUSEPORT选项开启地址和端口重用
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));
}
// 设置套接字阻塞属性——设置为非阻塞
void NonBlock()
{
// 使用fcntl函数获取当前套接字的属性,并设置为非阻塞模式
//int fcntl(int fd,int cmd,.../* arg */);
int flag = fcntl(_sockfd, F_GETFL, 0);
fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);
}
};