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);}
};