一.UDP網絡
1.socket()創建套接字
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
domain (地址族): AF_INET網絡?AF_UNIX本地
AF_INET
:IPv4 地址族,適用于 IPv4 協議。用于網絡通信AF_INET6
:IPv6 地址族,適用于 IPv6 協議。AF_UNIX
?或?AF_LOCAL
:Unix 域套接字,用于本地通信。AF_PACKET
:用于與鏈路層通信(如以太網接口)。- 還有其他的地址族,例如?
AF_APPLETALK
,?AF_NETLINK
?等,但這些較少使用。type (套接字類型):
SOCK_STREAM
:流式套接字,表示 TCP 協議,面向連接,提供可靠的數據傳輸。SOCK_DGRAM
:數據報套接字,表示 UDP 協議,無連接的、不可靠的數據傳輸。SOCK_RAW
:原始套接字,允許直接操作底層協議,如 IP、ICMP 等(通常需要管理員權限)。SOCK_SEQPACKET
:順序數據包套接字,適用于某些特定的協議。protocol (協議類型):
- 通常設置為?
0
,系統會根據地址族和套接字類型自動選擇合適的協議。- 也可以顯式指定某個協議,例如:
IPPROTO_TCP
(TCP協議)或?IPPROTO_UDP
(UDP協議)。返回值
- 如果調用成功,返回一個套接字描述符(一個非負整數),該描述符是后續與套接字進行交互的標識。
- 如果調用失敗,返回?
-1
,并設置?errno
?來指示錯誤原因。錯誤代碼
- EAFNOSUPPORT:不支持指定的地址族。
- EINVAL:無效的套接字類型或協議。
- ENFILE:系統中可用的文件描述符已用盡。
- ENOMEM:系統內存不足。
2.bind()綁定
在 Linux 中,bind 系統調用用于將一個 套接字(socket)與一個本地地址(IP 地址和端口號)綁定。這個操作通常用于服務器端,目的是讓服務器的套接字可以接收來自特定地址和端口的網絡數據。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數:
- sockfd:要綁定的套接字描述符,通常是通過?
socket()
?系統調用創建的。- addr:指向一個?
struct sockaddr
?或其派生結構體的指針,用于指定要綁定的地址和端口。這個結構體的內容取決于地址族(AF_INET
、AF_INET6
?等)。- addrlen:
addr
?指向的地址結構的大小,通常是?sizeof(struct sockaddr_in)
?或?sizeof(struct sockaddr_in6)
。返回值:
- 成功:返回?
0
。- 失敗:返回?
-1
,并設置?errno
?以指示錯誤。
sockaddr 結構
socket API 是一層抽象的網絡編程接口,適用于各種底層網絡協議,如 IPv4、IPv6,以及
后面要講的 UNIX Domain Socket. 然而, 各種網絡協議的地址格式并不相同.
#include<netinet/in.h>
#include<arpa/inet.h>
sockaddr:一個通用的地址結構體,所有網絡地址結構體(如 sockaddr_in 和 sockaddr_un)都可以通過它來表示。它為不同協議族提供了統一的接口。
sockaddr_in:用于表示 IPv4 地址的結構體。它擴展了 sockaddr,包括端口號、IP 地址等信息,常用于網絡通信/本地。
sockaddr_un:用于表示 UNIX 域套接字地址的結構體,主要用于進程間通信(本地),套接字地址基于文件路徑。
struct sockaddr 結構
#include <netinet/in.h>
struct sockaddr {sa_family_t sa_family; // 地址族,例如 AF_INET 或 AF_UNIXchar sa_data[14]; // 用于存儲地址信息的字符數組
};
strcuct sockaddr_in 結構
struct sockaddr_in {sa_family_t sin_family; // 地址族,通常為 AF_INET(網絡通信)in_port_t sin_port; // 端口號,使用網絡字節序(大端)struct in_addr sin_addr; // IP 地址unsigned char sin_zero[8]; // 填充字節,保持結構體大小一致
};
struct in_addr {in_addr_t s_addr; // 32 位的 IP 地址
};
strcuct sockaddr_un 結構
struct sockaddr_un {sa_family_t sun_family; // 地址族,通常是 AF_UNIX(本地通信)char sun_path[108]; // UNIX 域套接字路徑,最大長度為 108 字節
};
在套接字編程中,我們通常會使用特定的結構體(如 sockaddr_in 或 sockaddr_un)來處理網絡地址,而 sockaddr 作為通用結構體,通常在系統調用(如 bind(), connect() 等)中使用,要求通過類型轉換將具體的地址結構體轉為 sockaddr 類型。
htons() 主機字節序轉網絡字節序
因為TCP/IP 協議規定,網絡數據流應采用大端字節序。
所以向網絡發數據應改位大端。
htons()返回轉換后的 16 位無符號整數,這個整數是網絡字節序(大端字節序)下的值。
htonl()?回轉換后的 32?位無符號整數,這個整數是網絡字節序(大端字節序)下的值。
ntohs()用于將網絡字節序(大端字節序)轉換回主機字節序。 16位無符號整數
inet_addr() 字符型ip地址->32位網絡字節序
inet_addr 是一個用于將 IPv4 地址從點分十進制字符串表示(如 "192.168.1.1")轉換為網絡字節序的 in_addr_t 類型(通常是一個 32 位的無符號整數)的方法。
#include <arpa/inet.h> // 包含 inet_addr 的定義
in_addr_t inet_addr(const char *cp);
字符轉整型+改大端
inet_pton() 字符型ip地址->32位網絡字節序
int inet_pton(int af, const char *src, void *dst);
參數:
af
: 地址族,指定了 IP 地址的類型。常見的值有:
AF_INET
:表示 IPv4 地址。AF_INET6
:表示 IPv6 地址。
src
: 輸入的地址字符串,表示待轉換的 IP 地址。例如,IPv4 地址是"192.168.1.1"
,IPv6 地址可能是"2001:0db8:85a3:0000:0000:8a2e:0370:7334"
。
dst
: 指向用于存儲轉換后的二進制地址的緩沖區。對于 IPv4,通常是struct in_addr
類型,對于 IPv6,通常是struct in6_addr
類型。返回值:
- 成功:返回?
1
,表示地址轉換成功。- 失敗:
- 如果輸入的 IP 地址格式無效(例如,IPv4 地址包含不合法的數字),返回?
0
。- 如果出現其他錯誤,返回?
-1
,并且可以通過?errno
?獲取詳細錯誤信息。
inet_ntoa()?32位網絡字節序->字符型ip地址
inet_ntoa 是一個用于將 IPv4 地址(以網絡字節順序存儲的二進制格式)轉換為 點分十進制
格式的函數。這個函數通常在 C 語言中使用,用于將 struct in_addr 結構中的二進制形式的 IPv4 地址轉換為標準的文本表示形式。
<arpa/inet.h>
char *inet_ntoa(struct in_addr in);
inet_ntop() 32位網絡字節序->字符型ip地址(多線程)
inet_ntop() 是一個更通用的函數,支持 IPv4 和 IPv6 地址。把轉換的字符串地址放在提供的棧空間中,防止被覆蓋。
inet_ntoa() 函數返回的是一個靜態的緩沖區,該緩沖區在函數調用之間會被復用,因此在多次調用 inet_ntoa() 時,返回的字符串會被覆蓋。這使得在 多線程環境 中使用 inet_ntoa() 可能導致線程之間共享同一個靜態緩沖區,進而引發 數據競爭 或 意外的覆蓋問題。
#include <stdio.h>
#include <arpa/inet.h>const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af
:地址族,指定是 IPv4 還是 IPv6。IPv4 使用?AF_INET
,IPv6 使用?AF_INET6
。src
:指向網絡字節順序的地址結構(struct in_addr
?或?struct in6_addr
)。dst
:用于存儲結果字符串的緩沖區,必須足夠大以容納結果字符串(IPv4 地址需要 16 字節,IPv6 地址需要 46 字節)。size
:dst
?緩沖區的大小。返回值
- 如果成功,返回?
dst
?指向的字符串。- 如果出錯,返回?
NULL
,并設置?errno
。
3.recvfrom()接收數據
recvfrom 是一個用于接收數據的系統調用,它通常用于 UDP 套接字,或者在某些情況下,用于接收來自不同主機的 TCP 數據。它的功能是從指定的套接字中接收數據,并且可以獲取遠程主機的地址信息。
#include <arpa/inet.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);//socklen_t unsigned int 無符號整型
參數說明:
- sockfd:目標套接字的文件描述符,通常是通過?
socket()
?函數返回的套接字描述符。- buf:指向接收數據的緩沖區。
- len:緩沖區的長度,即可以接收的最大字節數。
- flags:接收的標志位,通常為 0,或者使用一些標志,如?
MSG_PEEK
?等。- src_addr:指向?
struct sockaddr
?結構的指針,用于存放發送方的地址信息。對于 UDP 來說,這將包含發送方的 IP 地址和端口號。- addrlen:指向一個?
socklen_t
?類型的變量,表示?src_addr
?緩沖區的大小,recvfrom
?調用返回后,這個變量將被更新為實際存放的地址長度。返回值:
- 成功時,返回實際接收到的字節數。
- 出錯時,返回?
-1
,并設置?errno
。
在使用 recvfrom 時,addrlen 初始時需要包含 src_addr 緩沖區的大小。recvfrom 在接收數據時,會根據實際填充的地址長度來更新 addrlen。
這種機制主要是為了處理不同類型的地址結構,它們可能有不同的大小。例如,IPv4 和 IPv6 的地址結構有不同的大小,因此 addrlen 必須傳入一個適當的初始值,并且 recvfrom 會修改它,以便傳出實際的地址長度。
如果 addrlen 不包含緩沖區的大小,系統無法確定在接收數據時應該使用多大的內存空間來存儲地址信息,這可能導致:
無法獲取正確的地址: recvfrom 在更新 addrlen 時,必須知道地址結構的空間。沒有正確的大小,recvfrom 可能不會正確填充地址信息,或者根本無法獲取發送方的地址。
4.sendto()發送數據
sendto 是一個用于發送數據報文(datagram)的系統調用函數,通常在基于 UDP 的網絡編程中使用。它允許應用程序將數據發送到指定的目標地址,而無需先建立連接。它是 sockets API 的一部分,適用于無連接的協議,如 UDP。
#include <sys/types.h>
#include <sys/socket.h>ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
參數說明:
sockfd:
一個已打開的 socket 描述符。它是通過?socket()
?函數返回的,并且該 socket 必須是使用無連接協議(如 UDP)創建的。buf:
指向包含要發送數據的緩沖區的指針。數據的大小由?len
?參數指定。len:
要發送的數據的長度(字節數)。flags:
發送的標志,通常為 0。可以使用一些額外的標志,如?MSG_CONFIRM
、MSG_DONTWAIT
、MSG_NOSIGNAL
?等,具體使用取決于需求。dest_addr:
指向一個?struct sockaddr
?類型的結構體,表示目標地址。對于 UDP,這通常是一個?struct sockaddr_in
?類型的結構,包含目標主機的 IP 地址和端口號。addrlen:
dest_addr
?結構的長度,通常是?sizeof(struct sockaddr_in)
。返回值:
- 成功時,返回發送的字節數(即?
len
)。- 失敗時,返回 -1,并設置?
errno
?以指示錯誤。
netstat命令查看網絡服務是否啟動
netstat -nuap
-u 查看UDP連接
-a?查看所有網絡連接
-p?查看每個連接的 PID(進程標識符)
-n?以數字形式顯示地址和端口號
ifconfig命令查看網絡接口(ip mac)
ifconfig 是一個用于在 Unix-like 操作系統(如 Linux 和 macOS)中查看或配置網絡接口的命令。它通常用于顯示當前的網絡接口配置,啟用或禁用網絡接口,配置 IP 地址,子網掩碼,廣播地址等。
eth0 Link encap:Ethernet HWaddr 00:0c:29:3d:5e:80 inet addr:192.168.1.10 Bcast:192.168.1.255 Mask:255.255.255.0inet6 addr: fe80::20c:29ff:fe3d:5e80/64 Scope:LinkUP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1RX packets:12345 errors:0 dropped:0 overruns:0 frame:0TX packets:12345 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1000 RX bytes:9876543 (9.8 MB) TX bytes:9876543 (9.8 MB)
在上面的輸出中,eth0 是網絡接口的名稱,inet addr 顯示的是接口的 IPv4 地址。
在一些現代的 Linux 發行版中,ifconfig 已被 ip 命令(由 iproute2 提供)所取代,ip 命令提供了更多的功能和更細粒度的控制。例如,使用以下命令查看網絡接口:
ip a
INADDR_ANY宏 人員地址綁定
它是一個 in_addr_t 類型的值,通常用于網絡編程中的 sockaddr_in 結構體,表示一個可以匹配所有網絡接口的地址。
INADDR_ANY 是一個常量,通常被定義為 0.0.0.0。
在綁定套接字時使用 INADDR_ANY,服務器可以接收來自本機所有接口的連接請求。
#define INADDR_ANY ((in_addr_t) 0x00000000)
UPD網絡流程
1. socket()創建套接字
客戶端和服務器端都需要使用 socket() 函數創建一個 UDP 套接字。UDP 套接字使用 SOCK_DGRAM 類型。
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 創建 UDP 套接字
2. bind()設置服務器地址(服務器端)
服務器需要綁定一個本地的地址和端口,以便能夠接收來自客戶端的數據。
void *memset(void *ptr, int value, size_t num);把定義的sockaddr_in結構體清零。
INADDR_ANY 0.0.0.0?服務器可以接收來自本機所有接口的連接請求。
htons 轉網絡字節序
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 綁定到本機所有接口
server_addr.sin_port = htons(PORT); // 綁定指定端口bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 綁定端口
3. sendto()發送數據(客戶端)
客戶端可以直接通過 sendto() 函數將數據發送到服務器的 IP 地址和端口,無需建立連接。
inet_pton()?用于將 IP 地址從文本表示(如點分十進制的字符串)轉換為二進制形式
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "192.168.1.100", &server_addr.sin_addr); // 服務器地址char message[] = "Hello, Server!";
sendto(sockfd, message, sizeof(message), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
//client不需要bind端口號嗎?
//1.系統自動給客戶端bind端口號 為什么?
//我客戶端打開進程,只要保證進程的端口號有唯一性,能運行 連上服務端就行。沒人會主動連我,所以沒必 要知道端口號。
//同一個進程的端口號每次啟動不一定是固定的,但一定唯一
//2.為什么服務端要顯示bind端口號呢?
//因為服務端的端口號是不能隨便改變的。不同的客戶端訪問到服務端就是靠唯一的端口號找到的。所以我們自 己定義端口號,固定端口號。
4. recvfrom() 接收數據(服務器端)
服務器端使用 recvfrom() 函數接收客戶端發送的數據。該函數會返回數據并填充客戶端的地址信息。
一定要設置對struct sockaddr結構體的大小addr_len
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
char buffer[1024];int n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_addr, &addr_len);
buffer[n] = '\0'; // 添加字符串結束符
printf("Received from client: %s\n", buffer);
5.??sendto()?發送響應(服務器端)
服務器端可以通過?sendto()?函數發送響應給客戶端。
char response[] = "Hello, Client!";
sendto(sockfd, response, sizeof(response), 0, (struct sockaddr*)&client_addr, addr_len);
6.關閉套接字
完成數據通信后,客戶端和服務器都需要關閉套接字。
close(sockfd);
二.TCP網絡
1.listen() 監聽套接字
listen() 將套接字設置為監聽模式,準備接收來自客戶端的連接請求。
int listen(int sockfd, int backlog);
參數說明:
sockfd
:這是一個已創建并綁定的套接字文件描述符,通常通過?socket()
?和?bind()
?函數獲得。backlog
:定義了等待連接隊列的最大長度。這個隊列用于存放尚未被?accept()
?接受的連接請求。隊列中的連接數量可能會有所限制,具體值由操作系統決定。backlog
?的大小決定了可以掛起多少個連接請求。返回值:
- 成功:返回?
0
,表示監聽成功。- 失敗:返回?
-1
,并設置?errno
?來指示錯誤原因。
2.accept() 獲取套接字 客戶端信息
accept 是一個用于處理網絡連接的系統調用,通常與套接字(socket)編程一起使用。它的作用是從一個已建立的連接的監聽套接字隊列中接受一個連接,并為該連接創建一個新的套接字。
最后兩個輸出型參數 獲取客戶端信息ip+port
int accept(int listen_sockfd, struct sockaddr *addr, socklen_t *addrlen);
參數:
sockfd
:這是監聽套接字的文件描述符,通常是通過?socket()
?函數創建的套接字,并且通過?bind()
?和?listen()
?函數綁定和監聽。addr
:一個指向?struct sockaddr
?結構體的指針,用來存儲客戶端的地址信息。addrlen
:這個參數是一個指向?socklen_t
?類型的指針,指示?addr
?所指向的結構體的長度。在調用?accept
?之后,它會被更新為實際存儲的地址信息長度。返回值:
- 成功時,返回一個新的套接字描述符,這個套接字可以用來與客戶端進行通信。
- 如果失敗,返回?
-1
,并設置?errno
?以指示錯誤。
telnet命令
Linux 中使用 telnet 客戶端進行網絡連接或遠程管理是非常常見的操作。雖然 Telnet 不提供加密,因此現在更多推薦使用 SSH(Secure Shell),但它仍然可以用于某些簡單的遠程連接和測試任務。
telnet <hostname> <port>
<hostname>:目標主機的域名或 IP 地址。
<port>:目標主機上的端口號,Telnet 默認使用 端口 23。
在 Telnet 會話中,按下 Ctrl + ],進入命令模式后,輸入 quit 或 exit 來退出。
3.connect()建立與服務端的連接?
在網絡編程中,connect 是一個系統調用,用于建立客戶端與遠程服務器之間的連接。它通常用于 TCP/IP 套接字編程,用于向服務器發起連接請求。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數
sockfd
:一個已經通過?socket()
?函數創建的套接字描述符。addr
:一個指向?sockaddr
?結構的指針,該結構包含服務器的地址信息(如 IP 地址和端口)。addrlen
:addr
?結構的大小,通常是?sizeof(struct sockaddr_in)
?或?sizeof(struct sockaddr_in6)
,取決于 IPv4 或 IPv6。返回值
- 成功時,返回 0。
- 失敗時,返回 -1,并設置?
errno
?來指示錯誤原因。工作原理
connect
?調用會向目標主機發送連接請求(對于 TCP 是三次握手過程)。- 如果連接成功,客戶端與服務器之間就建立了連接,之后可以進行數據的發送和接收。
- 如果連接失敗(例如目標主機不可達或端口不可用),
connect
?會返回 -1,并設置?errno
,指示錯誤原因。錯誤碼(常見)
ECONNREFUSED
:目標服務器拒絕連接。ETIMEDOUT
:連接超時。EADDRINUSE
:本地地址已經在使用。EHOSTUNREACH
:目標主機不可達。
4.recv() send() 收發消息
在網絡編程中,recv 和 send 是兩個常用的函數,它們分別用于接收和發送數據。這些函數通常與套接字(socket)一起使用,用于在客戶端和服務器之間進行數據交換。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd
:是套接字的文件描述符,表示一個有效的已連接套接字。buf
:指向緩沖區的指針,recv
?會將接收到的數據存放在這里。len
:緩沖區的大小,即?buf
?能夠存儲的最大字節數。flags
:操作標志,通常為 0 或?MSG_WAITALL
(等待接收指定字節數)。返回值:
- 成功時,返回接收到的字節數。
- 如果連接已關閉,返回 0。
- 如果發生錯誤,返回 -1,并設置?
errno
。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd
:是套接字的文件描述符,表示一個有效的已連接套接字。buf
:指向包含要發送數據的緩沖區。len
:要發送的數據字節數。flags
:操作標志,通常為 0。返回值:
- 成功時,返回實際發送的字節數。
- 如果發生錯誤,返回 -1,并設置?
errno
。
recv 用于從網絡上接收數據,它可以讀取指定大小的字節。
send 用于向網絡上發送數據,數據的大小由你提供。read 和 write 沒有套接字特有的標志控制功能,因此它們在某些情況下缺乏靈活性。例如,在高效處理網絡數據時,可能需要精確控制數據接收量、非阻塞模式等,而 recv 和 send 可以通過特定標志輕松實現。
flags 參數:recv 和 send 都可以接受一個 flags 參數,這個參數可以控制一些特定的行為,例如 MSG_DONTWAIT(設為非阻塞狀態) 或 MSG_WAITALL(接收指定數量的字節),但通常情況下我們將其設置為 0。
eg.
recv(sockfd, buffer, 512, MSG_WAITALL)
:此函數調用會阻塞直到至少接收到 512 字節的數據(或者發生錯誤)。- 如果沒有收到足夠的字節,
recv
?會繼續等待,直到數據完整接收或遇到錯誤。
5.popen() pclose()父子進程間通信
我們之前怎么完成父子進程間通信的?
1.創建管道?int pipe(int pipefd[2]);pipefd[0] 是讀取端,pipefd[1] 是寫入端。2.創建子進程 fork()
3.父進程關閉寫端 子進程關閉讀端 close()
4.子進程輸出重定向到管道寫端?dup2(pipefds[1], 1) 標志輸出文件描述符是1
5.父進程從管道讀端進行讀取
下面popen pclose可以代替以上操作
FILE *popen(const char *command, const char *mode);
command:要執行命令的字符串
mode:"w" "r",讀還是寫
子進程執行完命令會把結果放在文件中并返回,pipe fork失敗返回NULL
父進程接收文件并讀取,最后關閉。
popen 用于打開一個進程,并將其標準輸入(stdin)或標準輸出(stdout)與父進程連接,返回一個文件指針,使得父進程可以通過該指針與子進程進行數據交互。
FILE *popen(const char *command, const char *mode);
command: 要執行的命令字符串。它是你希望啟動的子進程命令。
mode: 訪問模式。常見的有:
"r":讀取模式,父進程從子進程讀取輸出(即子進程的標準輸出)。popen 會創建一個管道,將子進程的標準輸出與父進程連接。父進程可以通過 fgets、fread 等讀取子進程的輸出。
"w":寫入模式,父進程向子進程寫入輸入(即將數據傳遞到子進程的標準輸入)。popen 會創建一個管道,將父進程的標準輸入與子進程連接。父進程可以通過 fputs、fprintf 等向子進程傳遞數據。eg.FILE* p=popen("ls","r");
#include <stdio.h>int main() {FILE *fp;char buffer[128];// 打開子進程,執行命令并讀取輸出fp = popen("ls", "r");if (fp == NULL) {perror("popen");return 1;}// 讀取子進程輸出并打印while (fgets(buffer, sizeof(buffer), fp) != NULL) {printf("%s", buffer);}// 關閉文件指針pclose(fp);return 0;
}
pclose 用于關閉由 popen 打開的文件指針,并等待子進程的終止。
int pclose(FILE *fp);
fp
: 是由?popen
?返回的文件指針。返回值:
- 如果子進程正常退出,
pclose
?返回子進程的退出狀態(通常是?0
)。- 如果子進程異常退出,返回一個負值。
fgets() 讀文件
fgets 是 C 語言標準庫中的一個函數,用于從文件或其他輸入流中讀取一行數據。它通常用于從文件、標準輸入(如鍵盤輸入)等地方讀取字符串。與 gets 函數相比,fgets 更安全,因為它允許指定最大讀取字符數,避免緩沖區溢出問題。
char *fgets(char *str, int num, FILE *stream);
參數說明:
- str:用于存儲讀取數據的字符數組。
fgets
?會將從流中讀取的字符存儲到?str
?中。- num:要讀取的最大字符數,包括結束符?
\0
。一般而言,num
?應該是你希望讀取的最大字節數 + 1,以便為字符串結尾的空字符(\0
)留出空間。- stream:指向文件流的指針。它可以是:
stdin
,用于從標準輸入讀取數據(通常是鍵盤)。- 任何通過?
fopen
?或其他方式打開的文件指針。返回值:
- 如果成功,
fgets
?返回?str
,即讀取的字符串。- 如果發生錯誤或到達文件末尾(EOF),返回?
NULL
。特點:
fgets
?會讀取指定數量的字符,直到遇到換行符?\n
?或文件結尾(EOF),并將換行符包括在內。fgets
?會自動添加字符串結束符?\0
,以保證字符串正確終止。