1. 程式人生 > >linux CGI與命名管道非堵塞模式通訊問題總結

linux CGI與命名管道非堵塞模式通訊問題總結

第一篇博文,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的基礎知識可參考下面這兩篇文章

文章裡有例項程式碼,內容講的也挺全,這兩篇都是C++的,用的cgi庫是cgicc。我主要講碰到的問題和錯誤。

C++ CGI庫

在真實的例項中,您需要通過 CGI 程式執行許多操作。這裡有一個專為 C++ 程式而編寫的 CGI 庫,我們可以從 

$tar xzf cgicc-X.X.X.tar.gz 
$cd cgicc-X.X.X/ 
$./configure --prefix=/usr 
$make
$make install

一、什麼是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 檔案中的部分內容:

  1. <Directory "/var/www/cgi-bin">
  2.    AllowOverride None  
  3.    Options ExecCGI  
  4.    Order allow,deny  
  5.    Allow from all  
  6. </Directory>
  7. <Directory "/var/www/cgi-bin">
  8. Options All  
  9. </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報文頭部都有如下格式:

  1. HTTP Field Name: Field Content  
  2. For Example  
  3. 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)的許可權,為最高許可權。

命名管道(fifo)是程序間通訊方式。看了很多博文,一般都是堵塞模式。

這兩篇博文裡的程式例項,程式碼都是ok的,親身試驗過。命名管道的知識裡面也有講,我這就懶得講了。我後面的程式碼是把cgi參考博文裡的程式碼和fifo參考博文的程式碼結合起來。

cgi fifo write端程式碼:

cgi_fifo_write.cpp,這是一個C++程式。後面的fifo read端是C程式。

  1. // cgi_fifo_write.cpp
  2. #include "cgififowrite.h"
  3. #define FIFO_MODE O_CREAT|O_NONBLOCK|O_RDWR
  4. #define FILE_MODE O_WRONLY | O_NONBLOCK
  5. using namespace std;
  6. using namespace cgicc;
  7. //*********fifo讀函式******
  8. void MainProgram::fifo_write()
  9. {
  10. //////非堵塞模式fifo通訊 write端
  11. const char *fifo_name = "/media/disk0A/workspace/fiforeadnoblock/Debug/my_fifo";
  12. int fd;
  13. char w_buf[100];
  14. char w_t_buf[50];
  15. const char *hstr = "hello world";
  16. //mkfifo()函式生成fifo檔案
  17. if(mkfifo(fifo_name, FIFO_MODE) < 0 && (errno != EEXIST))
  18. {
  19. perror("failed to create fifo server");
  20. exit(1);
  21. }
  22. char cmd[100];
  23. sprintf(cmd, "chmod 704 %s", fifo_name);
  24. system(cmd);
  25. int nwrite;
  26. //open()函式開啟fifo檔案,O_WRONLY | O_NONBLOCK 非堵塞式只讀
  27. fd = open(fifo_name, FILE_MODE);
  28. if (fd == -1)
  29. {
  30. if (errno == ENXIO)
  31. {
  32. printf("open errop;no reading process\n");
  33. } else {
  34. perror("open error");
  35. exit(EXIT_FAILURE);
  36. }
  37. }
  38. /*if (argc >= 2)
  39. {
  40. strcpy(w_t_buf, argv[1]);
  41. } else {
  42. strcpy(w_t_buf, hstr);
  43. }*/
  44. strcpy(w_t_buf, hstr);
  45. int i=0;
  46. int n;
  47. time_t tp;
  48. //設定write次數,我下面設定了6次
  49. while(i++<6)
  50. {
  51. time(&tp);
  52. n=sprintf(w_buf, "Process %d ils sending %s at %s", getpid(), w_t_buf, ctime(&tp));
  53. printf("Start write Time %d 。FD = %d \n", i , fd);
  54. //***********************
  55. // 如果 fiforeadnoblock 可執行程式執行結束,那麼read() 函式就會結束,讀端的fd會關閉,下面的write()函式就會返回-1
  56. nwrite = write(fd, w_buf, n);
  57. //***********************
  58. printf("nwrite = %d \n" , nwrite);
  59. if (nwrite < 0)
  60. {
  61. if (errno == EAGAIN)
  62. {
  63. printf("the fifo has not been read yet.Please try later\n");
  64. } else {
  65. printf("error sorry!!\n");
  66. exit(1);
  67. }
  68. }
  69. printf("Send Message to FIFO: %s \n", w_buf );
  70. sleep(1);
  71. }
  72. close(fd);
  73. printf("Send finished\n" );
  74. }
  75. //*****************************
  76. //********cgi處理函式************
  77. void MainProgram::cgi_Request()
  78. {
  79. Cgicc formData;
  80. //報文頭表示返回的是一個html網頁
  81. cout << "Content-type:text/html\r\n\r\n";
  82. //報文頭表示返回的是一個JSON資料,得新增json的庫
  83. //cout << "Content-type:application/json\r\n\r\n";
  84. //cout << "{\"code\":1,\"message\":\"fifo right\"}\n";
  85. cout << "<meta charset='utf-8'>\n";
  86. cout << "<html>\n";
  87. cout << "<head>\n";
  88. cout << "<meta charset='utf-8'>\n";
  89. cout << "<title>CGI與FIFO通訊</title>\n";
  90. cout << "</head>\n";
  91. cout << "<body>\n";
  92. cout << "CGI函式沒問題" << endl;
  93. cout << "<br/>\n";
  94. cout << "</body>\n";
  95. cout << "</html>\n";
  96. //exit(EXIT_SUCCESS);
  97. }
  98. MainProgram::~MainProgram() {
  99. }
  100. MainProgram::MainProgram() {
  101. }
  102. int main(int argc, char *argv[]) {
  103. MainProgram m_MainProgram;
  104. m_MainProgram.cgi_Request();
  105. m_MainProgram.fifo_write();
  106. printf("back to main func\n");
  107. return 0;
  108. }

cgififowrite.h

  1. /*
  2. * cgififowrite.h
  3. * Created on: 2018-1-15
  4. * Author: lxf
  5. */
  6. #ifndef CGIFIFOWRITE_H_
  7. #define CGIFIFOWRITE_H_
  8. #define MYPCDEBUG 0
  9. #include <string.h>
  10. #include <unistd.h>
  11. #include <stdlib.h>
  12. #include <stdio.h>
  13. #include <iostream>
  14. #include <signal.h>
  15. #include <pthread.h>
  16. #include <signal.h>
  17. #include <assert.h>
  18. #include <sys/shm.h>
  19. #include <sys/types.h>
  20. #include <sys/stat.h>
  21. #include <fcntl.h>
  22. #include <limits.h>
  23. #include <errno.h>
  24. #include <fcntl.h>
  25. #include <time.h>
  26. #include <vector>
  27. #include <cgicc/CgiDefs.h>
  28. #include <cgicc/Cgicc.h>
  29. #include <cgicc/HTTPHTMLHeader.h>
  30. #include <cgicc/HTMLClasses.h>
  31. //標頭檔案有些可能用不到
  32. class MainProgram{
  33. public:
  34. MainProgram();
  35. ~MainProgram();
  36. void fifo_write();
  37. void cgi_Request();
  38. int init();
  39. int start();
  40. };
  41. #endif /* CGIFIFOWRITE_H_ */
fifo read端程式碼:

fifo_read_noblock.c,這是一個C程式。

  1. //
  2. // Created by : Harris Zhu
  3. // Filename : fifo_read_noblock.cpp
  4. // Created On : 2017-08-17 16:46
  5. // Last Modified :lxf
  6. // Update Count : 2018-01-15 10:46
  7. //
  8. //=======================================================================
  9. #include <unistd.h>
  10. #include <sys/types.h>
  11. #include <sys/stat.h>
  12. #include <errno.h>
  13. #include <fcntl.h>
  14. #include <stdio.h>
  15. #include <stdlib.h>
  16. #include <string.h>
  17. #define OPEN_MODE O_RDONLY | O_NONBLOCK
  18. #define FIFO_MODE O_CREAT|O_RDWR|O_NONBLOCK
  19. int main(int argc, char** argv) {
  20. const char *fifo_name = "/media/disk0A/workspace/fiforeadnoblock/Debug/my_fifo";
  21. char buf_r[100];
  22. int fd;
  23. int nread;
  24. int res;
  25. if (((res=mkfifo(fifo_name, FIFO_MODE)) < 0) && (errno != EEXIST))
  26. {
  27. printf("can not creat fifoserver %d :\n", res, errno);
  28. exit(1);
  29. }
  30. printf("preparing for reading bytes...\n");
  31. char cmd[100];
  32. sprintf(cmd, "chmod 704 %s", fifo_name);
  33. system(cmd);
  34. fd = open(fifo_name, OPEN_MODE); //非堵塞模式開啟fifo檔案
  35. if (fd == -1) {
  36. perror("error in openning fifo server");
  37. exit(1);
  38. }
  39. int i=0;
  40. //int len;
  41. while (i++<11) //設定讀的次數
  42. {
  43. memset(buf_r, 0, sizeof(buf_r));
  44. if ((nread = read(fd, buf_r, sizeof(buf_r))) < 0) { // read()函式
  45. if (errno == EAGAIN)
  46. {
  47. printf("no data yet\n");
  48. sleep(1);
  49. }
  50. } else {
  51. if(nread > 0)
  52. {
  53. printf("read %s from FIFO %d \n", buf_r, i);
  54. }
  55. sleep(1);
  56. }
  57. printf("FD = %d 。time %d \n",fd,i);
  58. }
  59. //pause();
  60. printf("Close FD \n");
  61. //sleep(3); //掛起3秒
  62. close(fd);
  63. unlink(fifo_name);
  64. return 0;
  65. }

我用的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++。結果如下: