共计 6634 个字符,预计需要花费 17 分钟才能阅读完成。
这个程序是看到的一本书上 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#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\n<html><body>request file not found</body></html>\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/ 更换一下!