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){// 如果是由于被信号中断或者暂时没有数据可读造成的接收失败,则记录日志并返回0INF_LOG("Recv has not ready!");return 0;}// 其他接收失败的情况,记录错误日志信息并返回-1ERR_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){// 发送失败时记录错误日志信息并返回-1ERR_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. 启动地址重用// 如果创建套接字失败,则返回falseif (Create() == false) return false;// 如果需要设置为非阻塞模式,则调用NonBlock函数if (block_flag) NonBlock();// 绑定地址,如果绑定失败则返回falseif (Bind(ip, port) == false) return false;// 开始监听,如果监听失败则返回falseif (Listen() == false) return false;// 启动地址重用ReuseAddress();return true;
}
15.创建一个客户端连接
// 创建一个客户端连接
bool CreateClient(uint16_t port, const std::string &ip)
{// 1. 创建套接字 // 2. 指向连接服务器// 如果创建套接字失败,则返回falseif (Create() == false) return false;// 连接服务器,如果连接失败则返回falseif (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){// 如果是由于被信号中断或者暂时没有数据可读造成的接收失败,则记录日志并返回0INF_LOG("Recv has not ready!");return 0;}// 其他接收失败的情况,记录错误日志信息并返回-1ERR_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){// 发送失败时记录错误日志信息并返回-1ERR_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. 启动地址重用// 如果创建套接字失败,则返回falseif (Create() == false) return false;// 如果需要设置为非阻塞模式,则调用NonBlock函数if (block_flag) NonBlock();// 绑定地址,如果绑定失败则返回falseif (Bind(ip, port) == false) return false;// 开始监听,如果监听失败则返回falseif (Listen() == false) return false;// 启动地址重用ReuseAddress();return true;}// 创建一个客户端连接bool CreateClient(uint16_t port, const std::string &ip){// 1. 创建套接字 // 2. 指向连接服务器// 如果创建套接字失败,则返回falseif (Create() == false) return false;// 连接服务器,如果连接失败则返回falseif (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);}
};