個人主頁:chian-ocean
文章專欄-Linux
前言:
網絡編程中的套接字(Socket)是通信的基本接口,允許不同計算機之間通過網絡交換數據。套接字是計算機網絡中通信的“端點”,通過它,應用程序可以與網絡中的其他計算機進行數據通信。網絡套接字接口提供了一種抽象的、平臺無關的方式來進行進程間通信(IPC)或網絡通信。
網絡套接字接口
頭文件
- 編寫網絡的常用的4個頭文件,基本常用的函數都在這4個頭文件里面。
#include <sys/types.h> // 包含各種系統數據類型
#include <sys/socket.h> // 包含套接字操作相關函數和常量
#include <arpa/inet.h> // 包含與Internet地址轉換相關的函數
#include <netinet/in.h> // 定義與網絡字節序及IPv4/IPv6地址相關的結構體和常量
接口
socket
socket()
函數是創建網絡通信套接字的基礎。它用于創建一個套接字(socket)并返回一個套接字描述符(socket descriptor),這個描述符將被用來進行后續的網絡通信(例如發送和接收數據)。
int socket(int domain, int type, int protocol);
參數說明
domain
(地址族):指定通信使用的協議族。- 常用值:
AF_INET
:IPv4 地址族(用于 TCP/IP 通信)。AF_INET6
:IPv6 地址族。AF_UNIX
:本地通信,適用于 Unix 域套接字。
- 常用值:
type
(套接字類型):指定套接字的類型,決定數據傳輸的方式。- 常用值:
SOCK_STREAM
:流套接字(用于 TCP)。SOCK_DGRAM
:數據報套接字(用于 UDP)。SOCK_RAW
:原始套接字,用于底層協議。
- 常用值:
protocol
(協議):指定使用的具體協議,通常設置為0
讓系統自動選擇協議。- 常用值:
0
:自動選擇合適的協議。IPPROTO_TCP
:用于 TCP。IPPROTO_UDP
:用于 UDP。
- 常用值:
返回值
- 成功時,
socket()
返回一個 非負整數,這是一個套接字描述符,代表這個套接字。該描述符將用于后續的套接字操作(如綁定、連接、發送數據等)。 - 失敗時,返回
-1
,并且設置全局變量errno
來指示錯誤類型。
bind()
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數說明
-
sockfd
(套接字描述符)-
類型:
int
-
描述:這是通過
socket()
創建的套接字描述符。客戶端使用該套接字發起連接請求。 -
說明:該套接字應該是已經創建并且可以進行連接的有效套接字。
-
-
addr
(目標地址)-
類型:
struct sockaddr *
-
描述:指向一個
struct sockaddr
結構體的指針,包含了服務器的地址(IP 地址和端口號)。 -
說明:具體的結構類型通常為
struct sockaddr_in
(用于 IPv4 地址)或者struct sockaddr_in6
(用于 IPv6 地址)。這個結構體包含了目標服務器的 IP 地址和端口號。
-
-
addrlen
(地址長度)-
類型:
socklen_t
-
描述:指定目標地址結構體的大小(字節數)。
-
說明:通常設置為
sizeof(struct sockaddr_in)
或sizeof(struct sockaddr_in6)
,用于告訴connect()
函數地址結構的實際長度。
-
返回值
- 成功時:返回
0
,表示成功將套接字與指定的本地地址綁定。 - 失敗時:返回
-1
,并將errno
設置為具體的錯誤碼。
listen()
int listen(int sockfd, int backlog);
參數說明
sockfd
:- 類型:
int
- 描述:表示要進入監聽狀態的套接字描述符。這個套接字通常是通過
socket()
創建的,并且應該已經通過bind()
綁定了本地地址(如 IP 地址和端口)。 - 說明:套接字需要是一個有效的連接套接字,用于接受客戶端連接。
- 類型:
backlog
:- 類型:
int
- 描述:表示 監聽隊列的最大長度,也就是可以等待的連接請求數量。如果有多個客戶端同時請求連接,系統會將這些請求放入隊列中,
backlog
參數設置了隊列的最大長度。 - 說明:如果有超過
backlog
數量的連接請求,新的連接請求會被拒絕,或者它們會根據操作系統的實現策略被丟棄。 - 推薦值:常見的
backlog
值一般設置為 5 到 128,根據服務器的需求而定。對于高并發系統,可能需要更大的backlog
值。
- 類型:
返回值
- 成功時:返回
0
,表示成功將套接字轉換為監聽狀態。 - 失敗時:返回
-1
,并設置errno
以指示錯誤原因。
accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
參數說明
-
sockfd
(套接字描述符)-
類型:
int
-
描述:這是通過
socket()
創建的套接字描述符。客戶端使用該套接字發起連接請求。 -
說明:該套接字應該是已經創建并且可以進行連接的有效套接字。
-
-
addr
(目標地址)-
類型:
struct sockaddr *
-
描述:指向一個
struct sockaddr
結構體的指針,包含了服務器的地址(IP 地址和端口號)。 -
說明:具體的結構類型通常為
struct sockaddr_in
(用于 IPv4 地址)或者struct sockaddr_in6
(用于 IPv6 地址)。這個結構體包含了目標服務器的 IP 地址和端口號。
-
-
addrlen
(地址長度)-
類型:
socklen_t
-
描述:指定目標地址結構體的大小(字節數)。
-
說明:通常設置為
sizeof(struct sockaddr_in)
或sizeof(struct sockaddr_in6)
,用于告訴connect()
函數地址結構的實際長度。
-
返回值
-
成功時:返回 新的套接字描述符,用于與客戶端進行通信。這個新的套接字是通過
accept()
函數創建的,它與原始的監聽套接字不同,可以用于數據發送和接收。 -
失敗時:返回
-1
,并設置errno
以指示錯誤原因。
connect()
參數說明
-
sockfd
(套接字描述符)-
類型:
int
-
描述:這是通過
socket()
創建的套接字描述符。客戶端使用該套接字發起連接請求。 -
說明:該套接字應該是已經創建并且可以進行連接的有效套接字。
-
-
addr
(目標地址)-
類型:
struct sockaddr *
-
描述:指向一個
struct sockaddr
結構體的指針,包含了服務器的地址(IP 地址和端口號)。 -
說明:具體的結構類型通常為
struct sockaddr_in
(用于 IPv4 地址)或者struct sockaddr_in6
(用于 IPv6 地址)。這個結構體包含了目標服務器的 IP 地址和端口號。
-
-
addrlen
(地址長度)-
類型:
socklen_t
-
描述:指定目標地址結構體的大小(字節數)。
-
說明:通常設置為
sizeof(struct sockaddr_in)
或sizeof(struct sockaddr_in6)
,用于告訴connect()
函數地址結構的實際長度。
-
返回值
-
成功時:返回
0
,表示連接成功。 -
失敗時:返回
-1
,并且會設置errno
來指示錯誤的具體原因。
close()
int close(int fd);
參數說明
fd
(文件描述符):- 類型:
int
- 描述:這是要關閉的文件描述符。對于套接字編程而言,這通常是由
socket()
函數返回的套接字描述符(sockfd
)。 - 說明:在網絡編程中,
fd
是表示套接字的描述符,它可以是通過socket()
創建的套接字描述符。關閉該描述符會釋放套接字占用的資源。
- 類型:
返回值
- 成功時:返回
0
,表示成功關閉套接字或文件描述符。 - 失敗時:返回
-1
,并設置errno
為具體的錯誤代碼
網絡套接字封裝(TCP)
1. 頭文件引用
#include <iostream>
#include <string>
#include <cstring>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"
<iostream>
:提供輸入輸出流的功能,常用于打印日志或錯誤信息。<string>
:提供 C++ 標準庫的字符串類std::string
,用于字符串處理。<cstring>
和<strings.h>
:用于處理字符串相關的操作,如bzero()
和strerror()
。<sys/types.h>
和<sys/socket.h>
:提供套接字編程所需的類型定義和系統調用。<arpa/inet.h>
:提供與 IP 地址轉換相關的函數(如inet_ntop()
和inet_addr()
)。<netinet/in.h>
:定義了用于 IPv4 地址和端口的結構體和常量(如sockaddr_in
和htons()
)。"log.hpp"
:這是一個自定義的日志頭文件,包含了日志記錄相關的內容。lg()
函數用于記錄日志,lg()
宏應該在log.hpp
中定義。
2. 全局變量和枚舉類型
int backlog = 10;enum err
{Socketerr = 1,Bindeterr,Listeneterr, Accepteterr,
};
backlog
:這是傳遞給listen()
函數的參數,定義了監聽隊列的最大長度(即最大客戶端連接數)。設置為10
。enum err
:定義了與套接字相關的錯誤類型。Socketerr = 1
:表示套接字創建失敗。Bindeterr
:表示套接字綁定失敗。Listeneterr
:表示監聽失敗。Accepteterr
:表示接受客戶端連接失敗。
3. Sock
類
3.1 構造函數和析構函數
Sock() {}
~Sock() {}
- 構造函數:默認構造函數,沒有進行任何初始化操作。
- 析構函數:默認析構函數,沒有執行任何資源清理操作。
3.2 Socket()
- 創建套接字
void Socket()
{sockfd_ = socket(AF_INET, SOCK_STREAM, 0);if(sockfd_ < 0){lg(FATAL,"Socket error: %d,%s",errno,strerror(errno));exit(Socketerr);}
}
- 目的:創建一個 TCP 套接字。
AF_INET
:表示 IPv4 地址族。SOCK_STREAM
:表示 TCP 流套接字(面向連接的套接字)。0
:表示默認協議,通常是 TCP 協議。
- 錯誤處理:如果
socket()
返回值小于0
,表示套接字創建失敗,記錄日志并退出程序,退出代碼為Socketerr
。
3.3 Bind(uint16_t port)
- 綁定套接字
void Bind(uint16_t port)
{struct sockaddr_in peer;socklen_t len = sizeof(peer);bzero(&peer,len);peer.sin_port = htons(port);peer.sin_family = AF_INET;peer.sin_addr.s_addr = INADDR_ANY;if(bind(sockfd_,(struct sockaddr *)&(peer),len) < 0){lg(FATAL,"Bind error: %d,%s",errno,strerror(errno));exit(Bindeterr);}
}
- 目的:將套接字與本地 IP 地址和端口號綁定。通過
INADDR_ANY
將套接字綁定到所有可用的網絡接口上,接受來自任何 IP 地址的連接。htons()
:將端口號從主機字節序轉換為網絡字節序。
- 錯誤處理:如果
bind()
返回值小于0
,表示綁定失敗,記錄日志并退出程序,退出代碼為Bindeterr
。
3.4 Listen()
- 開始監聽
void Listen()
{if(listen(sockfd_, backlog) < 0){lg(FATAL,"Listen error: %d,%s",errno,strerror(errno));exit(Listeneterr);}
}
- 目的:將套接字設置為監聽狀態,準備接受客戶端的連接。
backlog
:監聽隊列的最大長度,定義最多能排隊等待的連接數。
- 錯誤處理:如果
listen()
返回值小于0
,表示監聽失敗,記錄日志并退出程序,退出代碼為Listeneterr
。
3.5 Accept(std::string \* clientip, uint16_t\* clientport)
- 接受連接
int Accept(std::string * clientip, uint16_t* clientport)
{struct sockaddr_in peer;socklen_t len = sizeof(peer);bzero(&peer,len);int newfd = accept(sockfd_,(struct sockaddr*)&(peer),&len);if(newfd < 0){lg(FATAL,"Accept error: %d,%s",errno,strerror(errno));exit(Accepteterr);}char ip[64];inet_ntop(AF_INET,&peer.sin_addr.s_addr,ip,sizeof(ip));*clientip = ip;*clientport = ntohs(peer.sin_port);return newfd;
}
- 目的:接受來自客戶端的連接請求,并返回一個新的套接字用于與客戶端的通信。
accept()
函數返回一個新的套接字newfd
,用于與客戶端交換數據。- 通過
inet_ntop()
將客戶端的 IP 地址從二進制轉換為字符串格式,ntohs()
將客戶端的端口號轉換為主機字節序。
- 錯誤處理:如果
accept()
返回值小于0
,表示接受連接失敗,記錄日志并退出程序,退出代碼為Accepteterr
。
3.6 Connect(const std::string& ip, const uint16_t& port)
- 連接服務器
bool Connect(const std::string& ip,const uint16_t& port)
{struct sockaddr_in peer;socklen_t len = sizeof(peer);bzero(&peer,len);peer.sin_addr.s_addr = inet_addr(ip.c_str());peer.sin_port = htons(port);peer.sin_family = AF_INET;int n = connect(sockfd_,(struct sockaddr*)&(peer),len);if(n < 0){lg(WARNING,"Connect error: %d,%s",errno,strerror(errno));return false;}return true;
}
- 目的:客戶端通過此函數連接到遠程服務器,指定服務器的 IP 地址和端口。
inet_addr()
:將 IP 地址從字符串轉換為網絡字節序的二進制格式。htons()
:將端口號轉換為網絡字節序。
- 錯誤處理:如果
connect()
失敗,記錄警告日志并返回false
,否則返回true
表示連接成功。
3.7 GetFd()
- 獲取套接字描述符
int GetFd()
{return sockfd_;
}
- 目的:返回套接字描述符,便于外部訪問該套接字,用于進一步的操作(如
send()
,recv()
等)。