关于socket:c语言实现一个简单的web服务器借助http协议
这个程序是看到的一本书上socket编程章节的课后题,题目内容很多,具体可见链接:https://www.bookstack.cn/read...实现一个简略的Web服务器myhttpd。服务器程序启动时要读取配置文件/etc/myhttpd.conf,其中须要指定服务器监听的端口号和服务目录,我设置的是如下:Port=8000Directory=/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 1024char 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监听是否有连贯申请: ...