1. 程式人生 > >OHSCE入門教程(一)-高可靠性PHP通訊&控制框架

OHSCE入門教程(一)-高可靠性PHP通訊&控制框架

  "PHP是最好的(網際網路+物聯網)語言!",據說釋出PHP開源產品都要慣例的喊上這麼一句。 -2016.10.17 發此文之際是OHSCEV0.1.22版本

        開啟第一篇教程前總要賣點情懷,本人本科學習建築電氣與智慧化專業(即電氣工程及其自動化專業),一直是名正經的電氣工程師。其實本來和這個號稱是“Web語言”的PHP八竿子打不著,可是隨著國家所謂“網際網路+“的引導,也翻山越嶺的將WEB領域逐漸和各行各業拉到了一起。從很多行業的角度看“網際網路+“很大程度就是“WEB+”,於是PHP這門最好的語言便進入了我的技術棧,我發現PHP是最具潛力的工控、物聯網&智慧化語言。

        開啟PHP的官網,我們可以看見兩行白色的歡迎語:“PHP is a popular general-purpose scripting language that is especially suited to web development.Fast, flexible and pragmatic, PHP powers everything from your blog to the most popular websites in the world.”  -意譯:PHP是一各流行且全能的指令碼語言,特別合適於與網際網路相關的領域。它快速、靈活且實用,健壯的PHP能驅動小到你的部落格大到著名的FACEBOOK、百度等每個事物。

       工控工業4.0、物聯網、行業智慧化將是PHP的下一個最具潛力的領域。本著不造輪子多改進的原則我曾用款現有的PHP通訊框架(不含串列埠通訊功能)做過一次控制系統的實現,結果那幾天攪合的我一團糟,很多奇怪的問題和很高的失誤率,其實人家的框架是非常優秀的WEB框架可能是控制領域的技術棧和傳統的WEB領域有著很大的區別,傳統的web工程師更多的工作內容和一個強大的瀏覽器或APP互動而控制工程師則不是,於是我決定從底層重寫一套程式碼(同時也加上了串列埠通訊和共享記憶體輔助託管功能)。這套程式碼專為控制環境量身定製,是專業針對工控工業4.0、物聯網、行業智慧化場景(當然兼備了做WEB的能力,畢竟“”網際網路+“”嘛)。其實母程式已幾經實布和商布,我們將其核心部分剝離出來繼而做了開源化改動併合並了OpenIAC計劃成為了為控制場景量身定製的高可靠性PHP通訊&控制框架-OHSCE。它簡單高效並且特別親切於工業自動化工程師、硬體工程師、物聯網工程師、追求效率的PHP工程師的寫法風格,也能讓傳統的PHP-WEB工程師輕鬆上手。

         言歸正傳我們以搭建一個簡單的串列埠伺服器作為第一教程的內容,一個簡單的串列埠伺服器包含了上行網路(乙太網)和現場匯流排(這裡是RS485通訊)。

         乙太網做上行網路是大勢所趨,RS485是目前使用最普遍廣泛也是最具價效比的現場通訊手段,當然很多其它通訊手段都可以和RS232/485轉接,故我們以它做例。

          #用UDP還是TCP?

          UDP是一個非常好的選擇,它無連結、高效並且節約資源,但是缺點是會有丟包的可能性,不過您可以通過主從應答來保證您的資料的可靠性,而且您可以靈活的掌握是否應答,甚至這各特性都可以邏輯中的一部分。不過對於串列埠伺服器這個場景來說TCP是更合適的選擇,因為做一個串列埠的需求很簡單,可靠連線讀寫資料並轉發到串列埠上,串列埠寫讀資料並回轉回來。我們確定使用TCP

          示例所使用的機器的COM7是將使用的串列埠。

          #欲善其事必利其器

          下載最新版本的OHSCE:

          安裝PHP5.4+ 並配置PHP.INI至可用狀態。

          配置OHSCE的配置檔案 位於.../config/

          開啟Curl擴充套件、Shmop擴充套件、Sockets擴充套件

          建立OhsceComserver.php檔案

          載入OHSCE

<?php
ini_set('memory_limit',"64M");   //重置php可以使用的記憶體大小為64M
set_time_limit(0);               //重置執行時長為無限制
ob_implicit_flush(1);
include('loadohsce.php');        //載入Ohsce載入檔案              

          #開始構造串列埠伺服器的身軀

          首先我們開啟我們需要轉發的串列埠

<?php
//......
$comid="COM7";                            //windows下是comx linux下是/dev/ttyX
Ohsce_eng_serial_creat($hscecom,$comid);  //建立一個COM7 9600,n,8,1的待呼叫串列埠資源
Ohsce_eng_serial_open($hscecom);          //呼叫資源並佔用該串列埠 

           PS:Ohsce_eng_serial_creat函式預設為您填充了您所省略的所有相關引數,當然您可以手動指定他們以建立各種串列埠資源。

Ohsce_eng_serial_creat(&$OHSCESerial,$com,$flags="1",$mode=0,$baud=9600,$parity='n',$data=8,$stop=1,$fc='none',$xon='off',$to='off',$octs='off',$odsr='off',$idsr='off',$dtr='on',$rts='on')

         在建立TCP伺服器之前我們需要先構造它的回撥函式。    所謂回撥函式就是當有新的TCP客戶端到達、客戶端傳送新的訊息到達是會呼叫的函式。

<?php
//...................
functioncomserveraccept(&$socket,$ip,$port,$zv){ 
	global $hscecom;                                         //呼叫$hscecom串列埠資源
	$ohsce_cs_data=Ohsce_socketread($socket,1024);           //讀取1024位元組資料
	if(($ohsce_cs_data!=null)or($ohsce_cs_data[0]!=false)){
	Ohsce_eng_serial_write($hscecom,$ohsce_cs_data[1],false);//寫入串列埠
    Ohsce_eng_serial_read($hscecom,$data,null,true);         //讀取返回資料
	Ohsce_socketwrite($socket,$data);                        //迴轉所讀取的資料
	}
	return true;
}
functioncomservera(&$socket,$buf,$len,$zv){  
    global $hscecom;
	Ohsce_eng_serial_write($hscecom,$buf,false);
    Ohsce_eng_serial_read($hscecom,$data,null,true);
	Ohsce_socketwrite($socket,$data);
	return true;
}
functioncomserveralways(&$oibc_clients_zv){
	global $hscecom;
	Ohsce_eng_serial_read($hscecom,$data,null,true);
	if((!is_null($data))and(strlen($data)>0)){
		foreach($oibc_clients_zv['clients'] as $okey => $osclient){
			if($okey=="0"){
				continue;
			}
			Ohsce_socketwrite($osclient,$data);
		}
	}
	return true;
}

          其中comserveralways函式是每圈迴圈都會執行一次的函式,也就是常駐函式。在我們的串列埠伺服器程式中,他會將最新收到的串列埠資料轉發廣播出去。

          comservera(&$socket,$buf,$len,$zv)和comserveraccept(&$socket,$ip,$port,$zv)函式會被固定傳入這些變數。其中$socket是本次活動的socket資源的指標。$buf是收到的資料。$len是資料長度。$ip是新到客戶端的ip地址。$port是其埠。$zv是固定結構體,其結構如下:$oibc_clients_zv=array("clients"=>&$oibc_clients,"ip"=>&$oibc_clients_id_ip,"id"=>&$oibc_clients_id)

        其中$oibc_clients陣列為當前所有socket資源,key值為0者為服務監聽者。$ip陣列為ip對照表。$id陣列為id備份表。當然如果有需要您也可以print_r之一探究竟。

       小提示:print_r函式是您的好幫手,很多時候文件很難面面俱到QQ群我也很難隨時關注,這時候print_r可能比度娘還親切。

        #賦予它生命的力量

        好了,回撥函式和常駐函式都造好了,下面我們改讓它擁有跑起來的能力了。首先先建立一個可複用的TCP服務端資源,第二步傳入讓它跑起來,就那麼簡單。

<?php
Ohsce_eng_socket_server($ohsceserver,'tcp','7626','127.0.0.1',array('callback'=>'comservera','accept'=>'comserveraccept','fap'=>'comserveralways'),'comserveraccept');          
                                              //建立一個可以複用的SOCKETSERVER資源$ohsceserver,協議為TCP,監聽埠為7626,繫結IP127.0.0.1,回撥函式為comservera,首次回撥函式為comserveraccept,常駐函式為comserveralways.最後一個comserveraccept是為了相容OHSCEV0.1.22以前的版本。
Ohsce_eng_socket_server_runtcp($ohsceserver); //執行它

       PS:一個彩蛋,當你的實際生產中最好不要使用7626埠,因為它太著名了,曾經我們漫遊在整個網際網路上尋找開了7626埠的小夥伴。簡單的來說,容易招黑,而且招來的都是老黑:)

       #一個個人認為的好習慣

      無論您的程式多麼的完美,最後記得阻截並跳轉去丟擲錯誤。這樣當您的程式擴充套件和改動其它部分時至少能保證某一部分執行的不錯。

<?php
//..程式頭......
$errmsg='Unknow';
//.............
//..程式身軀....
//.............
goto terror;            //前往並丟擲錯誤
//.............
terror:                 //丟擲錯誤的錨點
exit($errmsg);

       關於goto,可以理解為JMP指令,熟悉的身影但是用法略有不同。在PHP中GOTO是不可以從一個函式跳到另一個函式的,同時也是不可以從一個檔案跳到另一個檔案的。其實,這個限制帶來了您程式可讀性的提升和效能&一致性的平衡。

       PS.其實在很多高階語言中別說凶殘的SETJMP了,連GOTO/JMP都被取締了。原因是在號幾十年前以為叫“Edsger Wybe Dijkstra”的先生提出了GOTO有害論,於是被很多人奉為聖旨而流傳下來甚至在不少語言中乾脆就被砍掉了(如JAVA)。這的確,大大的降低了程式設計對人員要求的門檻,但隨著發展,尤其我們進入了泛(互)物(聯)聯(網)網(+)時代,GOTO的親切與作用顯得愈發的重要。不過進入21世紀不久 Dijkstra先生就去世了,英明的PHP設計團隊在2009年將GOTO關鍵字重新引入了這門“世界上最好的語言”,所以從5.3開始我們又可以使用高效、簡潔、具備自底向上一致性的GOTO關鍵字了。

       #牛刀小試

      至此,一個簡單的串列埠伺服器原型工程完成了。其實它不僅僅是一個教程案例,更是一個非常實用的功能,我將它包裝了一下,這個原型便以Alpha的身份加入了V0.1.22_BETA開源版的Engine中。在後續的版本中它將跟隨著OHSCE的版本更新變得越來越健壯。

<?php
//.......................................
comserver:
$oibc_cnp_csa=getopt('r:m:p:c:');
Ohsce_eng_serial_creat($hscecom,trim($oibc_cnp_csa['c'])); 
Ohsce_eng_serial_open($hscecom);
functioncomserveraccept(&$socket,$ip,$port,$zv){ 
	global $hscecom;
	$ohsce_cs_data=Ohsce_socketread($socket,1024);
	if(($ohsce_cs_data!=null)or($ohsce_cs_data[0]!=false)){
	Ohsce_eng_serial_write($hscecom,$ohsce_cs_data[1],false);
    Ohsce_eng_serial_read($hscecom,$data,null,true);
	Ohsce_socketwrite($socket,$data);
	}
	return true;
}
functioncomservera(&$socket,$buf,$len,$zv){  
    global $hscecom;
	Ohsce_eng_serial_write($hscecom,$buf,false);
    Ohsce_eng_serial_read($hscecom,$data,null,true);
	Ohsce_socketwrite($socket,$data);
	return true;
}
functioncomserveralways(&$oibc_clients_zv){
	global $hscecom;
	Ohsce_eng_serial_read($hscecom,$data,null,true);
	if((!is_null($data))and(strlen($data)>0)){
		foreach($oibc_clients_zv['clients'] as $okey => $osclient){
			if($okey=="0"){
				continue;
			}
			Ohsce_socketwrite($osclient,$data);
		}
	}
	return true;
}
Ohsce_eng_socket_server($ohsceserver,'tcp',intval(trim($oibc_cnp_csa['p'])),OHSCE_MYIP_SYSTEM,array('callback'=>'comservera','accept'=>'comserveraccept','fap'=>'comserveralways'),'comserveraccept');
Ohsce_eng_socket_server_runtcp($ohsceserver); //開始執行
goto terror;
//.......................................

           好了下面我們啟動它並使用TCP連線串列埠伺服器在COM7串列埠上使用MODBUS-RTU協議讀取一臺壓力變送器的資料。

         tcpComClient.php:

<?php
ini_set('memory_limit',"88M");//重置php可以使用的記憶體大小為64M
set_time_limit(0);
ob_implicit_flush(1);
error_reporting(0);
include('loadohsce.php');
Ohsce_eng_socket_client($ohsceclient,'tcp',7628,'127.0.0.1'); //建立一個TCP客戶端資源並連線27.0.0.1:7626
Ohsce_socketsend($ohsceclient['socket'],"\x01\x03\x00\x01\x00\x04\x15\xc9");  //傳送資料
//Ohsce_socketsend($ohsceclient['socket'],array('in'=>"01030001000415c9",'bin'=>true));
echo Ohsce_socketread($ohsceclient['socket'],1024)[1]; //收取回複數據
sleep(30);

         執行效果:

           

         PHP是一個健壯的全能指令碼語言,特別適合於網路有關的場景。工業控制、物聯網、行業智慧化方向,OHSCE是您強大的戰艦。

          OHSCE官方網站: HTTP://WWW.OHSCE.ORG

          技術&交流:Q群-374756165 (隨風星海@作者)