这个程序是看到的一本书上socket编程章节的课后题,题目内容很多,具体可见链接:
https://www.bookstack.cn/read...
实现一个简单的Web服务器myhttpd。服务器程序启动时要读取配置文件/etc/myhttpd.conf,其
中需要指定服务器监听的端口号和服务目录,我设置的是如下:
Port=8000
Directory=/var/www
在Directory即服务器的/var/www放入你想要加载到client(此处是浏览器)的文件,如图片或者是html文件。可以看到这是我的ecs里面放的要加载的文件。
多进程传输的server的代码如下所示,注意是在linux环境下编程和执行!
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define RECV_PORT 8000
#define QLEN 10
#define MAX_SIZE 1024
char browser_com_buf[256];
int mylisten (void)
{
int listenfd;
int err;
socklen_t addrlen;
struct sockaddr_in server_addr;
char *address = "101.132.101.121";
listenfd = socket ( AF_INET, SOCK_STREAM, 0 ); /*creat a new socket*/
if ( listenfd < 0 )
{
printf ( "listen error\n" );
return -1;
}
memset ( &server_addr, 0, sizeof ( server_addr ) );
server_addr.sin_family = AF_INET; /*IPV4*/
server_addr.sin_port = htons ( RECV_PORT );
server_addr.sin_addr.s_addr = htonl ( INADDR_ANY );
addrlen = sizeof ( server_addr );
err = bind ( listenfd, ( struct sockaddr * )&server_addr, addrlen );
if ( err < 0 )
{
printf ( "bind error\n" );
return -2;
}
err = listen ( listenfd, QLEN );
if ( err < 0 )
{
printf ( "listen error" );
return -3;
}
return listenfd;
}
int myaccept ( int listenfd )
{
int clientfd;
int err;
socklen_t addrlen;
struct sockaddr_in client_addr;
addrlen = sizeof ( client_addr );
clientfd = accept ( listenfd, ( struct sockaddr * )&client_addr, &addrlen );
if ( clientfd < 0 )
{
printf ( "accept error\n" );
return -1;
}
return clientfd;
}
void browser_com_analysis ( char *com, char *buf )
{
int i = 0;
unsigned int flag = 0;
char *locate;
locate = strchr ( com, '/' );
//依次检索com字符串中的每一个字符,直到遇见字符 /,返回第一次出现字符/的位置
if ( locate == NULL )
{
printf ( "not find\n" );
exit ( 1 );
}
else
{
//把命令逐字符存到buf中
while ( *locate != ' ' )
{
buf[i++] = *locate;
locate++;
}
buf[i] = '\0';
}
//printf ( "%s\n", buf );
}
//判断文件的权限和状态,是纯txt/html还是图片,还是CGI程序(可执行文件)
int state_estimat ( char *buf )
{
int len;
unsigned int flag = 0;
int i = 0, j = 0;
char buf1[256];
char *image_style = ".jpg";
char file_name[256] = { "/var/www" };
struct stat statbuf;
len = strlen ( buf );
memset ( buf1, '\0', sizeof ( buf1 ) );
while ( i < len )
{
//将文件格式后缀的内容存到buf[1],如test.jpg将.jpg存到buf1中
if ( buf[i] == '.' )
{
flag = 1;
}
if ( flag == 1 )
{
if ( buf[i] == ' ' || buf[i] == '\n' )
{
break;
}
else
{
buf1[j++] = buf[i];
}
}
i++;
}
//printf ( "%s%d\n", buf1, len );
if ( len == 0 )
{
printf ( "http have not send comand\n" );
exit ( 1 );
}
//GET 之后只有一个命令/,所以len == 1,在本例中/实际上是/var/www
//Web服务器应该把该目录下的索引页(默认是index.html)发给浏览器.
//也就是把/var/www/index.html发给浏览器
else if ( len == 1 )
{
return 1;
}
else if ( len > 1 )
{
//将参数 src 字符串复制到参数 dest 所指的字符串尾部;dest 最后的结束字符 NULL 会被覆盖掉.
//并在连接后的字符串的尾部再增加一个 NULL。
strcat ( file_name, buf );
stat ( file_name, &statbuf );
//stat()用来将参数file_name 所指的文件状态, 复制到参数statbuf所指的结构中
if ( statbuf.st_mode & S_IXOTH ) //其他用户具有可执行权限
{
//如果具有可执行权限,则执行该文件,将结果发送到浏览器上
return 4;
}
else
{
//不具有可执行权限则判断图片还是文件
if ( strcmp ( buf1, image_style ) == 0 )
{
return 2; //浏览器请求的是图片
}
return 3; //浏览器请求的是文件
}
}
}
int find_file ( char *buf, int clientfd )
{
int state;
char my_file_buf[65536 * 2];
int n;
char fault[] = { "HTTP/1.1 404 Not Found\nContent-Type: text/html\nrequest file not found\n\n" };
char head[] = { "HTTP/1.1 200 OK\nContent-Type: text/html\n\n" };
char head2[] = { "HTTP/1.1 200 OK\nContent-Type: image/jpg\n\n" };
char head3[] = { "HTTP/1.1 200 OK\n\n" };
char file_name[] = { "/var/www" };
memset ( &my_file_buf, '\0', MAX_SIZE );
state = state_estimat ( buf );
//Web服务器应该把该目录下的索引页(默认是index.html)
if ( state == 1 ) //server send file to browser
{
int filefd;
filefd = open ( "/var/www/index.html", O_RDWR );//返回0表示成功
//参数 pathname 指向欲打开的文件路径/var/www/index.html.
if ( filefd < 0 )
{
printf ( "find file failed\n" );
write ( clientfd, fault, strlen ( fault ) );
close ( clientfd );
}
else
{
n = read ( filefd, my_file_buf, MAX_SIZE );//成功返回读到的字节数
//read()会把参数fd 所指的文件传送count个字节到buf 指针所指的内存中
if ( n < 0 )
{
printf ( "read the root file failed\n" );
exit ( 1 );
}
strcat ( head, my_file_buf );//将从文件获取到的内容放到head头内容 的下面
printf ( "%s", head );
write ( clientfd, head, strlen ( head ) );//成功返回实际写入的字节数
//把参数buf 所指的内存写入count 个字节到参数fd 所指的文件内
close ( clientfd );
}
}
else if ( state == 2 )
{
printf ( "picture\n" );
int picture_fd;
int n;
write ( clientfd, head2, strlen ( head2 ) );
memset ( &my_file_buf, '\0', sizeof ( my_file_buf ) );
strcat ( file_name, buf );
picture_fd = open ( file_name, O_RDONLY ); //只读打开图片文件
if ( picture_fd < 0 )
{
printf ( "open picture failed\n" );
exit ( 1 );
}
n = read ( picture_fd, my_file_buf, sizeof ( my_file_buf ) );
//将fd所指的文件传送count个字节到my_file_buf,此处文件是图片,所以传的是二进制码
if ( n < 0 )
{
printf ( "read picture data failed\n" );
exit ( 1 );
}
write ( clientfd, my_file_buf, sizeof ( my_file_buf ) );
close ( clientfd );
}
else if ( state == 3 )
{
//3是指普通不可执行文件
printf ( "file\n" );
int file_fd;
int n;
write ( clientfd, head, strlen ( head ) );//head是“text/html”
memset ( &my_file_buf, '\0', sizeof ( my_file_buf ) );
strcat ( file_name, buf );//buf是指浏览器请求的文件名放在file_name /var/www之后
file_fd = open ( file_name, O_RDONLY );
if ( file_fd < 0 )
{
printf ( "open unCGI-file failed\n" );
exit ( 1 );
}
n = read ( file_fd, my_file_buf, sizeof ( my_file_buf ) );
if ( n < 0 )
{
printf ( "read unCGI-file failed \n" );
exit ( 1 );
}
write ( clientfd, my_file_buf, sizeof ( my_file_buf ) );
}
else if ( state == 4 )
{
//是可执行文件
printf ( "executable file\n" );
pid_t pid;
pid = fork ();
if ( pid < 0 )
{
printf ( "creat child failed\n" );
exit ( 1 );
}
else if ( pid > 0 ) //parent
{
int stateval;
waitpid ( pid, &stateval, 0 );
//waitpid()会暂时停止目前进程的执行, wait有信号来到或子进程结束然后彻底清除该子进程
//如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值.
//子进程的结束状态值会由参数status返回
//wait等待第一个终止的子进程,而waitpid可以通过pid参数指定等待哪一个子进程
close ( clientfd );
}
else //child用来执行该可执行文件并返回结果到浏览器输出
{
int err;
char *argv[1];//char* argv[1]指针数组,只有一个指针元素
strcat ( file_name, buf );
argv[1] = file_name;
dup2 ( STDOUT_FILENO, clientfd );
//用dup2重定向子进程的标准输出到客户端socket
err = execv ( "/bin/bash", argv );//执行CGI 如/bin/bash test.sh
//其标准输出会向dup2指定的那样定向到浏览器
//不带字母p(表示path)的exec函数第一个参数必须是程序的相对路径或绝对路径
if ( err < 0 )
{
printf ( "executable file failed\n" );
exit ( 1 );
}
close ( clientfd );
}
}
}
int main ( int argc, char *argv[] )
{
int listenfd, clientfd;
int filefd, n;
char recvbuf[MAX_SIZE];
char data_buf[MAX_SIZE];
listenfd = mylisten();
if ( listenfd < 0 )
{
exit ( 0 );
}
printf ( "listening and listen fd is %d......\n", listenfd );
while ( 1 )
{
clientfd = myaccept ( listenfd );
printf ( "accept success......\n" );
if ( clientfd < 0 )
{
exit ( 1 );
}
memset ( &recvbuf, '\0', MAX_SIZE );
memset ( &data_buf, '\0', MAX_SIZE );
n = read ( clientfd, recvbuf, MAX_SIZE );
printf ( "read OK\n" );
if ( n < 0 )
{
printf ( "read error\n" );
exit ( 2 );
}
filefd = open ( "/tmp/read_html.txt", O_RDWR | O_CREAT | O_TRUNC ); //open o new file
write ( filefd, recvbuf, n ); //save the http information to filefd
browser_com_analysis ( recvbuf, data_buf );//将收到的命令解析出/+后面有用的保存在data_buf
find_file ( data_buf, clientfd );//对浏览器的请求内容进行判断,是普通html还是picture或是CGI
}
close ( clientfd );
return 0;
}
让我们来看一下效果:
1、首先在linux中运行该程序,此处我使用的vscode远程连接linux,可以直接运行server,如果你是在centos默认的界面或者vim编辑,可以使用gcc http_sever.c -o http_sever生成可执行文件 和 ./http_sever来执行该文件,可以看到服务器开始listen监听是否有连接请求:
2、打开浏览器,输入自己的服务器IP,例如我的是http://101.132.101.121 ,如果端口号不是80,我设置的是8000,则输入 http://101.132.101.121:8000,回车向服务器发出请求,即可看到你要加载到浏览器的数据!
3、回到服务器端,查看数据传输成功的相关回执,此处我用的是vscode远程连接代替vim编辑器和linux命令行,便于debug。第一张图是随便写的测试用的index.html文件,并导入了一张图片。
可以看到html文件和图片均接收成功,我们在网页中也看到了,而可执行文件打不开,因为这个文件夹刚好是tomcat的部署文件夹,所以有一些部署文件在里面,客户端试着打开他们当然失败了!
所以将如果你像我一样已经部署过tomcat的话,建议将部署文件夹var/www/更换一下!