1、pipe函数:
pipe函数用来创建一个管道,以实现进程之间通信。
#include<unistd.h>
int pipe(int fd[2]);
pipe函数的参数是一个包含两个int 类型整数的数组指针。该函数成功时返回0,并将一对打开的文件描述符值填入其参数指向的数组。如果失败,则返回-1并设置errno。
pipe函数创建的这两个文件描述符fd[0],fd[1]分别构成管道的两端,往fd[1]写入的数据可以从fd[0]读出。并且fd[1]只能用于往管道中写入数据,fd[0]只能用于从管道读出数据。而不能反过来使用。若是想实现双向通信,就必须要使用两个管道。
管道内部传输的数据是字节流,这和TCP字节流的概念相同。但二者又有细微的区别。应用程程序能往一个TCP连接中写入多少字节的数据,取决于对方的接受通告的大小和本端的拥塞窗口的大小。
socket的基础API中有一个skcketpair函数。它能够方便地创建双向管道。定义如下:
#include<sys/types.h>
#include<sys/socket.h>
int socketpair(int domain,int type,int protocol,int fd[2]);
socketpair前三个参数的含义与socket系统调用的三个参数完全相同,但domain只能使用unix本地与协议族af——unix,因为我们仅能在本地使用这个双向管道。最后一个参数则和pipe系统调用的参数一样,只不过socketpair创建的这对文件描述符都是即可读有可写的。socketpair成功时返回0失败时返回-1并设置errno。
2、readv函数和writefv函数。
readv函数将数据从文件描述符读到分散地内存块中,即分散读:writev函数将多块分散的内存数据一并读写入文件描述符,即集中写。定义如下:
#include<sys/uio.h>
ssize_t readv(int fd,const struct iovec* vector,int count);
ssize_t writev(int fd,const struct iovec* vector,int count);
fd是被操作的目标文件描述符。vector参数类型是iovec结构数组。我们再第五章讨论过了结构体iovec,该结构体描述一块内存区。count参数式vector数组的长度,既有多少块内存数据需要从fd读出或写到fd。readv和writev在成功时返回读出/写入fd字节数,失败则返回-1并设置errno。
2、dup函数和dup2函数。
我们希望把标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接。还可以通过下面的用与复制文件的dup或者dup2函数来实现:
#include<unistd.h>
int dup(int file_descroptor);
int dup2(int file_descriptor);
dup函数创建一个新的文件描述符,该描述符和原有文件描述符file_descriptor指向相同的文件,管道,或者网络连接。并且dup返回的文件描述符总是去系统当前可用的最小的整数值。dup2和dup相似,不过他将返回一个不小于file_descriptor_two的整数值。dup和dup2系统调用失败时则返回-1并设置errno。
通过dup和dup2创建的文件描述符并不继承元文件描述符的属性,比如close-on-exec和non-blocking等。。。
3、什么事CGI服务器?
在互联网行业中有一个词叫CGI,这个是服务器中的专业术语,所以很多人不知道CGI是什么。CGI是一个用于定Web服务器与外部程序之间通信方式的标准,使得外部程序能生成HTML、图像或者其他内容,而服务器处理的方式与那些非外部程序生成的HTML、图像或其他内容的处理方式是相同的。因此,CGI程序册仅使你能生成表态内容而能生动态内容。使用CGI的原因在于它是一个定义良好并被广泛支持的标准,没有CGI就不可能实现动态的Web页面,除非使用一些服务器中提供的特殊方法(如今,也有除CGI之外的其他技术逐渐在成为标准)。
在互联网行业中有一个词叫CGI,这个是服务器中的专业术语,所以很多人不知道CGI是什么。CGI是一个用于定Web服务器与外部程序之间通信方式的标准。
CGI 服务器原理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
# 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 ip_address port_num " ,basename(argv[ 0 ])); return 1 ; } const char * p = argv[ 1 ]; int port = atoi(argv[ 2 ]); struct sockaddr_in address; bzero(&address,sizeof(address)); address.sin_family=AF_INET; inet_pton(AF_INET,ip,&address.sin_addr); address.sin_port=htons(port); int sock=socket(PF_INET,SOCK_STREAM, 0 ); assert(sock>= 0 ); int ret=bind(sock,(struct sockaddr*)&address,sizeof(address)); assert(ret!=- 1 ); ret=listen(sock, 5 ); struct sockaddr_in client; socklen_t client_addrlength = sizeof(client); int connfd=accept(sock,(struct sockaddr*)&client,sizeof(client)); if (connfd< 0 ) { printf( "errno is %d " ,errno); } else { close(STDOUT_FILENO); dup(connfd); printf( "abcd
" ); close(connfd); } close(sock); return 0 ; } |
在代码清单中,我们先关闭标准输出文件描述符STDOUT_FILENO(其值是1)然后赋值socket文件描述符connfd。因为dup总是返回系统中最小的可用文件描述符,所以他的返回值实际上是1,即之前关闭的标准输出文件描述符的值。这样一来,服务器输出到标准输出的内容(abcd)就会直接发送到与客户连接对应的socket上,因此printf调用的输出将被客户端得到。这就是CGI服务器的基本工作原理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
# 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" , "50 internet" ); int main( int argc,char * argv[]) { if (argc<= 3 ) { printf( "usage: ip_address port_num
" ); return 1 '' } const char * ip=argv[ 1 ]; int port=atoi(argv[ 2 ]); ................. } |
3、sendfile函数:
sendfile函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这种被称为零拷贝。sendfile函数定义如下:
#include<sys/sendfile.h>
ssize_t sendfile(int out_fd,int in_fd,off_t* offser,size_t count);
int fd 参数是带读出内容的文件描述符,out_ fd参数是待写入内容的文件描述符。offset参数指定从读入文件流的哪个位置开始读,如果为空,则使用读入文件默认的起始位置count参数指定在文件描述符in_fd 和 out_fd 之间传输的字节数。sendfile成功时返回传输的字节数,失败时返回-1,并设置errno。该函数的man手册明确指出,in_fd必须是一个支持类似mmap函数的文件描述符,即他必须指向真实的文件,不能是socket和管道:而out_fd则必须是一个socket。由此可见,sendfile几乎是专门为在网络上传输文件而设计的。
如下,利用sendfile函数将服务器上的一个文件传送给客户端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
# 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/types.h> # include <sys/stat.h> # include <fcntl.h> # include <sys/sendfile.h> int main( int argc,char * argv[]) { if (argc<= 3 ) { printf( "usage : %s ip_address port_file file" ); 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); struct sockaddr_in address; bzero(&address,sizeof(address)); address.sin_family=AF_INET; inet_pton(AF_INET,ip,&address.sin_addr); address.sin_port=htons(port); int sock= socket(PF_INET,SOCK_STREAM, 0 ); assert(sock>= 0 ); 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 : %d
" ,errno); } else { sendfile(connfd,filefd,NULL,stat_buf.st_size); close(connfd); } close(sock); return 0 ; } |
在代码中,我们将目标文件作为第三个参数传递给服务器程序,客户telnet到该服务器上即可获得该文件。
4、mmap函数和munmao函数。
mmap函数用于申请一段内存空间。我们可以将这段内存作为进程之间通信的共享内存,也可以将文件直接映射到其中。munmap函数则释放有mmap创建的这段内存空间。他们的定义如下:
1
2
3
|
# include <sys/mman.h> void * mmap( void * start ,size_t length, int prot, int flags, int fd, off_t offset); int munmap( void * start,size_t length); |
start参数允许用户使用某个特定的地址作为这段内存的起始地址。如果他被指为NULL,则系统自动分配一个地址。length参数指定内存段的长度。prot参数用来设置内存的访问权限。它可以取以下几个值的按位或:
PROT_READ,内存段可读
PROT_WRITE,内存段可写
PROT_EXEC,内存段可执行
PROT_NONE,内存段不能被访问
flags参数控制内存段内容被修改后程序的行为。它可以被设置为下标中的某些值的按位或。
fd参数是被映射文件对应的文件描述符。它一般通过open系统调用获得。offset参数设置从文件的何处开始映射(对于不需要读入整个文件的情况)。
mmap函数成功时返回指向目标内存区域的指针,失败则返回MAP_FAILED(void * )-1)并设置errno。munmap函数成功时返回0,失败则返回-1并设置errno。
我们将在以后,在讨论如何利用mmap函数实现进程间的共享内存。
5、solice函数:
splice函数用于在两个文件描述符之间移动数据,也是零拷贝操作。splice函数定义如下:
#include<fcnt1.h>
ssize_t splice(int fd_in , loff_t * off_in , int fd_out , loff_t * off_out , size_t len, unsigned int flages );
fd_in 参数是待输入数据的文件描述符。如果fd_in 是一个管道文件描述符,那么off_in参数必须设置为NULL。如果fd_in 不是一个管道文件描述符(比如scoket),那么off_in表示从输入数据流的何处开始读取数据。此时,若off_in 被设置为NULL,则表示从输入数据流的当前偏移位置读入;若off_inn部位NULL,则它将指出具体的偏移位置,fd_out/off_out参数的含义与fd_in/off_in相同,不过用于输出数据流。len参数指定移动数据长度;flags参数则控制数据如何移动,它可以被设置为下标内容中的某些值的按位或。
使用splice函数时,fd_in和fd_out必须至少有一个是管道文件描述符。splice函数调用成功时返回移动字节的数量。他可能返回0,表示没有数据需要流动,这发生在管道中读取数据(fd_in是管道文件描述符)而该管道没有被写入任何数据时。splice函数失败时则返回-1并设置一个errno。常见的errno如表:
下面我们使用splice函数来实现一个零拷贝的回射服务器,它将客户端发送的数据原样返回给客户端,具体实现如下: