高级IO函数

高级IO函数

基本的GCI服务器

CGI(Common Gateway Interface)服务器是一种通过 Web 服务器执行外部程序的标准接口。它允许 Web 服务器调用外部程序来处理客户端的请求,并将程序的输出发送回客户端浏览器。CGI 是连接 Web 服务器和其他软件(通常是动态生成的程序或脚本)的桥梁。

基本上,当 Web 服务器接收到一个对动态内容的请求时,它可以调用与之相关联的 CGI 脚本或程序,将用户的请求传递给该程序进行处理。该程序生成动态内容,然后将其输出返回给 Web 服务器,最终发送到用户的浏览器。

一些关键点和特性:

  1. 动态内容生成: CGI 允许服务器调用外部程序来生成动态内容,而不是直接返回静态文件。
  2. 编程语言无关性: CGI 接口是独立于编程语言的,因此可以使用多种编程语言编写 CGI 脚本,包括但不限于 Perl、Python、C、C++等。
  3. 处理表单数据: CGI 可以用于处理来自 HTML 表单的数据。用户提交表单后,服务器可以调用 CGI 脚本来处理表单数据并生成相应的响应。
  4. 与 Web 服务器的通信: CGI 脚本与 Web 服务器通过环境变量和标准输入输出进行通信。
  5. 安全性考虑: 由于 CGI 允许执行外部程序,必须小心处理用户输入,以防止潜在的安全漏洞。

虽然 CGI 曾经是 Web 开发的主要方式,但随着时间的推移,其他技术和方法,如 FastCGI、mod_perl、PHP、ASP.NET 等,已经逐渐取代了纯粹的 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
54
#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_number\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;
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\n", errno );
}
else
{
close( STDOUT_FILENO );
dup( connfd );
printf( "abcd\n" );
close( connfd );
}

close( sock );
return 0;
}

关闭标准输出(STDOUT_FILENO):

1
close(STDOUT_FILENO);
  • close 函数用于关闭文件描述符。在这里,关闭标准输出文件描述符 STDOUT_FILENO,这是与标准输出相关联的文件描述符。

复制连接套接字到标准输出:

1
2
dup(connfd);
printf( "abcd\n" );
  • dup 函数用于复制文件描述符。在这里,将连接套接字的文件描述符 connfd 复制到标准输出的文件描述符 STDOUT_FILENO。这样,标准输出将与连接套接字相关联。
  • dup函数会创建一个新的文件描述符,该描述符和原文件描述符指向相同的文件‘管道或网络连接
  • 现在,printf 函数将数据写入标准输出,实际上是写入了连接套接字,因为标准输出已经被重定向到了连接套接字。

测试:

客户端

web服务器上集中写

readv函数是将数据从文件描述符读到分散的内存块中,writev函数将分散内存数据一并写入文件描述符中

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#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/uio.h>
#define BUFFER_SIZE 1024
//定义两种http状态码和状态信息
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];

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\n", errno );
}
else
{
//用于保存http应答的状态行,头部字段和一个空行的缓存区
char header_buf[ BUFFER_SIZE ];

memset( header_buf, '\0', BUFFER_SIZE );
//用于存放目标文件内容的应用程序缓存
char* file_buf;
//用于获取目标文件的属性
struct stat file_stat;
//记录目标文件是否有效
bool valid = true;
int len = 0;
//目标文件不存在
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 )
{
int fd = open( file_name, O_RDONLY );
//动态分配缓存区大小为目标文件的大小+1,将目标文件读入缓存
file_buf = new char [ 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 )
{
//将http应答状态行划入buf
ret = snprintf( header_buf, BUFFER_SIZE-1, "%s %s\r\n", "HTTP/1.1", status_line[0] );
len += ret;
//加入Content-Length头部字段
ret = snprintf( header_buf + len, BUFFER_SIZE-1-len,
"Content-Length: %d\r\n", file_stat.st_size );
len += ret;
//加入空行,"\r\n" 是回车和换行符,表示换行。
ret = snprintf( header_buf + len, BUFFER_SIZE-1-len, "%s", "\r\n" );
//利用weitev将header_buf和file_buf一并写出
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 );
}
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;
}

send( connfd, header_buf, strlen( header_buf ), 0 );

  • send 函数用于通过指定的套接字 (connfd) 发送数据。
  • header_buf 是包含待发送数据的缓冲区的地址。
  • strlen(header_buf) 是待发送数据的长度,使用 strlen 函数计算字符串的长度。
  • 0 是标志参数,表示不使用特殊的发送选项。

测试:

服务器端运行程序
客户端telnet连接接收

sendfile函数传输文件

修改schedule函数

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_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 );

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\n", errno );
}
else
{
sendfile( connfd, filefd, NULL, stat_buf.st_size );
close( connfd );
}

close( sock );
return 0;
}

​ 与之前的方法比,不用分配缓存就完成了文件的传输 。实验效果和上面一样

用splice函数实现回射服务器

将客户端发送的数据原样返回客户端

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 <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] );

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\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 );
//将connfd上流入的客户数据定向到管道中
assert( ret != -1 );
//将管道的数据定向到connfd客户连接文件描述符
//前面是管道描述符所以第二个参数是null
ret = splice( pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );
assert( ret != -1 );
close( connfd );
}

close( sock );
return 0;
}

这段代码涉及到 Linux 中的管道(pipe)和 splice 系统调用,用于实现零拷贝数据传输。

int pipefd[2];

  • 定义了一个整型数组 pipefd 用于存储管道的两个文件描述符。

  • 使用 pipe 函数创建了一个无名管道。pipefd[0] 用于读取,pipefd[1] 用于写入

  • 使用 spliceconnfd(客户端连接套接字)的数据传输到管道的写入端 (pipefd[1])。

  • 32768 是指定的最大传输字节数。

  • SPLICE_F_MORE | SPLICE_F_MOVE 是传输标志,其中 SPLICE_F_MORE 表示可能还有更多的数据,SPLICE_F_MOVE :合适的话按整页内存移动数据

实验效果:

发送K

TEE 同时输出数据到终端和文件

tee在两个管道文件描述符之间复制数据,零拷贝

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
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

int main( int argc, char* argv[] )
{
if ( argc != 2 )
{
printf( "usage: %s <file>\n", argv[0] );
return 1;
}
int filefd = open( argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666 );
assert( filefd > 0 );

int pipefd_stdout[2];
int ret = pipe( pipefd_stdout );
assert( ret != -1 );

int pipefd_file[2];
ret = pipe( pipefd_file );
assert( ret != -1 );
//将标准输入内容输入管道pipefd_stdout
ret = splice( STDIN_FILENO, NULL, pipefd_stdout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );
assert( ret != -1 );
//将pipefd_stdout输出内容复制给pipefd_file输入
ret = tee( pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK );
assert( ret != -1 );
//将pipefd_file输出重定向到filefd
ret = splice( pipefd_file[0], NULL, filefd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );
assert( ret != -1 );
//将pipefd_stdout重定向到STDOUT_FILENO标准输出
ret = splice( pipefd_stdout[0], NULL, STDOUT_FILENO, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );
assert( ret != -1 );

close( filefd );
close( pipefd_stdout[0] );
close( pipefd_stdout[1] );
close( pipefd_file[0] );
close( pipefd_file[1] );
return 0;
}