游双的 第6、7、8章
第6章 高级I/O函数 && 第7章 Linux服务器程序规范 && 第8章 高性能服务器程序框架
1.使用printf发送数据(使用dup将标准IO重定向到socket)
使用printf发送数据
#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;
}
接收的数据
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define BUF_SIZE 1024
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 server_address;
bzero( &server_address, sizeof( server_address ) );
server_address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &server_address.sin_addr );
server_address.sin_port = htons( port );
int sockfd = socket( PF_INET, SOCK_STREAM, 0 );
assert( sockfd >= 0 ); //如果表达式为假,那么就打印错误并终止程序。assert一般用于调试,可以添加#define NDEBUG 来禁用assert调用
if ( connect( sockfd, ( struct sockaddr* )&server_address,sizeof( server_address ) ) < 0 )
{
printf( "connection failed\n" );
}
else
{
char buffer[ BUF_SIZE ];
memset( buffer, '\0', BUF_SIZE ); // 用来给某一块内存空间进行赋值的
int ret = recv( sockfd, buffer, BUF_SIZE-1, 0 );
printf( "got %d bytes of normal data '%s'\n", ret, buffer );
}
close( sockfd );
return 0;
}
2.readv函数(分散读)和writev函数(集中写)
writev函数将多块分散的内存数据一并写入文件描述符中,在本代码中就是使用writev发送两个字符数组。
#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
{
char header_buf[ BUFFER_SIZE ]; /*用于保存HTTP应答的状态行、头部字段和一个空行的缓存区*/
memset( header_buf, '\0', BUFFER_SIZE );
char* file_buf; /*用于存放目标文件内容的应用程序缓存*/
struct stat file_stat; /*用于获取目标文件的属性,比如是否为目录,文件大小等*/
// struct stat这个结构体是用来描述一个linux系统文件系统中的文件属性的结构
bool valid = true; /*记录目标文件是否是有效文件*/
int len = 0; /*缓存区header_buf目前已经使用了多少字节的空间*/
if( stat( file_name, &file_stat ) < 0 ) /*目标文件不存在*/ // stat()函数获取file_name文件的属性结构
{
valid = false;
}
else // 将文件存入一个字符数组中
{
if( S_ISDIR( file_stat.st_mode ) ) /*目标文件是一个目录*/
{
valid = false;
}
else if( file_stat.st_mode & S_IROTH ) /*当前用户有读取目标文件的权限*/
{
/*动态分配缓存区file_buf,并指定其大小为目标文件的大小file_stat.st_size
加1,然后将目标文件读入缓存区file_buf中*/
int fd = open( file_name, O_RDONLY );
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 ) //将fd指向的文件,存入file_buf中。
{
valid = false;
}
}
else
{
valid = false;
}
}
if( valid )
{
/*下面这部分内容将HTTP应答的状态行、“Content-Length”头部字段和一个空行依次加入header_buf中*/
ret = snprintf( header_buf, BUFFER_SIZE-1, "%s %s\r\n", "HTTP/1.1", status_line[0] ); // snprintf将字符串"%s %s\r\n"存入header_buf字符数组中。
len += ret;
ret = snprintf( header_buf + len, BUFFER_SIZE-1-len,
"Content-Length: %ld\r\n", file_stat.st_size );
len += ret;
ret = snprintf( header_buf + len, BUFFER_SIZE-1-len, "%s", "\r\n" );
/*利用writev将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;
}
Kongming20上输入:./6-2testwritev.out 192.168.154.130 12345 6-2testwritev.cpp
ernest-laptop上输入:telnet 192.168.154.130 12345,ernest-laptop就可以获取到6-2testwritev.cpp的内容
3.用sendfile函数传输文件
sendfile函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝。
#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;
}
使用1中的接收数据的代码,即可接收到数据
4.使用splice函数实现的回射服务器
splice函数用于在两个文件描述符之间移动数据,也是零拷贝操作。
#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 ); /*创建管道*/
/*将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;
}
Kongming20上输入:./6-4testsplice.out 192.168.154.130 12345
ernest-laptop上输入:telnet 192.168.154.130 12345,然后随便输入一串字符,就可以得到相同字符的返回
5.tee函数同时输出数据到终端和文件的程序
tee函数在两个管道文件描述符之间复制数据,也是零拷贝操作。它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作
#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 );
//close( STDIN_FILENO );
// dup2( pipefd_stdout[1], STDIN_FILENO );
//write( pipefd_stdout[1], "abc\n", 4 );
/*将标准输入内容输入管道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的输出定向到标准输出,其内容和写入文件的内容完全一致*/
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;
}
本程序数据的流向如下:
首先STDIN_FILENO里的内容被放进管道pipefd_stdout中,此时STDIN_FILENO里的内容就没了,无法再读STDIN_FILENO来获取相同的内容。
然后为了让数据保留在管道pipefd_stdout中以便后续使用,故使用了tee函数将管道pipefd_stdout中的数据复制到管道pipefd_file,再将数据从管道pipefd_file传到 filefd。为什么不直接将管道pipefd_stdout的数据复制到 filefd?答:因为tee只能在两个管道之间复制数据,而filefd不是管道。
最后将保留再管道pipefd_stdout中的数据传到 STDOUT_FILENO
6.切换root用户为形参指定用户
// 切换root用户为形参指定用户
static bool switch_to_user( uid_t user_id, gid_t gp_id ) // 形参为目标用户ID,为目标组ID
{
if ( ( user_id == 0 ) && ( gp_id == 0 ) ) // 两个都等于零,代表是root用户
{
return false;
}
gid_t gid = getgid();
uid_t uid = getuid();
if ( ( ( gid != 0 ) || ( uid != 0 ) ) && ( ( gid != gp_id ) || ( uid != user_id ) ) ) // 当前用户如果是root或者目标用户时,就不进入if中。保证当前用户只能是root和目标用户
{
return false;
}
/*由于前面保证了当前用户只能是root和目标用户.如果不是root,则已经是目标用户*/
if ( uid != 0 )
{
return true;
}
if ( ( setgid( gp_id ) < 0 ) || ( setuid( user_id ) < 0 ) )
{
return false;
}
return true;
}
7.守护进程的编写
bool daemonize()
{
/*创建子进程,关闭父进程,这样可以使程序在后台运行*/
pid_t pid = fork();
if ( pid < 0 )
{
return false;
}
else if ( pid > 0 )
{
exit( 0 );
}
/*设置文件权限掩码。当进程创建新文件(使用open(const char*pathname,int flags,mode_t mode)系统调用)时,文件的权限将是mode&0777*/
umask( 0 );
/*创建新的会话,设置本进程为进程组的首领*/
pid_t sid = setsid();
if ( sid < 0 )
{
return false;
}
/*切换工作目录*/
if ( ( chdir( "/" ) ) < 0 )
{
/* Log the failure */
return false;
}
/*关闭标准输入设备、标准输出设备和标准错误输出设备*/
close( STDIN_FILENO );
close( STDOUT_FILENO );
close( STDERR_FILENO );
/*关闭其他已经打开的文件描述符,代码省略*/
/*将标准输入、标准输出和标准错误输出都定向到/dev/null文件*/
open( "/dev/null", O_RDONLY );
open( "/dev/null", O_RDWR );
open( "/dev/null", O_RDWR );
return true;
}
8.有限状态机
分析HTTP请求行和头部字段并返回不同的字符串:
本代码HTTP请求的一行(包括请求行和头部字段)为行,每一行都有一对回车(\r)换行(\n)符。
HTTP格式可参考:HTTP协议格式详解。其中Http请求消息结构如下:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#define BUFFER_SIZE 4096 /*读缓冲区大小*/
// 主状态机的两种可能状态,分别表示:当前正在分析请求行,当前正在分析头部字段
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };
// 从状态机的三种可能状态,即行的读取状态,分别表示:读取到一个完整的行、行出错和行数据尚且不完整
enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };
// 服务器处理HTTP请求的结果:
// NO_REQUEST表示请求不完整,需要继续读取客户数据;
// GET_REQUEST表示获得了一个完整的客户请求;
// BAD_REQUEST表示客户请求有语法错误;
// FORBIDDEN_REQUEST表示客户对资源没有足够的访问权限;
// INTERNAL_ERROR表示服务器内部错误;
// CLOSED_CONNECTION表示客户端已经关闭连接了
enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, FORBIDDEN_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
// 为了简化问题,我们没有给客户端发送一个完整的HTTP应答报文,而只是根据服务器的处理结果发送如下成功或失败信息
static const char* szret[] = { "I get a correct result\n", "Something wrong\n" };
// 从状态机:解析一行,得出是否从状态机的状态。
// 从状态机的三种可能状态,即行的读取状态,分别表示:读取到一个完整的行、行出错和行数据尚且不完整
LINE_STATUS parse_line(char* buffer, int& checked_index, int& read_index)
{
char temp;
// checked_index指向buffer(应用程序的读缓冲区)中当前正在分析的字节,
// read_index指向buffer中客户数据的尾部的下一字节。
// buffer中第0~checked_index字节都已分析完毕,第checked_index~(read_index-1)字节由下面的循环挨
// 个分析
for (; checked_index < read_index; ++checked_index)
{
// 获得当前要分析的字节
temp = buffer[checked_index];
// 如果当前的字节是“\r”,即回车符,则说明可能读取到一个完整的行
if (temp == '\r')
{
// 如果“\r”字符碰巧是目前buffer中的最后一个数据,那么这次分析没有读取到一个完整的行,\r后面应
// 该还有一个\n,返回LINE_OPEN以表示还需要继续读取客户数据才能进一步分析
if ((checked_index + 1) == read_index)
{
return LINE_OPEN;
}
// 如果下一个字符是“\n”,则说明我们成功读取到一个完整的行
else if (buffer[checked_index + 1] == '\n')
{
buffer[checked_index++] = '\0'; // 将\r置为\0
buffer[checked_index++] = '\0'; // 将\n置为\0
return LINE_OK;
}
// 否则的话,说明客户发送的HTTP请求存在语法问题
return LINE_BAD;
}
// 如果当前的字节是“\n”,即换行符,则也说明可能读取到一个完整的行
else if (temp == '\n')
{
if ((checked_index > 1) && buffer[checked_index - 1] == '\r')
{
buffer[checked_index - 1] = '\0';
buffer[checked_index++] = '\0';
return LINE_OK;
}
return LINE_BAD;
}
}
// 如果所有内容都分析完毕也没遇到“\r”字符,则返回LINE_OPEN,
// 表示还需要继续读取客户数据才能进一步分析
return LINE_OPEN;
}
// 分析请求行
HTTP_CODE parse_requestline(char* szTemp, CHECK_STATE& checkstate)
{
// 如果请求行中没有空格“\t”字符,则HTTP请求必有问题
char* szURL = strpbrk(szTemp, " \t"); // 返回char指针,指向szTemp中为\t的字符的位置
if (!szURL)
{
return BAD_REQUEST;
}
*szURL++ = '\0'; // 将空格置零,然后指针指向下一个字符
char* szMethod = szTemp;
if (strcasecmp(szMethod, "GET") == 0)
{
printf("The request method is GET\n"); // 仅支持GET方法
}
else
{
return BAD_REQUEST;
}
szURL += strspn(szURL, " \t"); // szURL所指位置为下一个空格。strspn( szURL, " \t" )表示在szURL中找到\t的下标。
char* szVersion = strpbrk(szURL, " \t");
if (!szVersion)
{
return BAD_REQUEST;
}
*szVersion++ = '\0';
szVersion += strspn(szVersion, " \t");
if (strcasecmp(szVersion, "HTTP/1.1") != 0)
{
return BAD_REQUEST;
}
if (strncasecmp(szURL, "http://", 7) == 0)
{
szURL += 7;
szURL = strchr(szURL, '/'); // 检索第一次出现'/'的位置
}
if (!szURL || szURL[0] != '/')
{
return BAD_REQUEST;
}
//URLDecode( szURL );
printf("The request URL is: %s\n", szURL);
checkstate = CHECK_STATE_HEADER;
return NO_REQUEST;
}
// 分析头部字段
HTTP_CODE parse_headers(char* szTemp)
{
// 遇到一个空行,说明我们得到了一个正确的HTTP请求
if (szTemp[0] == '\0') // 因为parse_line()将空行(回车换行)都弄成了\0
{
return GET_REQUEST;
}
else if (strncasecmp(szTemp, "Host:", 5) == 0) // 处理“HOST”头部字段
{
szTemp += 5; // 跳到Host:之后
szTemp += strspn(szTemp, " \t");
printf("the request host is: %s\n", szTemp);
}
else // 其他头部字段都不处理
{
printf("I can not handle this header\n");
}
return NO_REQUEST;
}
// 分析HTTP请求的入口函数
HTTP_CODE parse_content(char* buffer, int& checked_index, CHECK_STATE& checkstate, int& read_index, int& start_line)
{
LINE_STATUS linestatus = LINE_OK; // 记录当前行的读取状态
HTTP_CODE retcode = NO_REQUEST; // 记录HTTP请求的处理结果
while ((linestatus = parse_line(buffer, checked_index, read_index)) == LINE_OK) // 主状态机,用于从buffer中取出所有完整的行
{
char* szTemp = buffer + start_line; // start_line是行在buffer中的起始位置
start_line = checked_index; // 记录下一行的起始位置
switch (checkstate) // checkstate记录主状态机当前的状态
{
case CHECK_STATE_REQUESTLINE: // 第一个状态,分析请求行
{
retcode = parse_requestline(szTemp, checkstate);
if (retcode == BAD_REQUEST)
{
return BAD_REQUEST;
}
break;
}
case CHECK_STATE_HEADER: // 第二个状态,分析头部字段
{
retcode = parse_headers(szTemp);
if (retcode == BAD_REQUEST)
{
return BAD_REQUEST;
}
else if (retcode == GET_REQUEST)
{
return GET_REQUEST;
}
break;
}
default:
{
return INTERNAL_ERROR;
}
}
}
if (linestatus == LINE_OPEN) // 若没有读取到一个完整的行,则表示还需要继续读取客户数据才能进一步分析
{
return NO_REQUEST;
}
else
{
return BAD_REQUEST;
}
}
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 listenfd = socket(PF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(listenfd, 5);
assert(ret != -1);
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof(client_address);
int fd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength);
if (fd < 0)
{
printf("errno is: %d\n", errno);
}
else
{
char buffer[BUFFER_SIZE];
memset(buffer, '\0', BUFFER_SIZE);
int data_read = 0;
int read_index = 0;
int checked_index = 0;
int start_line = 0;
CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;
while (1)
{
data_read = recv(fd, buffer + read_index, BUFFER_SIZE - read_index, 0);
if (data_read == -1)
{
printf("reading failed\n");
break;
}
else if (data_read == 0)
{
printf("remote client has closed the connection\n");
break;
}
read_index += data_read;
HTTP_CODE result = parse_content(buffer, checked_index, checkstate, read_index, start_line);
if (result == NO_REQUEST)
{
continue;
}
else if (result == GET_REQUEST)
{
send(fd, szret[0], strlen(szret[0]), 0);
break;
}
else
{
send(fd, szret[1], strlen(szret[1]), 0);
break;
}
}
close(fd);
}
close(listenfd);
return 0;
}
运行和测试:
$ ./a.out 127.0.0.1 7777
# 开启另一个终端
$ curl -X GET http://localhost:7777
# 输出的结果:
The request method is GET
The request URL is: /
the request host is: localhost:7777
I can not handle this header
I can not handle this header
我们前面学习的都是发字符串操作,这就是最有用的操作。网络传输中多数的数据都是以字符串进行传输的,然后再对字符串的内容进行解析得到不同的结果。如图片链接会解析成具体的图片。