linux網路程式設計九:splice函式,高效的零拷貝

jasonliuvip發表於2014-03-30

最近在看《linux高效能伺服器程式設計》,在此做個日記,以激勵自己,同時分享於有需要的朋友。


1. splice函式

#include <fcntl.h>
ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);

splice用於在兩個檔案描述符之間移動資料, 也是零拷貝。

fd_in引數是待輸入描述符。如果它是一個管道檔案描述符,則off_in必須設定為NULL;否則off_in表示從輸入資料流的何處開始讀取,此時若為NULL,則從輸入資料流的當前偏移位置讀入。

fd_out/off_out與上述相同,不過是用於輸出。

len引數指定移動資料的長度。

flags引數則控制資料如何移動:

  • SPLICE_F_NONBLOCK:splice 操作不會被阻塞。然而,如果檔案描述符沒有被設定為不可被阻塞方式的 I/O ,那麼呼叫 splice 有可能仍然被阻塞。
  • SPLICE_F_MORE:告知作業系統核心下一個 splice 系統呼叫將會有更多的資料傳來。
  • SPLICE_F_MOVE:如果輸出是檔案,這個值則會使得作業系統核心嘗試從輸入管道緩衝區直接將資料讀入到輸出地址空間,這個資料傳輸過程沒有任何資料拷貝操作發生。

2. 使用splice時, fd_in和fd_out中必須至少有一個是管道檔案描述符。

呼叫成功時返回移動的位元組數量;它可能返回0,表示沒有資料需要移動,這通常發生在從管道中讀資料時而該管道沒有被寫入的時候。

失敗時返回-1,並設定errno


3. 程式碼:通過splice將客戶端的內容讀入到管道中, 再從管道中讀出到客戶端,從而實現高效簡單的回顯服務。整個過程未執行recv/send,因此也未涉及使用者空間到核心空間的資料拷貝。

//使用splice實現的回顯伺服器
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>


int main(int argc, char **argv)
{

	if (argc <= 2) {
		printf("usage: %s ip port\n", basename(argv[0]));
		return 1;
	}
	
	const char *ip = argv[1];
	int port = atoi(argv[2]);

	struct sockaddr_in address;
	bzero(&address, sizeof(address));
	address.sin_family = AF_INET;
	address.sin_port = htons(port);
	inet_pton(AF_INET, ip, &address.sin_addr);

	int sock = socket(PF_INET, SOCK_STREAM, 0);
	assert(sock >= 0);
	
	int reuse = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

	int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
	assert(ret != -1);

	ret = listen(sock, 5);
	assert(ret != -1);
	
	struct sockaddr_in client;
	socklen_t client_addrlength = sizeof(client);
	
	int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
	if (connfd < 0) {
		printf("errno is: %s\n", strerror(errno));
	}
	else {
		int pipefd[2];
				
		ret = pipe(pipefd);  //建立管道
		assert(ret != -1);
		
                //將connfd上的客戶端資料定向到管道中
		ret = splice(connfd, NULL, pipefd[1], NULL,
						32768, SPLICE_F_MORE | SPLICE_F_MOVE);
		assert(ret != -1);
		
                //將管道的輸出定向到connfd上
		ret = splice(pipefd[0], NULL, connfd, NULL,
						32768, SPLICE_F_MORE | SPLICE_F_MOVE);
		assert(ret != -1);				
		
		close(connfd);
	}

	
	close(sock);




	return 0;
}






相關文章