第一篇博文,fighting!!
本人生物医学工程专业,这专业什么都要学,什么都学不好。既要和程序猿一样学编程,还要和电信自动化的学硬件,偶尔也要解剖个小兔子,材料方向的还要天天做实验!坑!!
学到现在还是个编程小白,奈何导师让我开发一个路由器,之前从未接触网络编程这类东西,没学过Linux,多线程...没用过ubuntu、Eclipse,还得重新学C++,心里好苦,mmp!!
大一学了c语言,当时可是考了85呢!好像没什么卵用。自从大二上了c++的课,开始堕落,那老师上课只会对着ppt读,每次上课都想睡觉。混到大三那个暑假,决定考研,因为感觉找不到工作啊......还好那年我运气超级好,6级考了425(多一分我也不要),初试340,复试录取比例1:1(16年的孩子伤不起,英语、政治都泄题了。考研数学30周年,史上最难,哭,好多大神数学单科不过线),最终考上广东的一所985。所以,有时候人走运了...
写博文目的是为了整理知识,纪念一下自己走过的弯路,给自己一点积极的反馈!!
下面开始正文!!!!!!!!!!!!!
准备工作 系统:ubuntu 12.04 web服务器:Apache 软件:Eclipse C++cgi库 cgicc
cgi(通用网关接口)这个东西,话说好像很过时了,问了几个学计算机的同学,他们都不学这个的啊!!但是cgi在网关路由器中会用到!
cgi的基础知识可参考下面这两篇文章
http://www.runoob.com/cplusplus/cpp-web-programming.html。
http://blog.csdn.net/dsg333/article/details/9674723
文章里有实例代码,内容讲的也挺全,这两篇都是C++的,用的cgi库是cgicc。我主要讲碰到的问题和错误。
C++ CGI库
在真实的实例中,您需要通过 CGI 程序执行许多操作。这里有一个专为 C++ 程序而编写的 CGI 库,我们可以从 ftp://ftp.gnu.org/gnu/cgicc/ 上下载这个 CGI 库,并按照下面的步骤安装库:
$tar xzf cgicc-X.X.X.tar.gz
$cd cgicc-X.X.X/
$./configure --prefix=/usr
$make
$make install
您可以点击 C++ CGI Lib Documentation,查看相关的库文档。
一、什么是CGI
CGI(The Common Gateway Interface):通用网关接口,定义web服务器和客户脚本进行信息交互的一系列标准。
二、 web浏览器
为了了解CGI的概念,让我们来看看当我们单击一个超链接来浏览一个特定的web页或URL的时候,背后会发生什么事?
(1)浏览器首先会链接HTTP web 服务器并且请求一个URL 页面;
(2) WEB服务器将会解析这个URL并且查询请求的文件名,如果找到了请求文件服务器就会将这个文件发送回浏览器,否则发送回一个包含错误信息提示的页面指示你请求的是一个服务器并不包含的文件。
(3)WEB浏览器将接受来自服务器端的响应,并且向发出请求的用户显示接收到的文件。
然而,HTTP服务器也可能会以如何这种方式进行配置,那就是无论什么时候接受到对特定目录下的文件的请求,服务器不会将这个文件发送回客户端,而是它作为一个程序被服务器执行,并产生出输出发送回客户端浏览器进行显示。
CGI是一个标准化的协议,能够使应用程序(通常称为CGI程序或CGI脚本)同web服务器和客户端进行交互。CGI程序能够用Python, PERL, Shell, C or C++等语言来实现。
三、 CGI程序结构图
下图简单的展示了CGI程序架构
四、 web服务器配置
在你着手写CGI程序之前,确保你的web服务器支持CGI程序并且配置成处理CGI程序。所有的能够被HTTP服务器执行的CGI程序都被存放在预先配置好的目录下面,这个目录叫做CGI目录,并且按照约定命名为 /var/www/cgi-bin,并且约定CGI文件的后缀名为.cgi ,尽管它们是c++可执行文件。
一般的,Apache 服务器在/var/www/cgi-bin目录下配置文件来运行CGI程序,如果你想要声明另外的目录来运行CGI脚本,网上说的都是需要修改httpd.conf 文件中的部分内容:
- <Directory "/var/www/cgi-bin">
- AllowOverride None
- Options ExecCGI
- Order allow,deny
- Allow from all
- </Directory>
- <Directory "/var/www/cgi-bin">
- Options All
- </Directory>
但是在Linux Ubuntu系统下,httpd.conf 文件放在 /etc/apache2 路径下,打开确实空白的,一脸懵逼!
后来发现 /etc/apache2/sites-available 路径下有一个 default文件。修改如下:
ScriptAlias /cgi-bin/ /var/www/cgi-bin/<Directory "/var/www/cgi-bin">
AllowOverride all
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
AddHandler cgi-script .cgi .pl .py
Order allow,deny
Allow from all
</Directory>
注意:你直接打开default文件进行修改是没有用的。
Ctrl+ALT+T,输入命令 sudo gedit /etc/apache2/sites-available/default 可以修改。如下图所示:
五、HTTP头信息
这行字符串 ”Content-type:text/html\r\n\r\n”是发送回浏览器的HTTP报文头部的一部分,所有的HTTP报文头部都有如下格式:
- HTTP Field Name: Field Content
- For Example
- Content-type: text/html\r\n\r\n
cout << "Content-type:text/html\r\n\r\n";
\r\n\r\n很重要,\n\n也可以,错了web服务器就不知道你发送的是一个http请求。注意\的方向,别搞错了。另外,linux系统和windows系统的斜杠不一样。linux系统中/var/www/cgi-bin,路径斜杠方向是这样的;windows系统中C:\Program Files\Microsoft,路径斜杠方向相反。
报文头部如果错了,假设你的报文是这样的:cout << "Content-type:text/html";
恭喜你,你会看到如下错误!
记住:报错一定要看错误信息,你才能知道你错在哪里!!
错误信息在哪看呢? /var/log/apache2 路径下有一个 error.log 文件,打开error.log文件,拉到最底下,就会看到更新的错误栏:
[Sat Jan 13 22:09:15 2018] [error] [client 127.0.0.1] malformed header from script. Bad header=<head>: lxfcgi-c++
注:lxfcgi-c++ 是我的cgi可执行程序文件名。
如果error.log文件增加了下面两行错误信息:
[Sun Jan 14 13:08:19 2018] [error] (13)Permission denied: exec of '/var/www/cgi-bin/lxfcgi-c++' failed
[Sun Jan 14 13:08:19 2018] [error] [client 127.0.0.1] Premature end of script headers: lxfcgi-c++
错误信息表示,你的cgi程序的没有权限。命令行输入命令:sudo chmod 777 lxfcgi-c++。
补充linux中chmod更改文件权限命令知识,需要用到sudo命令暂时提升使用权限。
1、chmod是linux中更改文件权限的命令,常用的有:
(1) sudo chmod u+x 你的文件名
(2) sudo chmod g+x 你的文件名
(3) sudo chmod o+x 你的文件名
其中的 u、g、o 分别代表的就是 user、group、others,"+"代表赋予权限,x (executable)代表可执行权。
2、sudo chmod 777 代表什么:
三位数字分别代表 user、group、others 的权限,可读(r)、可写(w)、可执行(x)的权限分别用数字 4、2、1
表示,数字7是权限 4、2、1 的和,777 即代表 user、group、others
均具有可读(r)、可写(w)、可执行(x)的权限,为最高权限。
简单的 URL 实例:Get 方法请看http://www.runoob.com/cplusplus/cpp-web-programming.html
命名管道(fifo)是进程间通信方式。看了很多博文,一般都是堵塞模式。
堵塞模式,参考 http://blog.csdn.net/xiajun07061225/article/details/8471777。
非阻塞模式,参考 https://segmentfault.com/a/1190000010739303。
这两篇博文里的程序实例,代码都是ok的,亲身试验过。命名管道的知识里面也有讲,我这就懒得讲了。我后面的代码是把cgi参考博文里的代码和fifo参考博文的代码结合起来。
cgi fifo write端代码:
cgi_fifo_write.cpp,这是一个C++程序。后面的fifo read端是C程序。
- // cgi_fifo_write.cpp
-
- #include "cgififowrite.h"
- #define FIFO_MODE O_CREAT|O_NONBLOCK|O_RDWR
- #define FILE_MODE O_WRONLY | O_NONBLOCK
- using namespace std;
- using namespace cgicc;
-
- //*********fifo读函数******
- void MainProgram::fifo_write()
- {
- //非堵塞模式fifo通信 write端
- const char *fifo_name = "/media/disk0A/workspace/fiforeadnoblock/Debug/my_fifo";
- int fd;
- char w_buf[100];
- char w_t_buf[50];
- const char *hstr = "hello world";
- //mkfifo()函数生成fifo文件
- if(mkfifo(fifo_name, FIFO_MODE) < 0 && (errno != EEXIST))
- {
- perror("failed to create fifo server");
- exit(1);
- }
-
- char cmd[100];
- sprintf(cmd, "chmod 704 %s", fifo_name);
- system(cmd);
- int nwrite;
- //open()函数打开fifo文件,O_WRONLY | O_NONBLOCK 非堵塞式只读
- fd = open(fifo_name, FILE_MODE);
- if (fd == -1)
- {
- if (errno == ENXIO)
- {
- printf("open errop;no reading process\n");
- } else {
- perror("open error");
- exit(EXIT_FAILURE);
- }
- }
- /*if (argc >= 2)
- {
- strcpy(w_t_buf, argv[1]);
- } else {
- strcpy(w_t_buf, hstr);
- }*/
- strcpy(w_t_buf, hstr);
- int i=0;
- int n;
- time_t tp;
- //设置write次数,我下面设置了6次
- while(i++<6)
- {
- time(&tp);
- n=sprintf(w_buf, "Process %d ils sending %s at %s", getpid(), w_t_buf, ctime(&tp));
- printf("Start write Time %d 。FD = %d \n", i , fd);
- //***********************
- // 如果 fiforeadnoblock 可执行程序运行结束,那么read() 函数就会结束,读端的fd会关闭,下面的write()函数就会返回-1
- nwrite = write(fd, w_buf, n);
- //***********************
- printf("nwrite = %d \n" , nwrite);
- if (nwrite < 0)
- {
- if (errno == EAGAIN)
- {
- printf("the fifo has not been read yet.Please try later\n");
- } else {
- printf("error sorry!!\n");
- exit(1);
- }
- }
- printf("Send Message to FIFO: %s \n", w_buf );
- sleep(1);
- }
- close(fd);
- printf("Send finished\n" );
- }
-
-
- //*****************************
- //********cgi处理函数************
- void MainProgram::cgi_Request()
- {
- Cgicc formData;
- //报文头表示返回的是一个html网页
- cout << "Content-type:text/html\r\n\r\n";
-
- //报文头表示返回的是一个JSON数据,得添加json的库
- //cout << "Content-type:application/json\r\n\r\n";
- //cout << "{\"code\":1,\"message\":\"fifo right\"}\n";
- cout << "<meta charset='utf-8'>\n";
- cout << "<html>\n";
- cout << "<head>\n";
- cout << "<meta charset='utf-8'>\n";
- cout << "<title>CGI与FIFO通信</title>\n";
- cout << "</head>\n";
- cout << "<body>\n";
- cout << "CGI函数没问题" << endl;
- cout << "<br/>\n";
- cout << "</body>\n";
- cout << "</html>\n";
- //exit(EXIT_SUCCESS);
- }
-
- MainProgram::~MainProgram() {
- }
-
- MainProgram::MainProgram() {
- }
-
- int main(int argc, char *argv[]) {
- MainProgram m_MainProgram;
- m_MainProgram.cgi_Request();
- m_MainProgram.fifo_write();
- printf("back to main func\n");
- return 0;
- }
cgififowrite.h
- /*
- * cgififowrite.h
- * Created on: 2018-1-15
- * Author: lxf
- */
- #ifndef CGIFIFOWRITE_H_
- #define CGIFIFOWRITE_H_
-
- #define MYPCDEBUG 0
-
- #include <string.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <iostream>
- #include <signal.h>
- #include <pthread.h>
- #include <signal.h>
- #include <assert.h>
- #include <sys/shm.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <limits.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <time.h>
- #include <vector>
- #include <cgicc/CgiDefs.h>
- #include <cgicc/Cgicc.h>
- #include <cgicc/HTTPHTMLHeader.h>
- #include <cgicc/HTMLClasses.h>
- //头文件有些可能用不到
-
- class MainProgram{
- public:
- MainProgram();
- ~MainProgram();
- void fifo_write();
- void cgi_Request();
- int init();
- int start();
- };
-
- #endif /* CGIFIFOWRITE_H_ */
fifo read端代码:
fifo_read_noblock.c,这是一个C程序。
- //
- // Created by : Harris Zhu
- // Filename : fifo_read_noblock.cpp
- // Created On : 2017-08-17 16:46
- // Last Modified :lxf
- // Update Count : 2018-01-15 10:46
- //
- //=======================================================================
-
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- #define OPEN_MODE O_RDONLY | O_NONBLOCK
- #define FIFO_MODE O_CREAT|O_RDWR|O_NONBLOCK
-
- int main(int argc, char** argv) {
- const char *fifo_name = "/media/disk0A/workspace/fiforeadnoblock/Debug/my_fifo";
- char buf_r[100];
- int fd;
- int nread;
- int res;
- if (((res=mkfifo(fifo_name, FIFO_MODE)) < 0) && (errno != EEXIST))
- {
- printf("can not creat fifoserver %d :\n", res, errno);
- exit(1);
- }
- printf("preparing for reading bytes...\n");
- char cmd[100];
- sprintf(cmd, "chmod 704 %s", fifo_name);
- system(cmd);
- fd = open(fifo_name, OPEN_MODE); //非堵塞模式打开fifo文件
- if (fd == -1) {
- perror("error in openning fifo server");
- exit(1);
- }
- int i=0;
- //int len;
- while (i++<11) //设置读的次数
- {
- memset(buf_r, 0, sizeof(buf_r));
- if ((nread = read(fd, buf_r, sizeof(buf_r))) < 0) { // read()函数
- if (errno == EAGAIN)
- {
- printf("no data yet\n");
- sleep(1);
- }
- } else {
- if(nread > 0)
- {
- printf("read %s from FIFO %d \n", buf_r, i);
- }
- sleep(1);
- }
- printf("FD = %d 。time %d \n",fd,i);
- }
- //pause();
- printf("Close FD \n");
- //sleep(3); //挂起3秒
- close(fd);
- unlink(fifo_name);
- return 0;
- }
我用的Elicpse,不用自己写makefile文件,下面我简单提一下,因为我也不太会!具体的makefile文件写法,网上有教程。
上面第一个C++程序的makefile文件要加 LIBS =-lcgicc 这一行,表示动态链接到cgicc库。Elicpse是默认自动生成makefile文件的,所以你加了这一行,再次Build Project,makefile会自动更新,你再打开会发现你加的LIBS =-lcgicc 这一行没了。右击项目,属性Properties,点击C/C++ Build,把Generate Makefiles automatically的勾去掉,再次Build Project就可以了。假设你两个程序都编译好了,生成了两个可执行文件,分别为fifocgi-c++(C++程序生成的)和fiforeadnoblock(C程序生成的)。把fifocgi-c++(它是一个cgi可执行程序)放到你的cgi目录/var/www/cgi-bin下。
先运行fiforeadnoblock可执行文件,再在浏览器输入http://localhost/cgi-bin/fifocgi-c++。结果如下:
好像运行成功了,但是cgi_fifo_write.cpp中的fifo_write()没有运行啊,同样fiforeadnoblock没有读到数据啊!!打开error.log文件,发现有错误:
failed to create fifo server: Permission denied
权限的问题,然后我设置权限:sudo chmod 777 fifocgi-c++
再试还是同样的错误,这是怎么回事,我明明设置了最高权限啊?!
后来上网查,发现默认的cgi的权限其实是nobody,表明它没有权限调用write()函数和一些系统命令!!
然后我把权限设置成:sudo chmod u+s fifocgi-c++。先运行fifocgi-c++看看,结果如下
这个说明权限已经ok的啦,只不过没有先运行fiforeadnoblock可执行文件。
我先运行fiforeadnoblock可执行文件,再在浏览器输入http://localhost/cgi-bin/fifocgi-c++。结果如下:
运行成功!!!!!!!!!
但是运行了几次,你会发现,如果你让fiforeadnoblock先运行7、8秒再输入http://localhost/cgi-bin/fifocgi-c++,结果如下
怎么回事?!!这是因为fiforeadnoblock已经运行结束,read()函数已经结束了,读端的fd已经关闭了。所以fifocgi-c++里的write()函数返回的是-1。
那怎么避免呢?
你可以修改write()函数while循环的次数,之前是6,你改成3。再修改read()函数while循环的次数,之前是11,你修改为6。这样读和写差了3次,因为我设置的是隔一秒读一次和写一次,所以读和写差了3次,相当于差了3秒。还差3秒的话还是会出现前面的情况。那你可以在读端的fd关闭close(fd)之前加sleep(3),让它挂起3秒。fifo_read_noblock.c修改如下:
本文到此结束,内容有点多。欢迎大家一起讨论交流,如果还有人学cgi的话...
时间问题,没有仔细检查,可能有一些错误,请大家多包涵,欢迎大家提出问题和指出错误!!