pipe
創(chuàng)建一個管道祝峻,實現(xiàn)進程間通信魔吐。
dup、dup2
復制文件描述符
實例:cgi服務器原理
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char *argv[]) {
if (argc <= 2) {
printf("usage: %s id_address prot_number \n", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
//字符串轉(zhuǎn)換成整型數(shù)的一個函數(shù)
int port = atoi(argv[2]);
//創(chuàng)建一個專用 IPV4 socket 地址
struct sockaddr_in address;
//bzero功能:置字節(jié)字符串a(chǎn)ddress的前sizeof( address )個字節(jié)為零且包括‘\0’
bzero(&address, sizeof(address));
address.sin_family = AF_INET;//地址組
//inet_pton:將用字符串表示ip地址轉(zhuǎn)換成網(wǎng)絡字節(jié)序整數(shù)表示的ip地址莱找。 成功返回1 失敗返回0
inet_pton(AF_INET, ip, &address.sin_addr);
//小端轉(zhuǎn)大端 酬姆、主機字節(jié)序轉(zhuǎn)換網(wǎng)絡字節(jié)序
address.sin_port = htons(port);
//創(chuàng)建一個socket PF_INET:ipv4 SOCK_STREAM:流服務
int sock = socket(PF_INET, SOCK_STREAM, 0);
//判斷是否失敗 失敗返回-1并設置errno 成功返回socket文件描述符
assert(sock >= 0);
//命名socket 將address所指的地址分配給未命名的 sock 變量(文件描述符)
int ret = bind(sock, (struct sockaddr *) &address, sizeof(address));
// bind 成功返回0 失敗返回-1 并設置errno
assert(ret !=-1);
//監(jiān)聽socket sock參數(shù):指定被監(jiān)聽的socket backlog參數(shù):提示內(nèi)核監(jiān)聽隊列的最大長度
ret = listen(sock,5);
//listen 成功返回0 失敗返回-1 并設置errno
assert(ret !=-1);
//創(chuàng)建一個專用 IPV4 socket 地址
struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client);
int connfd = accept(sock,(struct sockaddr*)&client ,&client_addrlength);
if (connfd < 0)
{
printf("error is :%d\n",errno);
}
else
{
//關閉標準輸出文件描述符
close(STDOUT_FILENO);
//復制socket文件描述符connfd
dup(connfd);
printf("adcd\n");
close(connfd);
}
//關閉socket
close(sock);
return 0;
}
B主機執(zhí)行該程序
A主機執(zhí)行該程序
readv() 、wirtev()函數(shù)用法
readv和writev函數(shù)和前面提過的readmsg和writemsg函數(shù)類似奥溺,也是用來對數(shù)據(jù)的集中寫和分散讀辞色,相當于前面兩個函數(shù)的簡化版。舉一個例子來說明浮定,在Web服務器解析完HTTP請求后如果客戶端請求的文件存在并且有權(quán)限時相满,就需要返回一個HTTP首部狀態(tài)碼和狀態(tài)信息,然后再返回該文件桦卒,但是我們考慮效率問題立美,如果每次我們都需要將兩個不相關的存儲空間合并到一起再發(fā)送勢必會很影響效率,所以我們可以事先將HTTP不同的頭部存儲好方灾,找到文件后使用sendv函數(shù)直接發(fā)送即可建蹄。我們建立一個index.html文件模擬一下,服務器代碼如下:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#define BUFFER_SIZE 1024
static const char *status_line[2] = {"200 OK", "500 Internal server error"};
int main(int argc, char *argv[]) {
if (argc <= 3) {
printf("usage: %s ip_address port_number filename\n", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
const char *file_name = argv[3];
//創(chuàng)建一個專用 IPV4 socket 地址
struct sockaddr_in address;
//bzero功能:設置字符串a(chǎn)ddress的前sizeof(address)個字節(jié)為0且包括'\0'
bzero(&address, sizeof(address));
//地址族
address.sin_family = AF_INET;
//將用字符串表示ip地址轉(zhuǎn)換成網(wǎng)絡字節(jié)序整數(shù)表示的ip地址裕偿,成功返回1 失敗返回0
inet_pton(AF_INET, ip, &address.sin_addr);
//小端轉(zhuǎn)大端洞慎、主機字節(jié)序轉(zhuǎn)換網(wǎng)絡字節(jié)序
address.sin_port = htons(port);
//創(chuàng)建一個socket PF_INET:ipv4 SOCK_STREAM:流服務
int sock = socket(PF_INET, SOCK_STREAM, 0);
//判斷是否失敗 失敗返回-1 并設置error 成功返回socket文件描述符
assert(sock >= 0);
//命名socket 并將address所指向的地址分配給未命名的sock變量(文件描述符)
int ret = bind(sock, (struct sockaddr *) &address, sizeof(address));
//判斷是否失敗 失敗返回-1 并設置error
assert(ret != -1);
//監(jiān)聽socket sock: 指定被監(jiān)聽的socket backlog:提示內(nèi)核監(jiān)聽隊列的最大長度
ret = listen(sock, 5);
//成功返回0 失敗返回-1 并設置error
assert(ret != -1);
//創(chuàng)建一個專用ipv4 socket地址
struct sockaddr_in client;
//獲取client類型長度
socklen_t client_addrlength = sizeof(client);
//接收連接 成功返回一個新的 socket 失敗返回-1 并設置error
//sock : 監(jiān)聽套接字,即服務器端創(chuàng)建的用于listen的socket描述符嘿棘。
//client : struct sockaddr*這是一個結(jié)果參數(shù)劲腿,它用來接受一個返回值,這返回值指定客戶端的地址
//client_addrlength : 描述 client 的長度 socklen_t*
int connfd = accept(sock, (struct sockaddr *) &client, &client_addrlength);
if (connfd < 0) {
printf("errno is: %d\n", errno);
} else {
//用于保存http應答的狀態(tài)行鸟妙、頭部字段和一個空行的緩存區(qū)
char header_buf[BUFFER_SIZE];
//將已開辟內(nèi)存空間 header_buf 的首 BUFFER_SIZE 個字節(jié)的值設為值 '\0'焦人。
memset(header_buf, '\0', BUFFER_SIZE);
//用于存放目標文件內(nèi)容的應用程序緩存
char *file_buf;
//用于獲許目標文件的屬性挥吵,比如是否為目錄,文件大小等
struct stat file_stat;
//記錄目標文件是否是有效文件
bool valid = true;
//緩存區(qū)header_buf 目前已經(jīng)使用了多少個字節(jié)的空間
int len = 0;
//stat(): 獲取一些文件相關的信息 filename : 文件路徑名 file_stat 垃瞧;保存文件信息的結(jié)構(gòu)體 成功返回0蔫劣。失敗返回-1 并設置error
if (stat(file_name, &file_stat) < 0)//目標文件不存在
{
valid = false;
} else {
if (S_ISDIR(file_stat.st_mode))//目標文件是一個目錄
{
valid = false;
} else if (file_stat.st_mode & S_IROTH)//當前用戶有讀取目標文件的權(quán)限
{
//動態(tài)分配緩存區(qū) file_buf,并指定其大小為目標文件的大小file_stat.st_size + 1 ,然后講目標文件讀入緩存區(qū) file_buf
int fd = open(file_name, O_RDONLY);
file_buf = new char [ file_stat.st_size + 1 ];
// char file_buf[file_stat.st_size + 1];
memset(file_buf, '\0', file_stat.st_size + 1);
if (read(fd, file_buf, file_stat.st_size) < 0) {
valid = false;
}
} else {
valid = false;
}
}
if (valid) {
//下面這部分內(nèi)容講http應答的狀態(tài)行 坪郭、"Content-Length" 頭部字段和一個空行依次加入header_buf種
//snprintf() :最多從源串中拷貝size-1個字符到目標串中个从,然后再在后面加一個0。所以如果目標串的大小為size的話歪沃,將不會溢出嗦锐。
ret = snprintf(header_buf, BUFFER_SIZE - 1, "%s %s\r\n", "HTTP/1.1", status_line[0]);
len += ret;
ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len,
"Content-Length: %d \r\n", (int)file_stat.st_size);
len += ret;
ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len, "%s", "\r\n");
//利用writev將header_buff 和 file_buf 的內(nèi)容一并寫出
struct iovec iv[2];
iv[0].iov_base = header_buf;
iv[0].iov_len = strlen(header_buf);
iv[1].iov_base = file_buf;
iv[1].iov_len = file_stat.st_size;
ret = writev(connfd, iv, 2);
}
//如果目標文件無效 則通知客戶端 服務器發(fā)生了 內(nèi)部錯誤
else {
ret = snprintf(header_buf, BUFFER_SIZE - 1, "%s %s\r\n", "HTTP/1.1", status_line[1]);
len += ret;
ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len, "%s", "\r\n");
send(connfd, header_buf, strlen(header_buf), 0);
}
close(connfd);
delete [] file_buf;
}
close(sock);
return 0;
}
sendfile
函數(shù)可以在兩個文件描述符之間直接傳遞數(shù)據(jù)(完全在內(nèi)核中操作)沪曙,從而避免了內(nèi)核緩沖區(qū)和用戶緩沖區(qū)之間的數(shù)據(jù)拷貝奕污,效率高,被稱為零拷貝液走。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#define BUFFER_SIZE 1024
static const char *status_line[2] = {"200 OK", "500 Internal server error"};
int main(int argc, char *argv[]) {
if (argc <= 3) {
printf("usage: %s ip_address port_number filename\n", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
const char *file_name = argv[3];
int filefd = open(file_name, O_RDONLY);
assert(filefd > 0);
struct stat stat_buf;
fstat(filefd,&stat_buf);
//創(chuàng)建一個專用 IPV4 socket 地址
struct sockaddr_in address;
//bzero功能:設置字符串a(chǎn)ddress的前sizeof(address)個字節(jié)為0且包括'\0'
bzero(&address, sizeof(address));
//地址族
address.sin_family = AF_INET;
//將用字符串表示ip地址轉(zhuǎn)換成網(wǎng)絡字節(jié)序整數(shù)表示的ip地址碳默,成功返回1 失敗返回0
inet_pton(AF_INET, ip, &address.sin_addr);
//小端轉(zhuǎn)大端、主機字節(jié)序轉(zhuǎn)換網(wǎng)絡字節(jié)序
address.sin_port = htons(port);
//創(chuàng)建一個socket PF_INET:ipv4 SOCK_STREAM:流服務
int sock = socket(PF_INET, SOCK_STREAM, 0);
//判斷是否失敗 失敗返回-1 并設置error 成功返回socket文件描述符
assert(sock >= 0);
//命名socket 并將address所指向的地址分配給未命名的sock變量(文件描述符)
int ret = bind(sock, (struct sockaddr *) &address, sizeof(address));
//判斷是否失敗 失敗返回-1 并設置error
assert(ret != -1);
//監(jiān)聽socket sock: 指定被監(jiān)聽的socket backlog:提示內(nèi)核監(jiān)聽隊列的最大長度
ret = listen(sock, 5);
//成功返回0 失敗返回-1 并設置error
assert(ret != -1);
//創(chuàng)建一個專用ipv4 socket地址
struct sockaddr_in client;
//獲取client類型長度
socklen_t client_addrlength = sizeof(client);
//接收連接 成功返回一個新的 socket 失敗返回-1 并設置error
//sock : 監(jiān)聽套接字缘眶,即服務器端創(chuàng)建的用于listen的socket描述符嘱根。
//client : struct sockaddr*這是一個結(jié)果參數(shù),它用來接受一個返回值巷懈,這返回值指定客戶端的地址
//client_addrlength : 描述 client 的長度 socklen_t*
int connfd = accept(sock, (struct sockaddr *) &client, &client_addrlength);
if (connfd < 0) {
printf("errno is: %d\n", errno);
} else {
sendfile(connfd,filefd,NULL,stat_buf.st_size);
close(connfd);
}
close(sock);
return 0;
}
splice
函數(shù)用于在兩個文件描述符之間移動數(shù)據(jù)该抒,也是零拷貝。
在使用該函數(shù)時顶燕、fd_in 和 fd_out 必須至少有一個是管道文件描述符凑保。splice函數(shù)調(diào)用成功返回移動字節(jié)的數(shù)量,可能返回0涌攻,表示沒有數(shù)據(jù)需要移動欧引,比如從管道中讀取數(shù)據(jù)(fd_in是管道文件描述符)而該管道沒有被寫入任何數(shù)據(jù)時。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
int main(int argc, char *argv[]) {
if (argc <= 2) {
printf("usage: %s ip_address port_number \n", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
//創(chuàng)建一個專用 IPV4 socket 地址
struct sockaddr_in address;
//bzero功能:設置字符串a(chǎn)ddress的前sizeof(address)個字節(jié)為0且包括'\0'
bzero(&address, sizeof(address));
//地址族
address.sin_family = AF_INET;
//將用字符串表示ip地址轉(zhuǎn)換成網(wǎng)絡字節(jié)序整數(shù)表示的ip地址恳谎,成功返回1 失敗返回0
inet_pton(AF_INET, ip, &address.sin_addr);
//小端轉(zhuǎn)大端芝此、主機字節(jié)序轉(zhuǎn)換網(wǎng)絡字節(jié)序
address.sin_port = htons(port);
//創(chuàng)建一個socket PF_INET:ipv4 SOCK_STREAM:流服務
int sock = socket(PF_INET, SOCK_STREAM, 0);
//判斷是否失敗 失敗返回-1 并設置error 成功返回socket文件描述符
assert(sock >= 0);
//命名socket 并將address所指向的地址分配給未命名的sock變量(文件描述符)
int ret = bind(sock, (struct sockaddr *) &address, sizeof(address));
//判斷是否失敗 失敗返回-1 并設置error
assert(ret != -1);
//監(jiān)聽socket sock: 指定被監(jiān)聽的socket backlog:提示內(nèi)核監(jiān)聽隊列的最大長度
ret = listen(sock, 5);
//成功返回0 失敗返回-1 并設置error
assert(ret != -1);
//創(chuàng)建一個專用ipv4 socket地址
struct sockaddr_in client;
//獲取client類型長度
socklen_t client_addrlength = sizeof(client);
//接收連接 成功返回一個新的 socket 失敗返回-1 并設置error
//sock : 監(jiān)聽套接字,即服務器端創(chuàng)建的用于listen的socket描述符惠爽。
//client : struct sockaddr*這是一個結(jié)果參數(shù)癌蓖,它用來接受一個返回值,這返回值指定客戶端的地址
//client_addrlength : 描述 client 的長度 socklen_t*
int connfd = accept(sock, (struct sockaddr *) &client, &client_addrlength);
if (connfd < 0) {
printf("errno is: %d\n", errno);
} else {
int pipefd[2];
assert(ret != -1);
ret = pipe(pipefd);
ret = splice(connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
assert(ret != -1);
ret = splice(pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
assert(ret != -1);
close(connfd);
}
close(sock);
close(ret);
return 0;
}