hello !大家好呀! 歡迎大家來(lái)到我的Linux高性能服務(wù)器編程系列之項(xiàng)目實(shí)戰(zhàn)——仿QQ聊天程序源碼剖析,在這篇文章中,你將會(huì)學(xué)習(xí)到如何利用Linux網(wǎng)絡(luò)編程技術(shù)來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的聊天程序,并且我會(huì)給出源碼進(jìn)行剖析,以及手繪UML圖來(lái)幫助大家來(lái)理解,希望能讓大家更能了解網(wǎng)絡(luò)編程技術(shù)!??!
希望這篇文章能對(duì)你有所幫助
,大家要是覺(jué)得我寫(xiě)的不錯(cuò)的話(huà),那就點(diǎn)點(diǎn)免費(fèi)的小愛(ài)心吧!
(注:這章對(duì)于高性能服務(wù)器的架構(gòu)非常重要喲!?。。?/p>
? ? ? ? ?
目錄
一.項(xiàng)目介紹
二.服務(wù)器代碼剖析
2.1 頭文件和相關(guān)數(shù)據(jù)聲明
2.2 服務(wù)器連接準(zhǔn)備代碼
2.3 服務(wù)器處理邏輯代碼
2.3 客戶(hù)端代碼剖析
?
一.項(xiàng)目介紹
? ? ? 像ssh這樣的登錄服務(wù)通常要同時(shí)處理網(wǎng)絡(luò)連接和用戶(hù)輸入,這也可以使用I/O復(fù)用來(lái)實(shí)現(xiàn)。我們以poll為例實(shí)現(xiàn)一個(gè)簡(jiǎn)單的聊天室程序,以闡述如何使用I/O?復(fù)用技術(shù)來(lái)同時(shí)處理網(wǎng)絡(luò)連接和用戶(hù)輸入。該聊天室程序能讓所有用戶(hù)同時(shí)在線(xiàn)群聊,它分為客戶(hù)端和服務(wù)器兩個(gè)部分。其中客戶(hù)端程序有兩個(gè)功能:一是從標(biāo)準(zhǔn)輸入終端讀入用戶(hù)數(shù)據(jù),并將用戶(hù)數(shù)據(jù)發(fā)送至服務(wù)器;二是往標(biāo)準(zhǔn)輸出終端打印服務(wù)器發(fā)送給它的數(shù)據(jù)。服務(wù)器的功能是接收,客戶(hù)數(shù)據(jù),并把客戶(hù)數(shù)據(jù)發(fā)送給每一個(gè)登錄到該服務(wù)器上的客戶(hù)端(數(shù)據(jù)發(fā)送者除外)。下面我們依次給出客戶(hù)端程序和服務(wù)器程序的代碼。
二.服務(wù)器代碼剖析
2.1 頭文件和相關(guān)數(shù)據(jù)聲明
#define _GNU_SOURCE 1
#include<t_stdio.h>
#include<t_file.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/poll.h>
#include<fcntl.h>
#include<errno.h>
#define user_limit 5 //最大客戶(hù)連接數(shù)量
#define buffer_size 64
#define fd_limit 65535 //最大文件描述符數(shù)量
struct client_data{//創(chuàng)建一個(gè)客戶(hù)地址結(jié)構(gòu)體
struct sockaddr_in address ;
char * write_buf;
char buf[buffer_size];
};
int setnonblocking (int fd){//將文件描述符改為非阻塞模式
int old_option = fcntl(fd , F_GETFL);
int new_option = old_option | O_NONBLOCK;// 添加非阻塞選項(xiàng)
fcntl(fd , F_SETFL , new_option);//設(shè)置
return old_option;
}
這部分代碼包含了頭文件,定義了一些宏,以及一個(gè)用于存儲(chǔ)客戶(hù)端數(shù)據(jù)的結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體是為了服務(wù)器更好控制來(lái)自客戶(hù)端的socket套接字,以及靈活控制對(duì)socket套接字的讀寫(xiě),然后還定義了setnonblocking()函數(shù)來(lái)將傳入的文件描述符利用fcntl函數(shù)改為非阻塞模式,方便服務(wù)器進(jìn)行監(jiān)聽(tīng)。
2.2 服務(wù)器連接準(zhǔn)備代碼
這段代碼是服務(wù)器端程序的入口和初始化部分。下面是逐行的解釋?zhuān)?/p>
int main(int argc , char *argv[]){
if(argc <= 2)//如果參數(shù)太少
{
printf("usage :%s ip_address port_number\n",basename(argv[0]));
return 1;
}
這段代碼檢查命令行參數(shù)的數(shù)量。如果參數(shù)少于兩個(gè)(程序名稱(chēng)和IP地址/端口號(hào)),則打印使用說(shuō)明并退出程序。
const char * ip = argv[1] ;// 提取ip地址
int port = atoi(argv[2]); //提取端口號(hào)
從命令行參數(shù)中提取服務(wù)器的IP地址和端口號(hào)。
struct sockaddr_in address ; //服務(wù)器地址
bzero(&address ,sizeof(address));//清空
address.sin_family = AF_INET;
inet_pton(AF_INET , ip ,&address.sin_addr);//設(shè)置ip
address.sin_port = htons(port); //設(shè)置端口號(hào)
這里創(chuàng)建了一個(gè)sockaddr_in
結(jié)構(gòu)體來(lái)存儲(chǔ)服務(wù)器的地址信息,并使用bzero
函數(shù)將其清零。然后設(shè)置地址族為AF_INET
(IPv4),使用inet_pton
函數(shù)將點(diǎn)分十進(jìn)制的IP地址轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序的格式,并存儲(chǔ)在sin_addr
字段中。最后,將端口號(hào)從主機(jī)字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序并存儲(chǔ)在sin_port
字段中。
int listenfd = socket(PF_INET ,SOCK_STREAM , 0);//創(chuàng)建監(jiān)聽(tīng)套接字
assert(listenfd >=0);
創(chuàng)建一個(gè)TCP套接字(SOCK_STREAM
)用于監(jiān)聽(tīng)客戶(hù)端連接,并檢查套接字是否創(chuàng)建成功。
int ret = bind(listenfd , (struct sockaddr*)&address , sizeof(address));//綁定
assert(ret !=-1);
將套接字綁定到之前設(shè)置的服務(wù)器地址上,并檢查綁定操作是否成功。
ret = listen(listenfd ,5);//最多同時(shí)監(jiān)聽(tīng)五個(gè)
assert(ret!=-1);
調(diào)用listen
函數(shù),使套接字進(jìn)入監(jiān)聽(tīng)狀態(tài),并設(shè)置最大同時(shí)連接數(shù)為5。然后檢查監(jiān)聽(tīng)操作是否成功。
//創(chuàng)建user數(shù)組,放入多個(gè)客戶(hù)對(duì)象,并且使用socket的值可以直接用來(lái)索引(作為數(shù)組下標(biāo))連接對(duì)應(yīng)的client_data對(duì)象
struct client_data * user = malloc(fd_limit * sizeof(struct client_data));
//為了提高poll性能,限制用戶(hù)數(shù)量
struct pollfd *fds = malloc(sizeof(struct pollfd) * 6);
int user_counter = 0;//計(jì)算客戶(hù)連接數(shù)量
int i=0;
for( i = 1 ; i<=user_limit ; ++i){//對(duì)每個(gè)fds數(shù)據(jù)初始化
fds[i].fd = -1;
fds[i].events =0;
}
這段代碼分配了兩個(gè)數(shù)組:
user
數(shù)組用于存儲(chǔ)客戶(hù)端數(shù)據(jù),fds
數(shù)組用于poll
函數(shù)。user
數(shù)組的大小被設(shè)置為fd_limit
,這是一個(gè)預(yù)定義的最大文件描述符數(shù)量。fds
數(shù)組的大小被設(shè)置為6,這是因?yàn)榉?wù)器程序只監(jiān)聽(tīng)一個(gè)套接字(listenfd
),而其余的用于客戶(hù)端連接。user_counter
用于跟蹤當(dāng)前連接的客戶(hù)端數(shù)量。fds
數(shù)組的其余元素被初始化為-1
,表示沒(méi)有對(duì)應(yīng)的文件描述符。
//初始化怕poll中第一個(gè)數(shù)據(jù):監(jiān)聽(tīng)套接字
fds[0].fd = listenfd;
fds[0].events = POLLIN | POLLERR;
fds[0].revents = 0;
最后,將監(jiān)聽(tīng)套接字listenfd
添加到fds
數(shù)組中,并設(shè)置其監(jiān)聽(tīng)的事件為可讀事件(POLLIN
)和錯(cuò)誤事件(POLLERR
)。revents
字段用于poll
函數(shù)返回時(shí)存儲(chǔ)發(fā)生的事件,在這里初始化為0。
這段代碼為服務(wù)器程序的后續(xù)操作設(shè)置了基礎(chǔ),包括套接字的創(chuàng)建和綁定,以及用于poll
函數(shù)的數(shù)組的初始化。
2.3 服務(wù)器處理邏輯代碼
這段代碼是服務(wù)器程序的主循環(huán),它使用poll
系統(tǒng)調(diào)用來(lái)監(jiān)控多個(gè)文件描述符(fds
數(shù)組)的事件。這個(gè)循環(huán)會(huì)一直運(yùn)行,直到遇到錯(cuò)誤或者被顯式地退出。
while(1){
這是一個(gè)無(wú)限循環(huán),服務(wù)器程序?qū)⒁恢边\(yùn)行直到出現(xiàn)錯(cuò)誤或者執(zhí)行了退出循環(huán)的操作。
ret = poll(fds , user_counter+1 , -1);//開(kāi)始監(jiān)聽(tīng)
if(ret <0) {
printf("poll failed..\n");
break;
}
在循環(huán)的頂部,調(diào)用
poll
函數(shù)來(lái)等待事件發(fā)生。fds
數(shù)組包含了所有需要監(jiān)控的文件描述符,user_counter+1
表示總共有user_counter
個(gè)客戶(hù)端連接加上監(jiān)聽(tīng)套接字listenfd
。-1
表示poll
函數(shù)將阻塞直到至少有一個(gè)文件描述符上有事件發(fā)生。如果poll
調(diào)用失?。ǚ祷刂敌∮?),則打印錯(cuò)誤信息并退出循環(huán)。
for ( i =0 ; i <user_counter+1;i++){//每次對(duì)整個(gè)fds數(shù)組進(jìn)行遍歷處理
if(fds[i].fd==listenfd && (fds[i].revents & POLLIN)){//如果為第一個(gè)監(jiān)聽(tīng)字符且發(fā)生可讀事件時(shí)
struct sockaddr_in client_address;//創(chuàng)建一個(gè)新客戶(hù)套接字
socklen_t client_addrlength = sizeof(client_address);
int connfd = accept(listenfd ,(struct sockaddr*)&client_address ,&client_addrlength );//獲取客戶(hù)端套接字
if(connfd<0){//連接錯(cuò)誤
printf("erron is:%dd\n");
continue;
}
if(user_counter >=user_limit){//用戶(hù)太多
const char * info ="too many users\n";
printf("%s\n",info);
send(connfd , info ,strlen(info) , 0);//發(fā)送錯(cuò)誤給客戶(hù)端
close(connfd);
continue;
}
//對(duì)于新連接 ,我們要同時(shí)修改fds和users數(shù)組,user[connfd]即對(duì)應(yīng)客戶(hù)端數(shù)據(jù)
user_counter++;//客戶(hù)數(shù)量加一
user[connfd].address = client_address;
setnonblocking(connfd);//設(shè)置為非阻塞模式
fds[user_counter].fd = connfd;//最新數(shù)據(jù)放入數(shù)組
fds[user_counter].events = POLLIN | POLLRDHUP | POLLERR;
fds[user_counter].revents = 0;
printf("comes a new user , now have %d user\n",user_counter);
}
// ... 其他事件處理邏輯 ...
}
這個(gè)循環(huán)遍歷
fds
數(shù)組中的每個(gè)文件描述符,檢查它們是否有事件發(fā)生。對(duì)于每個(gè)事件,服務(wù)器程序執(zhí)行相應(yīng)的操作:
如果監(jiān)聽(tīng)套接字(
listenfd
)上有新的連接請(qǐng)求(POLLIN
事件),服務(wù)器接受新連接,并將新的文件描述符(connfd
)添加到fds
數(shù)組中。如果連接數(shù)超過(guò)限制(user_limit
),服務(wù)器會(huì)發(fā)送一個(gè)錯(cuò)誤消息并關(guān)閉新連接。如果有任何文件描述符上有
POLLERR
事件,表示發(fā)生了錯(cuò)誤,服務(wù)器會(huì)打印錯(cuò)誤信息。如果有任何已連接的套接字上有
POLLIN
事件,表示有數(shù)據(jù)可讀,服務(wù)器會(huì)讀取數(shù)據(jù)并打印。如果有任何套接字上有
POLLRDHUP
事件,表示對(duì)方已經(jīng)關(guān)閉了連接,服務(wù)器會(huì)關(guān)閉對(duì)應(yīng)的連接并更新fds
數(shù)組。如果有任何套接字上有
POLLOUT
事件,表示可以寫(xiě)數(shù)據(jù),服務(wù)器會(huì)發(fā)送數(shù)據(jù)(如果有數(shù)據(jù)要發(fā)送)。在循環(huán)結(jié)束后,服務(wù)器程序會(huì)繼續(xù)執(zhí)行下一次循環(huán),等待更多的連接和事件。
在服務(wù)器端代碼中,poll
函數(shù)用于監(jiān)控多個(gè)文件描述符的事件。poll
函數(shù)的返回值表示有多少個(gè)文件描述符發(fā)生了事件,而每個(gè)文件描述符的事件類(lèi)型存儲(chǔ)在revents
字段中。下面是服務(wù)器端代碼中使用poll
函數(shù)監(jiān)控的不同事件類(lèi)型及其解釋?zhuān)?/p>
if(fds[i].fd==listenfd && (fds[i].revents & POLLIN)){
// ... 接受連接邏輯 ...
}
else if(fds[i].revents & POLLERR){
// ... 錯(cuò)誤處理邏輯 ...
}
else if(fds[i].revents & POLLIN){
// ... 讀取數(shù)據(jù)邏輯 ...
}
else if(fds[i].revents & POLLRDHUP){
// ... 關(guān)閉連接邏輯 ...
}
else if(fds[i].revents & POLLOUT){
// ... 寫(xiě)數(shù)據(jù)邏輯 ...
}
POLLIN: 這個(gè)事件表示文件描述符上有數(shù)據(jù)可讀。對(duì)于服務(wù)器來(lái)說(shuō),這意味著有新的客戶(hù)端連接請(qǐng)求或者已連接的客戶(hù)端有數(shù)據(jù)發(fā)送過(guò)來(lái)。
POLLERR: 這個(gè)事件表示文件描述符發(fā)生了錯(cuò)誤??赡苁蔷W(wǎng)絡(luò)錯(cuò)誤,也可能是其他類(lèi)型的錯(cuò)誤。服務(wù)器需要檢查并處理這些錯(cuò)誤。
POLLRDHUP: 這個(gè)事件表示文件描述符的讀端已經(jīng)被對(duì)方關(guān)閉。這通常發(fā)生在客戶(hù)端突然斷開(kāi)連接的情況下。
POLLOUT: 這個(gè)事件表示文件描述符的寫(xiě)端準(zhǔn)備好了,可以寫(xiě)入數(shù)據(jù)。對(duì)于服務(wù)器來(lái)說(shuō),這意味著它可以向客戶(hù)端發(fā)送數(shù)據(jù)。
服務(wù)器程序通過(guò)檢查
fds
數(shù)組中每個(gè)文件描述符的revents
字段,來(lái)確定發(fā)生了哪種事件,并相應(yīng)地執(zhí)行處理邏輯。如果沒(méi)有任何事件發(fā)生,poll
函數(shù)會(huì)阻塞,直到至少有一個(gè)文件描述符上有事件發(fā)生。服務(wù)器程序通過(guò)這種方式可以高效地處理多個(gè)客戶(hù)端連接。
2.3 客戶(hù)端代碼剖析
這段代碼是一個(gè)簡(jiǎn)單的客戶(hù)端程序,用于連接到一個(gè)服務(wù)器,并通過(guò)標(biāo)準(zhǔn)輸入和輸出與服務(wù)器進(jìn)行通信
#define _GNU_SOURCE 1
#include<t_stdio.h>
#include<t_file.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include <sys/poll.h>
#include<fcntl.h>
#include<poll.h>
#define buffer_size 64 //緩沖區(qū)大小
這段代碼包含了必要的頭文件和宏定義。_GNU_SOURCE
是一個(gè)宏,它用于啟用一些GNU擴(kuò)展,如splice
系統(tǒng)調(diào)用。
int main(int argc , char * argv[])
{
if(argc <= 2)//如果參數(shù)太少
{
printf("usage :%s ip_address port_number\n",basename(argv[0]));
return 1;
}
這段代碼檢查命令行參數(shù)的數(shù)量。如果參數(shù)少于兩個(gè)(程序名稱(chēng)和IP地址/端口號(hào)),則打印使用說(shuō)明并退出程序。
const char * ip = argv[1] ;// 提取ip地址
int port = atoi(argv[2]); //提取端口號(hào)
從命令行參數(shù)中提取服務(wù)器的IP地址和端口號(hào)。
struct sockaddr_in server_address ; //服務(wù)器地址
bzero(&server_address ,sizeof(server_address));//清空
server_address.sin_family = AF_INET;
inet_pton(AF_INET , ip ,&server_address.sin_addr);//設(shè)置ip
server_address.sin_port = htons(port); //設(shè)置端口號(hào)
創(chuàng)建一個(gè)sockaddr_in
結(jié)構(gòu)體來(lái)存儲(chǔ)服務(wù)器的地址信息,并使用bzero
函數(shù)將其清零。然后設(shè)置地址族為AF_INET
(IPv4),使用inet_pton
函數(shù)將點(diǎn)分十進(jìn)制的IP地址轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序的格式,并存儲(chǔ)在sin_addr
字段中。最后,將端口號(hào)從主機(jī)字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序并存儲(chǔ)在sin_port
字段中。
int sockfd = socket(PF_INET , SOCK_STREAM , 0 );//創(chuàng)建本地套接字
assert(socket >= 0 ); //判錯(cuò)
if(connect(sockfd , (struct sockaddr *)&server_address , sizeof(server_address)) < 0){//連接失敗的話(huà)
printf("connection failed...\n");
close(sockfd);
return 1;
}
創(chuàng)建一個(gè)TCP套接字(SOCK_STREAM
)用于與服務(wù)器通信,并檢查套接字是否創(chuàng)建成功。然后嘗試連接到服務(wù)器。如果連接失敗,打印錯(cuò)誤信息并退出程序。
struct pollfd fds[2];//創(chuàng)建pollfd結(jié)構(gòu)類(lèi)型數(shù)組,注冊(cè)標(biāo)準(zhǔn)輸入和sockfd文件描述符上的可讀事件
fds[0].fd = 0;
fds[0].events = POLLIN ;//標(biāo)準(zhǔn)輸入可讀
fds[0].revents = 0; //實(shí)際發(fā)生事件,由內(nèi)核填充
fds[1].fd = sockfd;
fds[1].events = POLLIN | POLLRDHUP ;//標(biāo)準(zhǔn)輸入可讀
fds[1].revents = 0; //實(shí)際發(fā)生事件,由內(nèi)核填充
創(chuàng)建一個(gè)pollfd
結(jié)構(gòu)體數(shù)組,用于監(jiān)控標(biāo)準(zhǔn)輸入(0
)和套接字(sockfd
)上的可讀事件。
while (1){
ret = poll(fds , 2 , -1); //最大被監(jiān)聽(tīng)事件只有兩個(gè), 返回符合條件文件總數(shù)
if(ret < 0){//如果監(jiān)聽(tīng)發(fā)生錯(cuò)誤
printf("poll falied..\n");
break;
}
在循環(huán)的頂部,調(diào)用poll
函數(shù)來(lái)等待事件發(fā)生。fds
數(shù)組包含了所有需要監(jiān)控的文件描述符,2
表示總共有兩個(gè)文件描述符(標(biāo)準(zhǔn)輸入和套接字)。-1
表示poll
函數(shù)將阻塞直到至少有一個(gè)文件描述符上有事件發(fā)生。如果poll
調(diào)用失?。ǚ祷刂敌∮?),則打印錯(cuò)誤信息并退出循環(huán)。
if(fds[1].revents & POLLRDHUP){//假如發(fā)生了關(guān)閉對(duì)端連接
printf("server close the connection..\n");
break;
}
else if(fds[1].revents & POLLIN){//假如sockfd文件發(fā)生可讀,則讀取服務(wù)器傳來(lái)數(shù)據(jù)
memset(readbuf , '\0' , buffer_size);
recv(fds[1].fd , readbuf , buffer_size -1 , 0);//接收數(shù)據(jù)
if(ret <= 0){// 如果接收失敗或?qū)Ψ疥P(guān)閉了連接
printf("server close the connection..\n");
break;
}
printf("%s\n",readbuf);//打印數(shù)據(jù)
}
if(fds[0].revents & POLLIN){//標(biāo)準(zhǔn)輸入文件描述符可讀,說(shuō)明我們需要寫(xiě)入數(shù)據(jù)
ret = splice(0 , NULL , pipefd[1] , NULL ,32768 , SPLICE_F_MORE | SPLICE_F_MOVE);//從標(biāo)準(zhǔn)輸入寫(xiě)入數(shù)據(jù)到管道寫(xiě)端
ret = splice(pipefd[0] , NULL , sockfd , NULL ,32768 , SPLICE_F_MORE | SPLICE
ret = splice(0 , NULL , pipefd[1] , NULL ,32768 , SPLICE_F_MORE | SPLICE_F_MOVE);//從標(biāo)準(zhǔn)輸入寫(xiě)入數(shù)據(jù)到管道寫(xiě)端
ret = splice(pipefd[0] , NULL , sockfd , NULL ,32768 , SPLICE_F_MORE | SPLICE_F_MOVE);//從管道讀端將數(shù)據(jù)傳輸?shù)絪ockfd
printf("ok");
}
}
close(sockfd);
return 0;
}
這段代碼檢查套接字(
sockfd
)上的事件。如果套接字上有POLLRDHUP
事件,表示對(duì)方已經(jīng)關(guān)閉了連接,服務(wù)器會(huì)關(guān)閉對(duì)應(yīng)的連接并退出循環(huán)。如果套接字上有POLLIN
事件,表示有數(shù)據(jù)可讀,服務(wù)器會(huì)讀取數(shù)據(jù)并打印。這段代碼是客戶(hù)端程序主循環(huán)的最后一部分,它處理標(biāo)準(zhǔn)輸入(
0
)上的數(shù)據(jù),并通過(guò)管道(pipefd
)將其傳輸?shù)教捉幼郑?code>sockfd)上。
ret = splice(0 , NULL , pipefd[1] , NULL ,32768 , SPLICE_F_MORE | SPLICE_F_MOVE);
:
splice
是一個(gè)系統(tǒng)調(diào)用,用于直接在內(nèi)核空間復(fù)制數(shù)據(jù),避免了用戶(hù)空間和內(nèi)核空間之間的數(shù)據(jù)拷貝。- 第一個(gè)參數(shù)是源文件描述符,這里是從標(biāo)準(zhǔn)輸入
0
。- 第二個(gè)參數(shù)是源文件描述符的偏移量,這里為
NULL
,表示從文件開(kāi)始讀取。- 第三個(gè)參數(shù)是目標(biāo)文件描述符,這里是對(duì)應(yīng)的管道寫(xiě)端
pipefd[1]
。- 第四個(gè)參數(shù)是目標(biāo)文件描述符的偏移量,這里為
NULL
,表示從文件開(kāi)始寫(xiě)入。- 第五個(gè)參數(shù)是傳輸?shù)臄?shù)據(jù)量,這里為
32768
,是一個(gè)系統(tǒng)定義的常量,表示最多傳輸32768字節(jié)。- 第六個(gè)參數(shù)是
SPLICE_F_MORE
,表示這只是一個(gè)中間步驟,還有更多的數(shù)據(jù)要傳輸。- 第七個(gè)參數(shù)是
SPLICE_F_MOVE
,表示傳輸?shù)臄?shù)據(jù)是從內(nèi)核緩沖區(qū)直接移動(dòng),而不是復(fù)制。
ret = splice(pipefd[0] , NULL , sockfd , NULL ,32768 , SPLICE_F_MORE | SPLICE_F_MOVE);
:
- 類(lèi)似地,這段代碼使用
splice
系統(tǒng)調(diào)用來(lái)從管道讀端pipefd[0]
傳輸數(shù)據(jù)到套接字sockfd
。
printf("ok");
:
- 打印"ok"表示數(shù)據(jù)傳輸成功。
循環(huán)繼續(xù)執(zhí)行,重復(fù)上述操作,直到連接被關(guān)閉或出現(xiàn)錯(cuò)誤。
close(sockfd);
:
- 關(guān)閉套接字
sockfd
,釋放資源。
return 0;
:
- 程序返回0,表示正常退出。
這個(gè)客戶(hù)端程序通過(guò)
poll
系統(tǒng)調(diào)用來(lái)監(jiān)控標(biāo)準(zhǔn)輸入和套接字的事件,并通過(guò)splice
系統(tǒng)調(diào)用來(lái)高效地傳輸數(shù)據(jù)。它使用管道作為中間緩沖區(qū),以避免在用戶(hù)空間和內(nèi)核空間之間進(jìn)行數(shù)據(jù)拷貝。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-860784.html
???好啦!到這里這篇文章就結(jié)束啦,關(guān)于實(shí)例代碼中我寫(xiě)了很多注釋?zhuān)绻蠹疫€有不懂得,可以評(píng)論區(qū)或者私信我都可以哦
!! 感謝大家的閱讀,我還會(huì)持續(xù)創(chuàng)造網(wǎng)絡(luò)編程相關(guān)內(nèi)容的,記得點(diǎn)點(diǎn)小愛(ài)心和關(guān)注喲!
?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-860784.html
到了這里,關(guān)于【linux高性能服務(wù)器編程】項(xiàng)目實(shí)戰(zhàn)——仿QQ聊天程序源碼剖析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!