Welcome to TiddlyWiki created by Jeremy Ruston, Copyright © 2007 UnaMesa Association
<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
<style type='text/css'>#contentWrapper{display:none;}#SplashScreen{border:3px solid #666;display:block;text-align:center;width:360px;margin:100px auto;padding:50px;background-color:#000;position:relative;}#SplashScreenShadow{position:absolute;padding:3em 1em 1em 1em;left:0px;top:0px;width:420px}#SplashScreenForeground{position:absolute;padding:3em 1em 1em 1em;left:1px;top:1px;width:420px}#SplashScreen .siteTitle{font-family:Magneto,Stencil,Lucida Calligraphy,sans-serif;font-size:3em;}#SplashScreen .siteSubTitle{font-family:Arial,Tahoma,sans-serif;font-size:1.2em;}</style><div id='SplashScreen'><div id='SplashScreenShadow'><span class='siteTitle' style='color:#009;'>PHPRPC</span><br /><span class='siteSubTitle' style='color:#c00;'>perfect high performance remote procedure call</span></div><div id="SplashScreenForeground"><span class='siteTitle' style='color:#99f;'>PHPRPC</span><br /><span class='siteSubTitle' style='color:#f93;'>perfect high performance remote procedure call</span></div><br /><br /><br /><br /><br /><br /><img src='images/loading.gif' alt='' /></div>
<!--}}}-->
Background: #000
Foreground: #ccc
PrimaryPale: #333
PrimaryLight: #18f
PrimaryMid: #ff9
PrimaryDark: #ff6
SecondaryPale: #000
SecondaryLight: #333
SecondaryMid: #666
SecondaryDark: #ff0
TertiaryPale: #333
TertiaryLight: #666
TertiaryMid: #999
TertiaryDark: #090
Error: #f88
/*{{{*/
body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
a {color:[[ColorPalette::PrimaryMid]];}
a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
a img {border:0;}
h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
.button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
.button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
.button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}
.header {background:[[ColorPalette::PrimaryMid]];}
.headerShadow {color:[[ColorPalette::Foreground]];}
.headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
.headerForeground {color:[[ColorPalette::Background]];}
.headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}
.tabSelected{color:[[ColorPalette::PrimaryDark]];
background:[[ColorPalette::TertiaryPale]];
border-left:1px solid [[ColorPalette::TertiaryLight]];
border-top:1px solid [[ColorPalette::TertiaryLight]];
border-right:1px solid [[ColorPalette::TertiaryLight]];
}
.tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
.tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
.tabContents .button {border:0;}
#sidebar {}
#sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
#sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}
.wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
.wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
.wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
.wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
border:1px solid [[ColorPalette::PrimaryMid]];}
.wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
.wizardFooter {background:[[ColorPalette::PrimaryPale]];}
.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
.wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
.wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
.wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}
.wizard .notChanged {background:transparent;}
.wizard .changedLocally {background:#80ff80;}
.wizard .changedServer {background:#8080ff;}
.wizard .changedBoth {background:#ff8080;}
.wizard .notFound {background:#ffff80;}
.wizard .putToServer {background:#ff80ff;}
.wizard .gotFromServer {background:#80ffff;}
#messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
#messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}
.popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}
.popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
.popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
.popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
.popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
.popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
.listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}
.tiddler .defaultCommand {font-weight:bold;}
.shadow .title {color:[[ColorPalette::TertiaryDark]];}
.title {color:[[ColorPalette::SecondaryDark]];}
.subtitle {color:[[ColorPalette::TertiaryDark]];}
.toolbar {color:[[ColorPalette::PrimaryMid]];}
.toolbar a {color:[[ColorPalette::TertiaryLight]];}
.selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
.selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}
.tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
.selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
.tagging .button, .tagged .button {border:none;}
.footer {color:[[ColorPalette::TertiaryLight]];}
.selected .footer {color:[[ColorPalette::TertiaryMid]];}
.sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
.sparktick {background:[[ColorPalette::PrimaryDark]];}
.error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
.warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
.lowlight {background:[[ColorPalette::TertiaryLight]];}
.zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}
.imageLink, #displayArea .imageLink {background:transparent;}
.annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}
.viewer .listTitle {list-style-type:none; margin-left:-2em;}
.viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
.viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}
.viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
.viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
.viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}
.viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
.viewer code {color:[[ColorPalette::SecondaryDark]];}
.viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}
.highlight, .marked {background:[[ColorPalette::SecondaryLight]];}
.editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
.editorFooter {color:[[ColorPalette::TertiaryMid]];}
#backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
#backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
#backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
#backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
#backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
.backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
.backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
#backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
/*}}}*/
/*{{{*/
* html .tiddler {height:1%;}
body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}
h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
h1,h2,h3 {margin-top:1.2em;margin-bottom:0.3em;}
h1 {padding-bottom:8px;}
h2 {padding-bottom:5px;}
h3 {padding-bottom:2px;}
h4,h5,h6 {margin-top:1em;}
h1 {font-size:18px;}
h2 {font-size:16px;}
h3 {font-size:15px;}
h4 {font-size:14px;}
h5 {font-size:13px;}
h6 {font-size:12px;}
hr {height:1px;}
a {text-decoration:none;}
dt {font-weight:bold;}
ol {list-style-type:decimal;}
ol ol {list-style-type:lower-alpha;}
ol ol ol {list-style-type:lower-roman;}
ol ol ol ol {list-style-type:decimal;}
ol ol ol ol ol {list-style-type:lower-alpha;}
ol ol ol ol ol ol {list-style-type:lower-roman;}
ol ol ol ol ol ol ol {list-style-type:decimal;}
.txtOptionInput {width:11em;}
#contentWrapper .chkOptionInput {border:0;}
.externalLink {text-decoration:underline;}
.indent {margin-left:3em;}
.outdent {margin-left:3em; text-indent:-3em;}
code.escaped {white-space:nowrap;}
.tiddlyLinkExisting {font-weight:bold;}
.tiddlyLinkNonExisting {font-style:italic;}
/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
a.tiddlyLinkNonExisting.shadow {font-weight:bold;}
#mainMenu .tiddlyLinkExisting,
#mainMenu .tiddlyLinkNonExisting,
#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
#sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}
.header {position:relative;}
.header a:hover {background:transparent;}
.headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
.headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}
.siteTitle {font-size:3em;}
.siteSubtitle {font-size:1.2em;}
#mainMenu {position:absolute; left:0; width:18em; text-align:left; line-height:1.6em; padding:1.5em 0; font-size:1em;}
#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
#sidebarOptions {padding-top:0.3em;}
#sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
#sidebarOptions input {margin:0.4em 0.5em;}
#sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
#sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
#sidebarTabs .tabContents {width:15em; overflow:hidden;}
.wizard {padding:0.1em 1em 0em 2em;}
.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizardStep {padding:1em 1em 1em 1em;}
.wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
.wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
.wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
.wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}
#messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
.messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
#messageArea a {text-decoration:underline;}
.tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
.popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}
.popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
.popup .popupMessage {padding:0.4em;}
.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
.popup li.disabled {padding:0.4em;}
.popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
.listBreak {font-size:1px; line-height:1px;}
.listBreak div {margin:2px 0;}
.tabset {padding:1em 0em 0em 0.5em;}
.tab {margin:0em 0em 0em 0.25em; padding:2px;}
.tabContents {padding:0.5em;}
.tabContents ul, .tabContents ol {margin:0; padding:0;}
.txtMainTab .tabContents li {list-style:none;}
.tabContents li.listLink { margin-left:.75em;}
#contentWrapper {display:block;}
#splashScreen {display:none;}
#displayArea {margin:1em 17em 0em 20em;}
.toolbar {text-align:right; font-size:.9em;}
.tiddler {padding:1em 1em 0em 1em;}
.missing .viewer,.missing .title {font-style:italic;}
.title {font-size:1.65em; font-weight:bold;}
.missing .subtitle {display:none;}
.subtitle {font-size:1.1em;}
.tiddler .button {padding:0.2em 0.4em;}
.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
.isTag .tagging {display:block;}
.tagged {margin:0.5em; float:right;}
.tagging, .tagged {font-size:0.9em; padding:0.25em;}
.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
.tagClear {clear:both;}
.footer {font-size:.9em;}
.footer li {display:inline;}
.annotation {padding:0.5em; margin:0.5em;}
* html .viewer pre {width:99%; padding:0 0 1em 0;}
.viewer {line-height:1.4em; padding-top:0.5em;}
.viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
.viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}
.viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
table.listView {font-size:0.85em; margin:0.8em 1.0em;}
table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}
.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
.viewer code {font-size:1.2em; line-height:1.4em;}
.editor {font-size:1.1em;}
.editor input, .editor textarea {display:block; width:100%; font:inherit;}
.editorFooter {padding:0.25em 0em; font-size:.9em;}
.editorFooter .button {padding-top:0px; padding-bottom:0px;}
.fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}
.sparkline {line-height:1em;}
.sparktick {outline:0;}
.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
.zoomer div {padding:1em;}
* html #backstage {width:99%;}
* html #backstageArea {width:99%;}
#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
#backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel {display:none; z-index:100; position:absolute; width:90%; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
.backstagePanelFooter {padding-top:0.2em; float:right;}
.backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}
.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}
/*}}}*/
/***
StyleSheet for ~SyntaxHighlighter
***/
/*{{{*/
.dp-highlighter{font-family:"Courier New","Consolas",mono,serif; font-size:12px; background-color:#E7E5DC; width:99%; overflow:auto; margin:18px 0 18px 0 !important; padding-top:1px}
.dp-highlighter ol,
.dp-highlighter ol li,
.dp-highlighter ol li span{margin:0; padding:0; border:none}
.dp-highlighter a,
.dp-highlighter a:hover{background:none; border:none; padding:0; margin:0}
.dp-highlighter .bar{padding-left:45px}
.dp-highlighter.collapsed .bar,
.dp-highlighter.nogutter .bar{padding-left:0px}
.dp-highlighter ol{list-style:decimal; background-color:#fff; margin:0px 0px 1px 45px !important; padding:0px; color:#5C5C5C}
.dp-highlighter.nogutter ol,
.dp-highlighter.nogutter ol li{list-style:none !important; margin-left:0px !important}
.dp-highlighter ol li,
.dp-highlighter .columns div{list-style:decimal-leading-zero; list-style-position:outside !important; border-left:3px solid #6CE26C; background-color:#F8F8F8; color:#5C5C5C; padding:0 3px 0 10px !important; margin:0 !important; line-height:14px}
.dp-highlighter.nogutter ol li,
.dp-highlighter.nogutter .columns div{border:0}
.dp-highlighter .columns{background-color:#F8F8F8; color:gray; overflow:hidden; width:100%}
.dp-highlighter .columns div{padding-bottom:5px}
.dp-highlighter ol li.alt{background-color:#FFF; color:inherit}
.dp-highlighter ol li span{color:black; background-color:inherit}
.dp-highlighter.collapsed ol{margin:0px}
.dp-highlighter.collapsed ol li{display:none}
.dp-highlighter.printing{border:none}
.dp-highlighter.printing .tools{display:none !important}
.dp-highlighter.printing li{display:list-item !important}
.dp-highlighter .tools{padding:3px 8px 3px 10px; font:9px Verdana,Geneva,Arial,Helvetica,sans-serif; color:silver; background-color:#f8f8f8; padding-bottom:10px; border-left:3px solid #6CE26C}
.dp-highlighter.nogutter .tools{border-left:0}
.dp-highlighter.collapsed .tools{border-bottom:0}
.dp-highlighter .tools a{font-size:9px; color:#a0a0a0; background-color:inherit; text-decoration:none; margin-right:10px}
.dp-highlighter .tools a:hover{color:red; background-color:inherit; text-decoration:underline}
.dp-about{background-color:#fff; color:#333; margin:0px; padding:0px}
.dp-about table{width:100%; height:100%; font-size:11px; font-family:Tahoma,Verdana,Arial,sans-serif !important}
.dp-about td{padding:10px; vertical-align:top}
.dp-about .copy{border-bottom:1px solid #ACA899; height:95%}
.dp-about .title{color:red; background-color:inherit; font-weight:bold}
.dp-about .para{margin:0 0 4px 0}
.dp-about .footer{background-color:#ECEADB; color:#333; border-top:1px solid #fff; text-align:right}
.dp-about .close{font-size:11px; font-family:Tahoma,Verdana,Arial,sans-serif !important; background-color:#ECEADB; color:#333; width:60px; height:22px}
.dp-highlighter .comment, .dp-highlighter .comments{color:#008200; background-color:inherit}
.dp-highlighter .string{color:blue; background-color:inherit}
.dp-highlighter .keyword{color:#069; font-weight:bold; background-color:inherit}
.dp-highlighter .preprocessor{color:gray; background-color:inherit}
/*}}}*/
/***
StyleSheet for use when a translation requires any css style changes.
This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
***/
/*{{{*/
body {font-size:0.8em;}
#sidebarOptions {font-size:1.05em;}
#sidebarOptions a {font-style:normal;}
#sidebarOptions .sliderPanel {font-size:0.95em;}
.subtitle {font-size:0.8em;}
.viewer table.listView {font-size:0.95em;}
/*}}}*/
/*{{{*/
@media print {
#mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none ! important;}
#displayArea {margin: 1em 1em 0em 1em;}
/* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
noscript {display:none;}
}
/*}}}*/
/*{{{*/
div#header p, div#header h1, div#header ul {
margin: 0;
padding: 0;
border: 0;
}
div#logo a {
margin-top: 20px;
display: block;
}
div#logo h1 a span, div#logo p {
display: none;
}
div#header {
height: 85px;
padding: 0 36px;
min-width: 700px;
margin: 0 auto;
border-bottom: 6px solid #333333;
}
div#logo h1 a {
background: url(images/logo.gif) no-repeat;
height: 60px;
width: 200px;
float: left;
}
#topMenu {
position: absolute;
top: 60px;
right: 36px;
}
#topMenu ul {
float: right;
list-style: none;
position: relative;
}
#topMenu ul li {
float: left;
margin-left: 5px;
line-height: 1.4;
}
#topMenu ul li a, #topMenu ul li strong {
color: #ffffff;
text-decoration: none;
font-weight: bold;
text-align: center;
width: 85px;
height: 18px;
padding: 4px 0 3px 0;
display: block;
background-image: url(images/tab.gif);
background-repeat: no-repeat;
}
#topMenu ul li a {
background-position: center left;
}
#topMenu ul li strong {
background-position: bottom left;
}
#topMenu ul li a:hover, #topMenu ul li a:active {
background-position: top left;
}
#footer {
background: #000000;
border-top: 6px solid #333333;
margin-top: 20px;
padding: 15px 0;
color: #333333;
text-align: center;
}
#footer a, #footer a:hover, #footer a:active {
color: #333333;
background: #000000;
text-decoration: none;
}
/*}}}*/
<!--{{{-->
<div id='header'>
<div id='logo'>
<h1><a href='/'><span refresh='content' tiddler='SiteTitle'></span></a></h1>
<p refresh='content' tiddler='SiteSubtitle'></p>
</div>
<div id='topMenu' refresh='content' tiddler='TopMenu'></div>
</div>
<div id='mainMenu' refresh='content' tiddler='MainMenu'></div>
<div id='sidebar'>
<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<div id='footer' refresh='content' tiddler='Footer'></div>
<!--}}}-->
<!--{{{-->
<div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='viewer' macro='view text wikified'><span macro='tiddler ReplaceDoubleClick'></span><span macro='tiddler HideTiddlerToolbar'></span></div>
<div class='tagClear'></div>
<!--}}}-->
<!--{{{-->
<div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='editor' macro='edit title'></div>
<div macro='annotations'></div>
<div class='editor' macro='edit text'></div>
<div class='editor' macro='edit tags'></div><div class='editorFooter'><span macro='message views.editor.tagPrompt'></span><span macro='tagChooser'></span></div>
<!--}}}-->
如果你已经把 PHPRPC 安装好了,那么接下来就让我们开始第一个小程序吧。按照惯例,第一个演示程序几乎总是 HelloWorld,我们也不想打破这个惯例,不过对于 PHPRPC 来说,有服务器端就要有客户端,否则我们就没有什么好演示的啦,所以我们的第一个演示程序实际上是两个,一个是服务器端,另一个是客户端。我们都先用 PHP 语言来写好了。
''服务器端''
<code php:firstline[0]>
<?php
include ("php/phprpc_server.php");
function HelloWorld() {
return 'Hello World!';
}
$server = new PHPRPC_Server();
$server->add('HelloWorld');
$server->start();
?>
</code>
''客户端''
<code php:firstline[0]>
<?php
include ("php/phprpc_client.php");
$client = new PHPRPC_Client('http://127.0.0.1/server.php');
echo $client->HelloWorld();
?>
</code>
对于服务器端程序,我们应该将它命名为 server.php(这是因为客户端调用时用的是这个名字,而不是 PHPRPC 的什么规定),然后把它放在本地 Web 服务器的根目录下,并保证服务器可以正常运行 PHP 程序,之后在浏览器或命令行下运行客户端程序,你就可以看到结果了。
这两个程序几乎简单到无需解释的地步,所以如果你已经明白它们的意思,那么就可以直接跳过下面的解释,继续看后面的例子。
服务器端第 1 句是将 PHPRPC 的服务器端程序包含到你的程序里,之后的 2 - 4 句是定义一个远程调用的函数,你会发现它与本地函数没有任何区别。第 5 句是创建服务器端对象,第 6 句是添加要发布的方法,这里添加的就是刚刚定义的 ~HelloWorld 函数,在 PHP 中,添加的发布方法是函数名的字符串表示,在其它语言中可能略有不同。第 7 句是启动服务。
客户端就更简单了,第 1 句是将 PHPRPC 的客户端程序包含到你的程序里。第 2 句是创建客户端对象,其中的参数就是服务器端的地址。第 3 句是对远程方法(函数)的调用,之后通过 echo 将它显示出来。如果顺利的话,执行后你就会看到输出的 {{{Hello World!}}}。
上面的例子是发布的是函数,下面我们来看一下类中的静态方法如何发布:
<code php:firstline[0]>
<?php
include ("php/phprpc_server.php");
class Hello {
static function HelloWorld() {
return 'Hello World!';
}
}
$server = new PHPRPC_Server();
$server->add('HelloWorld', 'Hello');
$server->start();
?>
</code>
这个服务器端只要它的名字与发布的地址与上面那个发布函数的例子一样的话,上面的那个客户端就可以得到同样的结果,也就是说,在客户端看来是没有任何区别的。
PHPRPC 并不是只可以在 PHP 中使用,它同样支持其它语言的服务器和客户端,而且还可以无差别的相互调用。
现在我们来看一下如何在 Java 中调用这个 PHP 的服务器方法:
<code java>
import org.phprpc.*;
interface IHello {
public String helloWorld();
}
public class HelloWorld {
public static void main ( String [] args ) {
PHPRPC_Client client = new PHPRPC_Client("http://127.0.0.1/server.php");
IHello clientProxy = (IHello)client.useService(IHello.class);
System.out.println(clientProxy.helloWorld());
}
}
</code>
当我们把这个例子编译之后,在命令行中输入以下命令就可以看到执行结果了:
{{{
java -classpath .;phprpc_client.jar HelloWorld
}}}
这个 Java 的客户端看上去比 PHP 的要稍微复杂一些,不过仍然很好理解。在 Java 客户端中,我们使用了接口来描述远程方法,之后我们通过 useService 方法返回一个远程代理对象,该对象实现了我们定义的接口,之后我们就可以直接调用远程方法 helloWorld 了。如果你比较细心的话,你还会发现我们在 PHP 中定义的方法和在 Java 中定义的接口的名字的大小写有点不同,但是仍然可以正常调用。是的,PHPRPC 发布的方法是不区分大小写的。所以不论你所使用的语言是否区分大小写,都可以按照自己(或语言)的习惯来定义方法名。
在本章的最后,我们再来看一下在 ~JavaScript 如何调用 PHPRPC 服务。顺便再强调一下,服务器端不止是可以用 PHP 来编写,你同样可以使用其它语言(比如 Java,.NET,Ruby,Python 等),这里我们只是以 PHP 为例而已。在这个例子中你还会看到如何使用加密传输。
<code javascript>
<html>
<head>
<title>HelloWorld</title>
<script type="text/javascript" src="phprpc_client.js"></script>
<script type="text/javascript">
var client = new PHPRPC_Client('http://127.0.0.1/server.php', ['HelloWorld']);
client.setEncryptMode(2);
client.HelloWorld(function (result) {
alert(result);
});
</script>
</head>
<body>
</body>
</html>
</code>
这个 ~JavaScript 是在网页中运行的,这里建议大家要把 head 和 body 标签都写全,即使它们对你来说看上去没有什么用处,但是在有些浏览器中,如果这些标签没有写全,或者写的不正确,程序就不能正确运行。
在这个例子中,我们会发现在创建 ~PHPRPC_Client 对象时,除了要写服务器地址以外,还要将远程方法的名字作为参数,因为远程方法可能不止一个,所以这个参数是数组类型的。
{{{client.setEncryptMode(2);}}} 这句是设置加密传输,参数 2 表示双向加密,就是参数和结果都加密(只不过这个 ~HelloWorld 比较特殊,它没有参数)。
接下来就是调用远程方法 ~HelloWorld 了,我们会发现它跟 PHP 和 Java 客户端的调用不太一样。是的,在 ~JavaScript 中远程调用都是采用异步方式的,也就是说要获得结果,需要用回调函数,回调函数作为远程方法的最后一个参数,回调函数也有四个参数,这里我们只使用第一个参数,也就是返回结果 result,通过 {{{alert(result);}}} 我们就可以在浏览器中看到最后弹出的 {{{Hello World!}}} 提示框了。
通过上面的例子,我们已经看到在 PHP、Java 和 ~JavaScript 这三种语言中使用 PHPRPC 都很简单,但因为语言的不同,在写法和用法上又有一些差别。不过你不用担心其它的语言跟它们也会有很大的差别,因为在接下来的章节中你很快就会发现其它语言跟这上面三种语言实现的写法和用法上的相似或相同之处了。
<script>
var t=story.findContainingTiddler(place);
if (!t || t.id =='tiddlerHideTiddlerTitle') return;
var nodes=t.getElementsByTagName("*");
for (var i=0; i<nodes.length; i++)
if (hasClass(nodes[i],"title"))
nodes[i].style.display="none";
</script>
<script>
var t=story.findContainingTiddler(place);
if (!t || window.location.protocol == "file:") return;
var nodes=t.getElementsByTagName("*");
for (var i=0; i<nodes.length; i++)
if (hasClass(nodes[i],"toolbar"))
nodes[i].style.display="none";
</script>
*@@color(red):''PHPRPC 用户指南''@@
##[[欢迎来到 PHPRPC 世界]]
##[[快速入门]]
##[[PHPRPC for PHP]]
##[[PHPRPC for Java]]
##[[PHPRPC for JavaScript]]
##[[PHPRPC for .NET]]
##[[PHPRPC for Delphi]]
##[[PHPRPC for ActionScript]]
##[[PHPRPC for ASP]]
##[[PHPRPC for Ruby]]
##[[PHPRPC for Python]]
##[[PHPRPC for Perl]]
##[[常见问题解答]]
*@@color(red):''PHPRPC 开发指南''@@
##[[PHPRPC 协议概述]]
##[[PHPRPC 数据表示]]
##[[PHPRPC 加密传输]]
##[[PHPRPC 会话管理]]
##[[PHPRPC 其他话题]]
Java 中似乎并不缺少远程过程调用一类的工具,从最初简单的只能在 Java 之间通讯的 RMI,到现在各种各样的 ~WebService 实现,看上去你确实有足够多的选择。不过经过一番痛苦的挑选后,你会发现不是功能太简单以至于不能满足你的要求,就是复杂到让你根本不知道如何下手。还有一些你尽管花了大力气学会了使用它,但当你确实需要用到它那些所鼓吹的特性时(比如可以与各种语言无障碍交互),你会发现你要付出的代价比你最初想象的要多得多。
这方面例子我就亲身经历过一些。当我还在学校工作的时候,一次学校要上一个统一身份认证系统,有几家公司前来竞标,其中有一家是使用所谓的 ~J2EE 实现的。当学校提出我们需要在各种不同语言实现的现有异构系统到这个平台的统一认证时,他们说已经提供了 ~WebService 接口来实现这一点。这听上去简直太棒了。可是当他们真的带他们的系统来测试时,我们就发现完全不是这么回事了。他们没有提供任何可以让我们在其它语言客户端中调用他们服务的工具,他们只是不停的强调 ~WebService 是一个标准,只要按照标准去做就可以了。可是事实上,就连他们也根本不知道该如何按照所谓的标准来实现一个可以用的客户端,他们甚至不知道他们所用的工具(Axis)使用的是哪个版本的 ~WebService 标准。
这还不算最糟糕的,更糟糕的是,他们甚至不理解 ~WebService 本身会将数据序列化为 XML 传输。所以,他们在使用 Axis 发布他们的服务时,居然传递的参数和结果都是他们自己定义的 XML 字符串,他们要在自己的程序中将对象构造成 XML,之后,Axis 把他们处理后的 XML 又一次包装成了更加臃肿的 XML,而当他们获取数据时,Axis 把收到的数据作为 XML 解析,但解析后仍然是一个 XML,接着他们自己的程序再将经过一次解析后的 XML 解析为对象。也就是说,他们用 XML 包装了 XML!这是一个多么愚蠢、多么可笑的对 ~WebService 的应用啊!可是当我问他们为什么要这样愚蠢的```当然在我问他们时,并没有在嘴上说出这个形容词,但在心里不知道已经说了多少遍了```来使用 ~WebService 时,他们的回答更是让我吃惊,他们居然说如果不这样做,根本不能传递对象!我想事实上并不是不能够传递对象,而是因为它太难用了,以至于让人根本无法掌握传递对象的办法。
当然,如果你是 Java ~WebService 这方面的专家的话,你可能会说,这不是 ~WebService 的问题,而是因为这些使用者太愚蠢了。或者你还会责怪他们所使用的工具太过时了,如果他们用 ~CXF2 这类新的 ~WebService 工具事情可能就会简单很多了。可是事实真的是这样吗?你能告诉我你到底用了多少时间才成为了这方面的专家吗?是几个星期?还是几个月?甚至几年?是的,要成为这方面的专家,你确实需要一段很长的时间。
从后来的例子,也证实了我的这个观点,并不是开发者们愚蠢,而是现在的那些 ~WebService 实在太难以理解和应用了。那是我在学校参与的最后一个项目——一个省级的考试报名系统,这个项目也是基于 ~J2EE 来开发的。这个系统需要在每个高校部署一个独立的报名系统,在省教育厅部署一个集中的服务器处理上报来的数据,这需要在各个高校与省教育厅之间进行数据交互。另外,这两个部分并不是都由我们来完成的,而是分别由两个高校做。我们学校负责高校报名系统,而省教育厅的部分由另一个比我们学校实力更强的高校来负责。
当然到了这个系统最终要整合时,我们和另一个高校就需要坐下来讨论该用何种方式在两个相对独立又紧密联系的系统之间交换数据了。他们首先想到的当然是 ~WebService,并且跟我们大谈了一番 ~WebService 的好处,我们确实差一点被他们说动了,让我们认为他们确实有这方面充足的开发经验和良好体验。不过我们还是提出了我们的方案,那就是使用 PHPRPC,他们当然没听说过,也不敢相信 PHPRPC 能够代替 ~WebService 来做同样的事情,他们更不敢相信 PHPRPC 会做的更好。所以,最终 PHPRPC 只是作为一个备用方案,也就是说如果他们使用 ~WebService 失败了的话,再上 PHPRPC。
要知道,这个方案从确定下来到最终上线并开始使用一共只有 2 个星期,而第一个星期的时间他们全都浪费在了 ~WebService 上。在第一个星期快要结束时,他们还没有搞定如何来用 ~WebService 传输那些并不算复杂的报名数据结构,更不要说安全的传输了。他们终于打电话过来告诉我们他们的 ~WebService 计划并没有成功,他们想换 PHPRPC 来试一下。于是当天下午,他们就派人带着他们的系统过来,准备用 PHPRPC 试一下可不可以完成他们认为不可能完成的任务。
不好意思的说,在当时,PHPRPC for Java 还不算成熟,不要说详细的用户指南,连一个简单的使用说明都还没有。于是当天下午,我先用了不到一个小时的时间写了一个简单的使用说明给他们。他们对照我写的简单说明,只用了不到一个小时的时间,就解决了他们用 ~WebService 花了一个星期都没有解决的问题。他们简直不敢相信这么简单好用的 PHPRPC 是我一个人完成的。就这样,最终使用 PHPRPC 的方案确定了下来。
之后的一切都变得很顺利了,系统最后基本按时上线```比预定的上线时间还是晚了一天,但导致时间延期一天的问题是出在对系统的打包安装上面,其中 ~MySQL 打包到安装程序中的时间比他们预期的时间要多得多。但这跟 PHPRPC 没有丝毫关系。```。系统上线后,他们又在在线照片采集上遇到了问题,开始他们用的是 Java Applet + RMI 来做的照片采集,可是这个 Java Applet 在对摄像头的控制上需要安装一个媒体包到每一个使用这个 Web 系统报名的桌面,这显然是不现实的事情,更糟糕的是即使这样照出来的照片还是变形的,这个问题虽然不能怪到 RMI 头上,不过如果他们能在 Flash 中用 RMI,就不会用 Applet 来采集照片了,不是吗?最终他们还是把这个棘手的问题交给了我,我把照相客户端从 Java Applet 换成了 Flash,通讯从 RMI 换成了 PHPRPC,通过 PHPRPC for ~ActionScript 客户端到 PHPRPC for Java 的服务器端很容易就完成了这个不同语言之间的数据交换问题。
好了,我想我应该不需要再举更多的例子了,虽然类似的例子我还可以举出好多。但如果我再说太多,你可能就要把我当成卖大力丸```江湖上一种据说可以让人力大无穷的神奇药丸,但实际上可能只是糯米面团。```的啦,呵呵。所以,下面我们还是来看看如何具体的安装和使用 PHPRPC for Java 吧。
本章的内容比较多,放在一整页里不便于阅读,所以这里列了一个目录,你只要点击相应的条目,就可以进入你想看的章节了。
*[[PHPRPC for Java 的安装]]
*[[PHPRPC for Java 服务器]]
*[[PHPRPC for Java 客户端]]
*[[PHPRPC for Java 的其它工具]]
*[[PHPRPC for Java 常见问题解答]]
!!如何调用 PHPRPC 服务?
我们先通过一个简单的例子,来介绍如何调用 PHPRPC 服务:
<code java>
import org.phprpc.*;
import java.io.*;
interface IUpload {
public int open(String filename);
public void close(int handler);
public void write(int handler, byte[] b, int off, int len);
public void write(int handler, byte[] b);
}
public class UploadClient {
public static void main(String[] args) throws IOException {
if (args.length > 0) {
PHPRPC_Client client = new PHPRPC_Client("http://192.168.0.1/upload.jsp");
client.setProxy("10.1.0.1", 8000);
client.setKeyLength(512);
client.setEncryptMode(1);
String filename = args[0];
System.out.println(filename);
IUpload upload = (IUpload)client.useService(IUpload.class);
FileInputStream is = new FileInputStream(filename);
int handler = upload.open(filename);
byte[] bytes = new byte[102400];
int read = 0;
while ((read = is.read(bytes)) >= 0) {
upload.write(handler, bytes, 0, read);
}
upload.close(handler);
}
}
}
</code>
上面这个 {{{UploadClient}}} 例子中,我们调用的是前面发布的那个 Upload 服务。
首先我们定义了一个接口 {{{IUpload}}},这个接口中的方法声明跟前面服务中 Upload 类所提供的方法的声明是一致的,但是在这个接口中不需要声明抛出错误。
然后,我们创建了一个 {{{PHPRPC_Client}}} 对象 {{{client}}},创建时的参数是 PHPRPC 服务器的 URL,如果我们在创建 {{{client}}} 对象时不指定服务器的 URL,后面我们也可以在调用 {{{useService}}} 方法时再指定服务器的 URL。
接下来,通过 {{{client}}} 的 {{{setProxy}}} 方法,我们可以设置连接到服务器所经过的 HTTP 代理服务器,代理服务器的设置除了可以单独指定 IP 地址和端口号以外,还可以直接通过 http://10.1.0.1:8000/ 这种 URL 方式来指定代理服务器。另外,{{{setProxy}}} 方法也支持 Basic 方式认证,只要将用户名和密码作为参数带入即可。
{{{PHPRPC_Client}}} 对象的 {{{setKeyLength}}} 和 {{{setEncryptMode}}} 这两个方法是跟加密传输有关的。
{{{setKeyLength}}} 方法用于设置加密传输前[[密钥长度]],默认长度是 128。
{{{setEncryptMode}}} 方法用于设置[[加密模式]],默认值为 0。
上面设置代理、设置密钥长度、加密模式都是可选项,如果你不需要这些功能,可以直接忽略它们。
接下来,我们调用了 {{{client}}} 的 {{{useService}}} 方法,因为前面在创建 {{{client}}} 时已经指定了服务器端的 URL,这里调用 {{{useService}}} 时,我们只要指定返回的代理对象的接口类型就可以了。它的返回值我们通过转型操作,将它转型为 {{{IUpload}}} 的一个对象 {{{upload}}}。
在最后的代码里面,我们直接通过 {{{upload}}} 的 {{{open}}}、{{{write}}} 和 {{{close}}} 方法来调用远程过程,看上去跟调用本地的方法没有什么区别。
通过这个例子,我想你已经可以掌握 PHPRPC for Java 客户端的基本使用方法了。
!!如何在调用 PHPRPC 服务时,进行引用参数传递?
我们可以直接调用 {{{PHPRPC_Client}}} 对象的 {{{invoke}}} 方法进行引用参数传递的远程过程调用。上面的例子中,我们是通过先声明接口,然后返回代理对象,再通过代理对象来作远程调用的,而代理对象本身实际上也是通过调用 {{{PHPRPC_Client}}} 的 {{{invoke}}} 方法来实现远程调用的。下面我们来通过一个简单的例子,看一下如何直接用 {{{invoke}}} 方法来调用远程过程,并实现引用参数传递。这个例子中我们用 Java 版本的客户端来调用 PHP 版本的服务器端。下面是服务器端的 PHP 代码:
<code php:firstline[0]>
<?php
include('phprpc_server.php');
function inc(&$n) {
$n++;
}
$phprpc_server = new PHPRPC_Server();
$phprpc_server->add('inc');
$phprpc_server->start();
?>
</code>
这个服务器发布了一个 {{{inc}}} 方法,该方法是将参数值加一。这个方法是需要引用参数传递的。下面我们来看看如何在 Java 中调用这个远程过程:
<code java>
import org.phprpc.*;
import java.io.*;
public class IncClient {
public static void main(String[] args) {
if (args.length > 0) {
PHPRPC_Client client = new PHPRPC_Client("http://192.168.0.1/inc.php");
Object[] a = new Object[1];
a[0] = args[0];
System.out.println(a[0]);
client.invoke("inc", a, true);
System.out.println(a[0]);
}
}
}
</code>
这个例子中,我们定义的 {{{Object}}} 数组 {{{a}}} 就是要传给服务器的参数列表,因为只有一个参数,所以这个初始化时,只给这个数组分配了一个元素的空间。我们首先把给参数赋值,我们是通过命令行方式来给它赋值的,在调用之后,我们再次输出 {{{a[0]}}} 的值,我们会发现它确实被加了 1。这里我们还发现,我们通过命令行传给 {{{a[0]}}} 的实际是个字符串,但是远程过程调用却把它作为数值处理了,而且返回的 {{{a[0]}}} 也变成了数值。你可以通过在调用前和调用后,分别加上:
<code java:nocontrols>
System.out.println(a[0].getClass().toString());
</code>
来验证我们的结果。也就是说,PHPRPC 的远程调用是弱类型参数传递的,返回结果也是弱类型的,PHPRPC 会根据实际调用来自动进行类型转换。
{{{invoke}}} 有两个重载方法:
<code java:nocontrols>
Object invoke(String funcname, Object[] args);
Object invoke(String funcname, Object[] args, boolean byRef);
</code>
第一个参数是远程方法名,第二个参数是远程方法的参数列表,第三个参数表示是否进行引用参数传递。当我们调用有 3 个参数的重载方法,并将第三个参数 {{{byRef}}} 设置为 {{{true}}} 时,就可以进行引用参数传递了。
第二个参数 {{{args}}} 在进行引用参数传递后,会返回修改后的参数值列表。在直接使用 {{{invoke}}} 进行远程调用时,我们需要自己处理返回值的转型操作。
!!如何得到服务器端输出重定向的内容?
前面在介绍服务器端输出重定向时,我们已经提到过如何在客户端获取输出内容的方法了。这里我们详细介绍一下。
当我们通过代理对象的方法或直接通过 {{{PHPRPC_Client}}} 对象的 {{{invoke}}} 方法进行远程调用之后,我们可以使用 {{{PHPRPC_Client}}} 对象的 {{{getOutput}}} 方法来得到服务器端输出的内容。如果服务器端没有输出内容,该方法会返回空字符串。
!!如何来得到远程过程执行的错误信息?
PHPRPC 把错误分为两种,一种是致命错误,一种是警告性错误。
当远程过程执行发生致命错误时,如果客户端是通过 {{{invoke}}} 方法直接调用的,则 {{{invoke}}} 方法的返回值是一个 {{{PHPRPC_Error}}} 类型的对象,它其中包含了远程过程执行时发生的致命错误信息。如果客户端是通过代理对象的方法来进行的远程过程调用,则会抛出一个错误,错误对象的类型也是 {{{PHPRPC_Error}}} 类型。
当远程过程执行发生警告性错误时,不管客户端使用何种方式调用,都会返回结果值,但是你可以通过 {{{PHPRPC_Client}}} 对象的 {{{getWarning}}} 方法来得到警告错误,{{{getWarning}}} 方法的返回的值也是 {{{PHPRPC_Error}}} 类型的对象。如果没有发生警告错误,{{{getWarning}}} 方法返回 {{{null}}}。
另外,如果通过代理对象的方法来进行的远程过程调用,你可能会得到更多的错误信息,例如当返回值的类型跟代理对象接口声明的返回类型不兼容时,就会抛出类型转换错误。因此当进行非引用参数传递的远程过程调用时,我们推荐使用通过声明代理对象接口的方式来进行远程调用。这样不但让你的程序更直观,也会让你更加方便调试。
!!远程过程调用中的超时问题如何解决?
PHPRPC for Java 客户端在进行远程过程调用时,默认的超时时间为 30 秒,如果你的网络线路不好,或者需要传递的数据量较大,或者服务器端处理数据的时间较长,则在执行过程中会发生超时错误。这时,只需要调用 {{{PHPRPC_Client}}} 对象的 {{{setTimeout}}} 方法来设置超时时间就可以了,单位是毫秒。
!!远程过程调用中参数长度是否有限制?
PHPRPC 客户端本身没有对参数长度做限制,但是因为 PHPRPC 协议是通过 HTTP 协议进行传输的,如果 PHPRPC 服务器端的设置限制了 HTTP POST 数据的长度(例如 Tomcat 默认设置最大允许的 POST 数据量为 2M),则调用过程中,如果参数序列化之后的长度大于了这个限制,就会调用失败。
另外,如果服务器没有限制 POST 数据的大小,则客户端可以提交任意大小的数据,直到服务器内存耗尽才会出错。
PHPRPC 传输的数据量通常小于同样调用的 Web Service 传输的数据量的。因此,PHPRPC 调用所允许的参数长度一般比 Web Service 调用所允许的参数长度更大。
!!什么情况下才需要设置字符集?
PHPRPC 客户端和服务器都允许通过 {{{setCharset}}} 方法来设置字符集,但通常是不必要的,默认情况下,字符集为 ~UTF-8。字符集设置通常是跟单字节字符串语言(例如 PHP、Perl)进行通讯时才需要设置的,例如:当我们要在 Java 中远程调用一个 PHP 语言提供的 PHPRPC 服务时,而这个服务使用的是 GBK 字符集,我们可以通过 {{{PHPRPC_Client}}} 的 {{{setCharset}}} 方法将字符集设置为 GBK,然后就可以正确的调用了。
但通常我们不推荐使用 ~UTF-8 以外的其它字符集。即使是 PHP 这种语言所提供的服务,我们也推荐使用 ~UTF-8 字符集,因为有许多客户端(~JavaScript,~ActionScript)或者服务器(ASP)实现只支持 ~UTF-8 字符集,如果我们使用其它字符集,将不能跟这些服务器和客户端进行通讯。
!!方法重载需要注意什么问题?
前面我们已经提到过,PHPRPC for Java 支持方法的重载,但是有几点问题需要注意。
最重要的一点是最好避免发布重载的方法。因为服务器处理重载方法比非重载方法效率要低。另外,如果你不发布重载的方法,也就不需要注意下面这些问题了。
首先,应尽量避免重载参数个数相同且参数类型相容的方法。原因是 PHPRPC 支持弱类型参数传递。如果你的服务器端的方法有类型相容的重载方法,最后客户端实际调用的方法可能不是你希望调用的方法。
如本指南一开始的例子中,我们发布了一个 {{{Math.min}}} 方法,它有 4 个重载版本,参数类型分别是 {{{double}}}、{{{float}}}、{{{int}}} 和 {{{long}}}。这四种类型是相容的,因此,如果客户端调用该方法时,如果带入的参数是浮点类型,服务器端有可能调用 {{{double}}} 或 {{{float}}} 类型的方法,也有可能调用到 {{{int}}} 或 {{{long}}} 类型的方法,服务器端检测的顺序是跟 JDK 实现有关的,因此很难说最后调用到的是哪个。如果不幸调用了 {{{int}}} 类型的方法,那么返回的结果就不对了。
如果遇到这种情况如何避免呢?因为这四个方法都是在 {{{Math}}} 类中的,因此使用 PHPRPC for Java 服务器端的别名机制是无法解决的。但我们可以重新定义一个类,来给这四个方法取不同的名字。例如:
<code java>
public class MyMath {
public static double doubleMin(double a, double b) {
return Math.min(a, b);
}
public static float floatMin(float a, float b) {
return Math.min(a, b);
}
public static int intMin(int a, int b) {
return Math.min(a, b);
}
public static long longMin(long a, long b) {
return Math.min(a, b);
}
}
</code>
这样,我们发布 {{{MyMath}}} 的 {{{doubleMin}}}、{{{floatMin}}}、{{{intMin}}} 和 {{{longMin}}} 这四个方法,就可以避免相容类型的重载带来的问题了。这时,客户端再调用服务器端的 {{{doubleMin}}},就会按照声明的 {{{double}}} 类型进行参数传递了。
第二个需要注意的问题是,不要重载除了隐含参数以外,其它参数都相同或相容的方法。这里说的隐含参数指的是前面我们提到的 {{{session}}}、{{{request}}}、{{{context}}} 和服务器端输出重定向参数。
例如下面的重载方法发布后,在客户端调用时会产生问题:
<code java>
public class Hello {
public static String hello(String a) {
return "hello " + a;
}
public static void hello(String a, java.io.PrintStream out) {
out.println("hello " + a);
}
}
</code>
当客户端调用这个 {{{hello}}} 方法时,因为 {{{out}}} 这个参数不是在客户端传入的,因此,当服务器端调用该方法时,可能调用了第一个方法,也可能调用了第二个方法。这种情况下,你就无法确定你调用的是哪个方法了。
!!哪些类型是相容类型?
PHPRPC 是支持弱类型(不是无类型,这一点一定要区分开)参数传递的,这是它跟 Web Service、Java RMI 等远程过程调用机制的一个重要区别。弱类型参数传递的支持对于目前动态脚本语言来说是一个很大的特征,这个特征会让你的程序设计变得更加灵活。
对于弱类型语言来说,相容类型之间是可以根据实际调用情况来自动转换的,而且这个转换是双向的而不是单向的。例如,数字可以转换为字符串,字符串也可以转换为数字。
PHPRPC 中支持的所有简单类型都是相容类型,它们包括整数、实数、布尔类型和字符串。在 PHPRPC for Java 中,所有的原生类型以及 {{{Byte}}}、{{{Short}}}、{{{Integer}}}、{{{Long}}}、{{{Float}}}、{{{Double}}}、{{{Boolean}}}、{{{String}}} 类型都是相容类型。
另外,{{{String}}}、{{{StringBuilder}}},{{{char[]}}} 和 {{{byte[]}}} 之间是相容类型。但 {{{byte[]}}} 类型更适合传输 binary 类型的数据,例如文件、图片等。
数组类型、{{{ArrayList}}}、{{{HashMap}}} 和 {{{AssocArray}}} 是相容类型。如果 {{{ArrayList}}} 中存放的类型是单一的数据类型,那么它跟该数据类型的数组是相容的。如果 {{{HashMap}}} 中的索引键都是数组,并且是从零开始递增的,那么它与 {{{ArrayList}}} 是相容的。{{{ArrayList}}} 和 {{{HashMap}}} 类型都可以通过 {{{AssocArray}}} 的构造方法转换为 {{{AssocArray}}} 类型,另外,其它的实现了 {{{Collection}}} 接口或者 {{{Map}}} 接口的类型也都可以通过 {{{AssocArray}}} 的构造方法转换为 {{{AssocArray}}} 类型。
上面所说的这些类型都是可以通过 PHPRPC 直接跟其它语言交互的。
另外,{{{java.util.Date}}} 类型比较特殊,PHPRPC 会作为一个特殊的对象对它序列化,序列化后的类名为 {{{PHPRPC_Date}}},该类与其他语言中相应的日期类型相容。
!!PHPRPC for Java 是否支持自定义类型?
PHPRPC for Java 当然也支持自定义类型,并且自定义类型也可以跟其它语言交互。关于自定义类型需要注意以下几个问题。
自定义类型必须是可序列化的类型。也就是实现了 {{{java.io.Serializable}}} 接口,并且该类型当中所有字段(不包括方法)都是可序列化的类型。否则,在调用过程中,传递该类型的参数或结果时会抛出空指针异常({{{NullPointerException}}})。
只有相同包中的同名类型才被认为是同一类型,如果两个类型同名,并且定义也完全相同,但是分属于两个不同的包,那么它们就是不同的类型。如果客户端所传递的类型和服务器端接收的类型是这样的两个分属于不同包中的同名类型,调用时会抛出类型转换错误({{{ClassCastException}}})。
Java 中的自定义类型跟其它语言交互时,如果另一种语言不支持包机制和名称空间机制(比如 PHP 或 ~JavaScript),那么在该语言中定义的类名与Java 中点号分割符和美元分割符被替换成下滑线分割符后的自定义类型全名相同。例如:Java 中如果定义了一个 {{{demo.data.MyData}}} 类,那么在 PHP 中应该定义名为 {{{demo_data_MyData}}} 的类来跟 Java 中的 {{{demo.data.MyData}}} 进行交互。并且,应该保证 {{{demo_data_MyData}}} 中的所有字段和 Java 中的 {{{demo.data.MyData}}} 中的所有字段名称一致。
事实上,PHPRPC 在传递 Java 对象时,对 Java 类的全名的序列化处理就是将点号和美元符号都替换成下滑线。因此,当还原回 Java 对象时,自然要对类名进行相反操作的替换匹配。因此,在 Java 中不要定义全名中除点号和美元符号外,其它部分都完全相同的类,否则,这些类中只有一个可以被还原。
!!PHPRPC for Java 是否支持 Java 中的其它内建类型?
只要是实现了序列化接口的 Java 类,都可以通过 PHPRPC 传递,不管是内建类型还是自定义类型。不过如果要跟其它语言进行交互。你需要在其它语言中实现跟 Java 中的内建类型定义相同的类,包括内部的各个字段名称和类型都要一致(或相容)才可以。要实现这一点,对用户来说比较困难。因此不推荐使用其它的 Java 内建类型作为参数或结果类型传递,除非你只打算把这个服务提供给 Java 客户端来调用。
!!传递数组和 ~ArrayList 哪个执行效率更高?
你可能会认为也许数组更高效一点,其实不然。在 PHPRPC 中,如果你用 {{{ArrayList}}} 作为参数或者作为返回结果的类型,要比具体的数组类型更高效。原因在于 PHPRPC for Java 在反序列化数据时,对于数组类型的数据都是先反序列化为 {{{AssocArray}}} 类型,然后再转换 {{{ArrayList}}} 类型,最后转换为数组类型。{{{AssocArray}}} 到 {{{ArrayList}}} 的转换效率是很高的。如果你直接用 {{{ArrayList}}} 作为参数,则省去了从 {{{ArrayList}}} 到数组转换这一步,因此,执行效率更高。尤其是当你传递的数组中包含很多数据时,用 {{{ArrayList}}} 会节省很多内存、CPU 资源以及执行时间。
!!~ArrayList 和 ~HashMap 中的元素数据类型如何转换?
{{{ArrayList}}} 和 {{{HashMap}}} 作为结果返回或者作为参数传递给服务器处理的时候,对它们当中元素类型的处理最好不要用直接的转型操作,因为数据的真实类型可能不是你所认为的,所以直接转型的操作很可能会失败。最好的方式是使用 {{{Cast}}} 类的 {{{cast}}} 方法来进行转型。
!!org.phprpc.util.Serializable 这个接口是做什么用的?
PHPRPC 除了支持简单的继承 {{{java.io.Serializable}}} 接口这种序列化方式以外,还支持用户自定义的序列化方式。而且用户自定义序列化方式也分两种。这两种都跟 PHP 语言所提供的用户自定义序列化方式类似。{{{org.phprpc.util.Serializable}}} 接口就是其中的一种方式,该方式跟 ~PHP5 中所提供的用户自定义序列化方式兼容。
{{{org.phprpc.util.Serializable}}} 中定义了两个方法,如果一个类实现了 {{{java.io.Serializable}}} 接口(这是一个很关键的前提)并且同时实现了 {{{org.phprpc.util.Serializable}}} 接口中定义的两个方法,那么它就具有了自定义序列化方式的能力。
{{{org.phprpc.util.Serializable}}} 中的 {{{serialize}}} 方法是在对象被序列化时自动触发的,你可以在 serialize 方法中对该对象做任何方式的序列化,例如 SOAP、JSON 或者 WDDX 等,只要返回的结果是字节数组就可以了。
{{{org.phprpc.util.Serializable}}} 中的 {{{unserialize}}} 方法是在对象被反序列化时自动触发的,传入的参数就是前面 {{{serialize}}} 方法的返回内容。你可以用同样的方式将它反序列化。{{{unserialize}}} 方法没有返回值。
一般情况下,我们没有必要使用这种方式来自定义序列化,如果你只是想要将类中的部分字段序列化,或者只是想在对象被序列化之前做一些清除操作的话,那么你可以实现 {{{__sleep}}} 和 {{{__wakeup}}} 这两个魔术方法来实现。
!!{{{__sleep}}} 和 {{{__wakeup}}} 这两个魔术方法如何使用?
{{{__sleep}}} 和 {{{__wakeup}}} 方法也是与 PHP 自定义序列化相兼容的一种方式。这两个方法不需要从某个接口继承,直接在类中实现即可。
{{{__sleep}}} 方法也是在对象被序列化之前自动触发,但是它的返回值不是序列化后的内容,而是指定哪些字段需要序列化。它的返回值类型是一个字符串数组,数组的每一个元素是需要序列化的字段名。你除了在 {{{__sleep}}} 方法中返回这些字段之外,你也可以作其他一些操作,比如关闭 socket 连接或者关闭数据库连接等操作。
{{{__wakeup}}} 方法是在对象被反序列化之后自动触发的,它被触发时,所有序列化的字段都已经被反序列化完毕,所以,你在 {{{__wakeup}}} 中要做的是反序列化的事后工作,比如恢复数据库连接或者 socket 连接等。
{{{__sleep}}} 和 {{{__wakeup}}} 方法无需同时实现,你只需把你用到的方法实现即可。
另外,{{{__sleep}}} 和 {{{__wakeup}}} 方法和前面提到的 {{{org.phprpc.util.Serializable}}} 接口中的 {{{serialize}}} 和 {{{unserialize}}} 方法都无需定义成 {{{public}}} 方法,定义成 {{{private}}} 即可。
!!~J2ME 版本如何支持自定义对象?
只要从 {{{stdClass}}} 继承的就可以了。你甚至不需要去实现它,你可以通过 {{{get}}} 去获取它的属性,{{{set}}} 去设置它的属性。但如果你要打算重新实现 {{{get}}} 或 {{{set}}} 方法,那么你需要重写 {{{getProperty}}} 和 {{{setProperty}}} 方法,而不是重写 {{{get}}} 和 {{{set}}} 方法。
!!客户端是否支持 Android ?
对于现在的 Android SDK,可以完美支持!
但是因为老版本的 Android SDK 的 {{{java.lang.reflect.Proxy}}} 类不能正常工作,所以,只能使用 {{{invoke}}} 方法来调用远程过程,而不能使用声明代理对象接口的方式。
新的(从 android-sdk_m5-rc15 之后)Android SDK 的 {{{java.lang.reflect.Proxy}}} 类已经可以正常工作,所以,现在开发 Android 程序跟开发普通的 ~J2SE 程序中使用 PHPRPC 的方法已经没有区别了。
!!PHPRPC for Java 支持哪些 ~J2EE 服务器?
目前已经完全通过测试的服务器有 Tomcat、Resin、~JRun 和 Jetty,在这些服务器的各个版本上都可以正常运行。其它的 ~J2EE 服务器我们因为无法得到相应的服务器软件而未做测试,因此这并非说它不支持其它的 ~J2EE 服务器。我们非常欢迎您能够报告您在其他服务器上的部署情况,以帮助我们改善该开发包。
!!如何发布服务?
Java 中的对象方法或类的静态方法都可以直接作为 PHPRPC 服务来发布。但发布的方法必须是用 {{{public}}} 声明的方法,而且参数和返回值都必须是可序列化的类型(继承自 {{{java.io.Serializable}}} 的类型)。除此之外,没有别的要求。
服务可以通过 JSP 发布,也可以直接通过 Servlet 发布。下面我们举一个简单的用 JSP 发布的例子:
''rpc.jsp''
<code java>
<%@ page import="java.lang.*" %>
<%@ page import="org.phprpc.*" %>
<%
PHPRPC_Server phprpc_server = new PHPRPC_Server();
phprpc_server.add("abs", Math.class);
phprpc_server.add("min", Math.class);
phprpc_server.start(request, response);
%>
</code>
上面的代码发布的是 {{{java.lang.Math}}} 类中的 {{{abs}}} 和 {{{min}}} 这两个静态方法,它们都有重载方法,PHPRPC 支持重载方法的发布。
通过这个例子,你会发现用 PHPRPC 发布服务很简单,只要先创建 {{{PHPRPC_Server}}} 类的一个对象实例,然后执行它的 {{{add}}} 方法来添加要发布的方法,最后执行它的 {{{start}}} 方法来启动服务就可以。{{{start}}} 方法的两个参数 {{{request}}} 和 {{{response}}} 是固定的。
另外,提醒大家一点,不要忘记发布服务时,把 phprpc.jar 放到服务页面所在的 {{{WEB-INF\lib}}} 目录下。服务页面的名字没有要求,rpc.jsp、index.jsp 或其它什么名字都可以。只要在调用时指定的 URL 与发布时的名字一样就可以了。
!!如何发布一个对象方法?
上面的例子中发布的是静态方法,如果要发布对象方法,只需要把 {{{add}}} 第二个参数换成方法所在的对象即可。
!!如何发布整个对象中的方法或整个类中的静态方法?
如果在使用 {{{add}}} 方法时,只代入一个 {{{Object}}} 类型的参数,则该对象上的所有 {{{public}}} 方法都会作为服务发布。如果只代入一个 {{{Class}}} 类型的参数,则该类上的所有 {{{static public}}} 方法都会作为服务发布。
!!如何发布一个对象中的一组方法或一个类中一组静态方法?
如果第一个参数不是字符串,而是字符串数组,则所有与该数组中字符串值匹配的 {{{public}}} 方法都会作为服务发布。
!!如何用别名来发布方法?
有时你发布的几个对象或类中的方法可能具有相同的方法名,你也许会希望用不同的名字来发布它们,以便能够将它们区分开来。例如,你可能要同时发布一个 User 类和一个 Catalog 类,但是它们都有 add、update、delete 方法,那么你可以通过这种方式来发布它们:
<code java>
<%@ page import="demo.User" %>
<%@ page import="demo.Catalog" %>
<%@ page import="org.phprpc.*" %>
<%
PHPRPC_Server phprpc_server = new PHPRPC_Server();
phprpc_server.add("add", User.Class, "addUser");
phprpc_server.add("update", User.Class, "updateUser");
phprpc_server.add("delete", User.Class, "deleteUser");
phprpc_server.add(new String[] { "add",
"update",
"delete"
},
Catalog.class,
new String[] { "addCatalog",
"updateCatalog",
"deleteCatalog"
}
);
phprpc_server.start(request, response);
%>
</code>
从上面的代码中我们可以看到,不管是添加一个方法还是添加一组方法,都是支持别名的。当然,如果 {{{User.add}}} 和 {{{Catalog.add}}} 方法的参数类型不同,并且不能相互转换的话,那么你也可以用同一个名字来发布它们,当客户端用不同的参数调用时,服务器端会根据参数类型来判断该调用哪个类的 {{{add}}} 方法,也就是说,PHPRPC 所发布的服务,不但支持同一个类或对象中方法的重载,而且支持不同的类或对象中方法的重载。
当然,在这种情况下,我们绝不推荐您使用这种不同的类或对象中方法的重载特性,用不同的别名来发布方法,可以减少服务器查找方法的时间。
!!如何在发布的方法中使用会话(session)?
有时,你可能希望服务器端发布的对象支持会话功能,例如下面这个类:
<code java>
package demo;
import java.io.*;
import java.util.*;
final public class Upload {
private ArrayList files;
public Upload() {
files = new ArrayList();
}
final public int open(String filename) throws IOException {
try {
FileOutputStream file = new FileOutputStream(filename);
int length = files.size();
for (int i = 0; i < length; i++) {
if (files.get(i) == null) {
files.set(i, file);
return i;
}
}
files.add(file);
return (files.size() - 1);
}
catch (Exception e) {
return -1;
}
}
final public void close(int handler) throws IOException {
FileOutputStream file = (FileOutputStream)files.get(handler);
file.close();
files.set(handler, null);
}
final public void write(int handler, byte[] b, int off, int len) throws IOException {
FileOutputStream file = (FileOutputStream)files.get(handler);
file.write(b, off, len);
}
final public void write(int handler, byte[] b) throws IOException {
FileOutputStream file = (FileOutputStream)files.get(handler);
file.write(b);
}
}
</code>
这个类中有 4 个方法,一个 {{{open}}},一个 {{{close}}},还有两个 {{{write}}} 方法,如果我们在发布方法时,仅仅简单的创建 {{{Upload}}} 对象,并且直接将它的这些方法发布的话,那么客户端调用 {{{open}}} 方法后,再调用 {{{write}}} 方法时,将无法通过 {{{open}}} 返回的 {{{handler}}} 来找到已经打开的 {{{FileOutputStream}}} 对象,因为,客户端调用的 {{{write}}} 方法是一个新的 {{{Upload}}} 对象中的,而不是原来那个执行过 {{{open}}} 方法的 {{{Upload}}} 对象中的 {{{write}}} 方法了。
这时我们就需要会话支持了。这种情况下,只需要将创建的 {{{Upload}}} 对象放入 Session 中,下次调用时,从 Session 中把 {{{Upload}}} 对象取出再发布就可以了:
<code java>
<%@ page import="demo.Upload" %>
<%@ page import="org.phprpc.*" %>
<%
Upload upload;
if (session.getAttribute("upload") == null) {
upload = new Upload();
session.setAttribute("upload", upload);
}
else {
upload = (Upload)session.getAttribute("upload");
}
PHPRPC_Server phprpc_server = new PHPRPC_Server();
phprpc_server.add(upload);
phprpc_server.start(request, response);
%>
</code>
通过上面这段 jsp 代码,就可以正确的发布我们的服务了。
但有时我们可能希望更细粒度的控制会话,比如我们可能需要在方法中存取会话变量的值。PHPRPC 提供了这样的能力。只要在定义要发布的方法时,将最后的参数设置为 {{{javax.servlet.http.HttpSession}}} 类型的变量,就可以直接在方法中存取会话了。客户端在调用时这种方法时,只需要代入其它参数即可,该会话参数应直接忽略,PHPRPC 服务会自动将 {{{session}}} 变量传递给该参数。
!!如何在发布的方法中使用 request 对象?
有时我们还需要在远程方法中直接获取 {{{request}}} 对象中的参数,PHPRPC 也提供了这种直接在远程方法中直接存取 {{{request}}} 对象的能力。只要在定义要发布的方法时,将最后一个参数设置为 {{{javax.servlet.http.HttpServletRequest}}} 类型的变量,PHPRPC 服务就会自动把 {{{request}}} 变量传入了。另外,在同一个方法中,{{{javax.servlet.http.HttpServletRequest}}} 和 {{{javax.servlet.http.HttpSession}}} 这两个类型的参数不能同时定义。如果你需要同时存取 {{{request}}} 对象和 {{{session}}} 对象,你只需要定义 {{{javax.servlet.http.HttpServletRequest}}} 类型的参数,然后通过 {{{request}}} 对象的 {{{getSession}}} 方法来获取 {{{session}}} 对象就可以了。
同样,你还可以将最后一个参数设置为 {{{javax.servlet.ServletContext}}} 类型的变量,让 PHPRPC 自动传入 {{{context}}} 对象,或者通过 {{{session}}} 对象的 {{{getServletContext}}} 方法来获取它。
总之,你可以在定义远程方法时,将最后一个参数设置为以上三种类型之一,就可以自由存取 {{{request}}}、{{{session}}} 和 {{{context}}} 对象了。
!!如何让发布的方法支持输出重定向?
有时,我们定义的方法除了返回结果以外,还会输出一些信息,在远程调用这种方法时,我们可能会希望客户端不但可以得到远程方法的结果,还可以单独得到这些输出信息。PHPRPC 提供了这种能力。
你只需要将最后一个参数设置为 {{{java.io.PrintStream}}} 或 {{{java.io.PrintWriter}}} 类型的变量(任选其一,但不能同时定义它们),就可以通过该变量输出信息,并返回给客户端了。客户端在进行远程调用时,同样不需要代入该参数,而是通过 {{{getOutput}}} 方法来获取远程的输出信息。
输出重定向参数与前面提到的 {{{request}}}、{{{session}}} 和 {{{context}}} 参数可以同时定义,它们之间没有冲突,它们之间的前后顺序也是任意的,只要保证这两个参数的都在其它需要客户端传入的参数的后面就可以了。
当你需要返回给客户端许多字符串形式的内容时,我们推荐你用这种输出重定向方式来返回,因为它不需要服务器端序列化和客户端反序列化的过程,可以大大提高服务器和客户端的处理速度。
!!如何在服务器端方法执行出错时,返回更多的调试信息?
在发布服务时,只需要先执行 {{{setDebugMode}}} 方法,将参数值设置为 {{{true}}},然后再执行 {{{start}}} 方法就可以返回更加详细的出错调试信息了。
!!如何启用服务器端的压缩输出特性?
PHPRPC 服务是使用 HTTP 协议来传输信息的,因此它同样支持 HTTP 协议中的压缩输出特性,只需要先执行 {{{setEnableGZIP}}} 方法,将参数值设置为 {{{true}}},然后再执行 {{{start}}} 方法就可以启用压缩输出特性了。但通常我们不推荐启用服务器端的压缩输出特性,因为压缩输出会占用服务器更多的 CPU 资源,增加服务器的处理负担。
另外一个关于启用压缩输出特性的问题是:如果你是通过 JSP 来发布 PHPRPC 服务的话,在某些服务器上(例如 Tomcat 5.0),你启用压缩输出后,可能会发生下列错误:
{{{
java.lang.IllegalStateException: getOutputStream() has already been called for this response
}}}
解决方法是,执行 {{{start}}} 方法后,再执行:
<code java>
out.clear();
out = pageContext.pushBody();
</code>
就可以了。
!!如何使用 Servlet 发布服务?
把发布服务的代码写到 Servlet 的 {{{service}}} 方法中就可以了,你也可以放在 {{{doGet}}} 和 {{{doPost}}} 方法中。
使用 Servlet 发布服务时,你还可以在 {{{init}}} 方法中使用 {{{PHPRPC_Server}}} 的 {{{addGlobal}}} 方法来添加要发布的方法,这样只在 Servlet 启动时添加一次,就可以全局可用,可以使你的服务器工作的更有效率,不过需要注意,你要处理好共享数据访问时可能遇到冲突的问题。
PHPRPC for Java 中除了服务器和客户端实现之外,还有一些工具类,这些工具类除了在服务器和客户端的实现中被调用之外,你也可以单独使用它们。下面是关于这些工具类的常见问题解答。
!!~AssocArray 类如何使用?
3.0.1 版中新增加了一个 {{{AssocArray}}} 类,该类主要用于优化反序列化联合数组类型时的效率。3.0.1 版本之前反序列化联合数组时,是根据下标类型来自动判断是 {{{ArrayList}}} 类型还是 {{{HashMap}}} 类型的,但这样做的缺点是,如果联合数组中包含对其自身的引用,则反序列化将会重复多次递归操作来完成,效率较低。而且其自动判断的类型也不一定准确。3.0.1 版本中的 {{{AssocArray}}} 类就是为解决这个问题而加入的。当反序列化联合数组时,直接反序列化为 {{{AssocArray}}} 类型的对象,而不再进行类型判断和递归,大大提高了反序列化的效率。并且,{{{AssocArray}}} 对象提供了 {{{toArrayList}}} 和 {{{toHashMap}}} 两个方法,可以在使用时转换为你需要的对象类型。引入 {{{AssocArray}}} 对象的另一个好处是在某些语言中对于数字下标的数组序列化时并不一定是按照数字顺序排序的,这种情况下,原来的实现将会反序列化为 {{{HashMap}}} 类型,而原来的 {{{Cast}}} 类中的 {{{cast}}} 方法不能将 {{{HashMap}}} 转换为 {{{ArrayList}}} 或数组对象,而 {{{AssocArray}}} 对象则可以接受不按数字顺序序列化的数组类型,并且当你将它转换为 {{{ArrayList}}} 时,将自动返回排好序的 {{{ArrayList}}}。
!!Cast 类如何使用?
PHPRPC for Java 包中的 {{{org.phprpc.util.Cast}}} 静态类提供了 PHPRPC 中相容类型(关于相容类型的解释请参见高级问题解答部分)的转化功能。其中最重要的方法是 {{{cast}}} 方法,它有几个重载,你可以根据需要来使用它进行类型转换,例如将数字转换为字符串,或者将字符串转换数字。你也可以通过该方法将 {{{AssocArray}}} 转换为 {{{ArrayList}}}、{{{HashMap}}} 或者数组。
该类是在 PHPRPC 进行远程调用时,进行自动类型转换用的,一般情况下,不建议直接使用。
!!~PHPSerializer 类如何使用?
该类提供了将 Java 对象和 PHP 序列化格式文本相互转换的功能。因为 Java 中的字符串在内部都是以 ~UTF16 编码表示的,而 PHP 序列化文本实际上是无字符编码集的字节流,因此在转换过程中需要指定字符集才能够进行正确的转换,默认使用 ~UTF-8 字符集在 Java 字符串和字节流之间进行转换。你可以通过 {{{setCharset}}} 方法来设置转换字符集。{{{PHPSerializer}}} 类中最重要的两个方法是 {{{serialize}}} 方法和 {{{unserialize}}} 方法,{{{serialize}}} 方法很简单,只要带入要序列化的对象,就可以返回序列化后的字节数组了。{{{unsierialize}}} 方法的参数除了需要序列化后的字节数组以外,还需要一个对象类型,如果没有带入这个对象类型参数,则返回的对象需要自己用 {{{Cast}}} 类的 {{{cast}}} 方法来转换为你需要的结果类型。
!!Base64 类如何使用?
{{{Base64}}} 是个简单实用的静态类,它有两个静态方法,一个是 {{{encode}}},另一个是 {{{decode}}}。{{{encode}}} 是编码方法,参数是要编码的字节数组,返回值是 {{{Base64}}} 字符串;{{{decode}}} 是 {{{encode}}} 的逆操作,参数是 {{{Base64}}} 字符串,返回值是原字节数组。
!!XXTEA 类如何使用?
{{{XXTEA}}} 类是用来加密解密数据的静态类,它也有两个静态方法,一个是 {{{encrypt}}},另一个是 {{{decrypt}}}。{{{encrypt}}} 是加密数据的方法,第一个参数是原文数据,第二个参数是密钥;{{{decrypt}}} 是解密数据的方法,第一个参数是密文数据,第二个参数是密钥。这两个方法的参数和返回值都是 {{{byte[]}}} 类型。
!!编译
PHPRPC for Java 有 2 个版本,一个是 for ~J2SE、~J2EE 的版本,另一个是 for ~J2ME 的版本。 后面我们所说的 PHPRPC for Java 就是指 for ~J2SE、~J2EE 的版本,而不包含 for ~J2ME 的版本。
PHPRPC for Java 支持在 JDK 1.4 及其更高版本上编译。编译前,你需要将你使用的 ~J2EE 服务器中的 servlet.jar(不同的服务器,这个文件名称也不同,比如 Tomcat 中叫做 servlet-api.jar)复制到 PHPRPC for Java 目录下,取代默认的 servlet.jar,然后执行 make.bat(Windows 下)或者 make(unix/linux 下),就会自动生成 phprpc.jar 和 phprpc_client.jar 了。你也可以通过 ant 来编译,build.xml 文件就是 ant 编译所需要的配置文件,但它不会生成 phprpc_client.jar 文件。如果你只需要使用客户端版本 phprpc_client.jar,直接用 make 批处理或脚本编译就可以使用,不需要替换 servlet.jar 文件,在这种情况下,虽然仍会生成 phprpc.jar,但是它的服务器端是不可用的。phprpc.jar 包含服务器端和客户端两部分,而 phprpc_client.jar 只包括客户端部分。
PHPRPC for ~J2ME 支持在 JDK 1.2 下编译,编译时需要安装 WTK。并修改 make.bat 中 WTK 的路径设置。实际上,你不需要自己编译,dist 目录下已经有编译好的版本了。
!!安装
不需要特别安装,直接复制到你的项目的库目录下就可以直接使用了。
PHPRPC 最早就是在 PHP 语言中实现的,并且在 [[PHPRPC 数据表示]]中序列化数据部分就是采用的 PHP 语言中提供的序列化方式,所以这也是最初命名为 PHPRPC 的原因。不过现在 PHPRPC 支持的语言已经多达十几种,并且因为它的序列化效率远远高于 XML 和 JSON,所以 PHPRPC 在命名解释上又有了新的含义,那就是完美的高性能远程过程调用(Perfect High Performance Remote Procedure Call)。不过既然 PHP 是让 PHPRPC 诞生的最初语言,那么我们就先来介绍 PHP 语言的 PHPRPC 实现吧。
本章的内容比较多,放在一整页里不便于阅读,所以这里列了一个目录,你只要点击相应的条目,就可以进入你想看的章节了。
*[[PHPRPC for PHP 的安装]]
*[[PHPRPC for PHP 服务器]]
*[[PHPRPC for PHP 客户端]]
*[[PHPRPC for PHP 的其它工具]]
*[[PHPRPC for PHP 常见问题解答]]
在[[快速入门]]的 HelloWorld 例子中,我们已经看过一个简单的客户端调用服务器端的例子了。这里我们来看一个更复杂一点的例子:
<code php:firstline[0]>
<?php
include ("php/phprpc_client.php");
$client = new PHPRPC_Client();
$client->setProxy(NULL);
$client->useService('http://127.0.0.1/server.php');
$client->setKeyLength(1000);
$client->setEncryptMode(3);
$client->setCharset('UTF-8');
$client->setTimeout(10);
echo $client->hi('PHPRPC'), "\r\n";
echo $client->getKeyLength(), "\r\n";
echo $client->getEncryptMode(), "\r\n";
echo $client->getCharset(), "\r\n";
echo $client->getTimeout(), "\r\n";
?>
</code>
这里,我们调用的是在上一节中发布的那个 {{{hi}}} 方法。这里你会发现,我们在创建 {{{PHPRPC_Client}}} 的实例对象时,并没有带任何参数,这样后面就需要用 useService 来指定服务器地址。
setProxy 方法是用来设置 HTTP 代理服务器地址的,当为 NULL,表示不使用代理服务器。设置代理服务器有 2 种方式,一种是通过 URL 形式:{{{http://username:password@host:port}}},这里面 username 和 password 是可选的,port 也是可选的。这样只要将第一个参数设置为这样的字符串就可以了。另一种是通过传递四个参数来设置这些信息,按照顺序分别是 host,port,username,password,其中 port,username,password 也都是可选的。
useService 方法用来设置服务器地址,如果已经在初始化对象时设置了地址,这个方法就不需要调用了。但你可以通过这个方法来随时改变客户端对象中设置的服务器地址。
setKeyLength 方法用来设置[[密钥长度]]。
setEncryptMode 方法用来设置[[加密模式]]。
setCharset 方法用来设置客户端的字符集。
setTimeout 方法用来设置超时时间,在 PHP 中这个超时时间的单位是秒。
getKeyLength 方法是用来获取密钥长度的,只有当进行过一次远程调用后,你才能得到协商后的值。否则,就是你通过 setKeyLength 设定的值(或者是默认值 128)。
getEncryptMode 方法是用来获取加密模式的,当进行过一次远程调用后,如果服务器不支持加密传输,该方法返回的结果为 0,否则为你设置的值。
getCharset 方法用来返回字符集,当进行过一次远程调用后,该方法将返回服务器端设置的字符集,否则返回你设置的字符集。
getTimeout 方法返回你设置的超时时间或者默认的超时时间(默认为 30 秒)。
!!引用参数传递
PHPRPC 客户端在通过方法名直接调用服务器端的方法时,是不采用引用参数传递的,原因在于远程过程调用跟本地过程调用的引用参数传递从本质上是不一样的。对于本地过程调用的引用参数传递,实际上是传递参数的地址(指针),这样本地过程才可能修改引用参数的值(不是修改它的地址指针)。因为本地引用参数传递是通过指针来实现的,所以这种调用的开销大部分情况下甚至比值传递还要低。而远程过程调用时,就完全不是这样了,我们不可能传递一个本地数据的指针到远端去,因为在远端服务器上的同一个内存地址上是找不到我们的本地数据的。因此,远程过程调用的参数传递,一定是将本地数据的值通过某种方式序列化后传递到远端的,也就是值传递,那么要想实现类似于本地过程调用中引用参数传递的效果(就是可以修改参数值),那么就要将服务器端修改后的参数的值也已某种方式序列化后再传回到本地。因此远程过程调用中的引用参数传递要比值传递的开销还要大。这也是在 PHPRPC 客户端实现中默认不采用引用参数传递的原因。
要想使用引用参数传递也不难,但这需要用 invoke 方法来调用远程服务。我们来看一个例子。假设我们的服务器端是用 PHP 发布了一个 PHP 内置的对数组排序的函数 sort,下面是我们在客户端对它的调用:
<code php:firstline[0]>
<?php
include ("php/phprpc_client.php");
$client = new PHPRPC_Client('http://127.0.0.1/server.php');
$fruits = array("lemon", "orange", "banana", "apple");
$args = array(&$fruits, SORT_STRING);
print_r($fruits);
$client->invoke('sort', $args, true);
print_r($fruits);
?>
</code>
执行后,我们会看到如下结果:
{{{
Array
(
[0] => lemon
[1] => orange
[2] => banana
[3] => apple
)
Array
(
[0] => apple
[1] => banana
[2] => lemon
[3] => orange
)
}}}
很好,它成功了。
这里我们要看到需要注意的三点:
第一,invoke 方法的第二个参数是一个引用参数,因此它必须是一个变量,所以我们不可以写成:
<code php:nocontrols>
$client->invoke('sort', array(&$fruits, SORT_STRING), true);
</code>
否则,你会得到类似于这样的错误信息:
{{{
Fatal error: Cannot pass parameter 2 by reference in ...
}}}
第二,如果在给 {{{$args}}} 赋值时,{{{$fruits}}} 如果不是一个引用的话,那么 {{{invoke}}} 调用后 {{{print_r($fruits);}}} 你并不会看到改变后的值,但如果你 {{{print_r($args);}}} 时,会发现 $args 的第一个元素仍然被改变了,也就是说,引用参数传递还是成功了。
第三,也是最重要的一点,invoke 的第三个参数,就是觉得是否引用参数传递的关键,如果它是 true,就是引用参数传递,如果它是 false,就是值传递。该参数的默认值是 false。也就是说,对于非引用参数传递的远程过程,你也可以使用 invoke 来进行调用,只是看上去不那么直观。但是当你真正需要通过字符串表示的方法名来调用远程方法时,它就是你最好的选择了。
!!问:
<<<
为何发布服务器时出现
{{{
Warning: Cannot modify header information - headers already sent ...
}}}
这样的错误?
<<<
!!答:
<<<
一般来说,在使用 PHPRPC 发布服务时,{{{include('php/phprpc_server.php');}}} 是放在第一句的,这是因为在 phprpc_server.php 中有初始化 Session 和输出缓存区的语句,把它放在开始,就可以保证服务启动时不会因为其它程序输出而导致出现上面的了```这个错误是在直接用浏览器打开这个服务页时看到的,而不是在客户端调用时,所以要测试服务器是否正常,最好的方式是先用浏览器看一下有没有 {{{functions="...";}}} 这样的输出,其中 ... 表示一些看上去乱七八糟的大小写不一且与数字混杂的字符串,实际上那是 base64 编码过的服务器端方法名称列表,如果你看到这个列表,那么表示你的客户端已经可以调用你发布的服务了。```。但是如果在 {{{include('php/phprpc_server.php');}}} 之前包含的其它 PHP 程序都不会有输出的话,那么也是可以的。
如果你保证它已经放在了第一句或者之前没有输出,仍然出现上面那种错误信息的话,那么请检查 {{{<?php}}} 之前是不是有字符,在 {{{<?php}}} 之前出现的任何字符都会影响服务器的正常发布。有时候这种字符在你的编辑器(比如 Windows 的记事本)中是不可见的,比如 ~UTF-8 的 BOM 信息,这时候你需要用一个可以去掉 BOM 信息的编辑器来重新保存你的程序。
<<<
----
!!问:
>如何使用输出重定向?
!!答:
>服务器端很简单,只要发布的方法中有 each、print、print_r 之类的输出,这些输出就可以重定向到客户端,而客户端通过 getOutput 方法就可以获取到它。它是一个字符串值。如果服务器端没有输出,getOutput 方法将返回一个空字符串。
----
!!问:
>如果与其它语言之间传递自定义类型的对象?
!!答:
<<<
大部分语言是支持名空间的,而 PHP 语言却是不支持名空间的(虽然 PHP 5.3 支持名空间,但是因为目前还是 alpha 版本,所以它对带有名空间的类如何序列化尚未明朗),那么如何让它们当中定义的类能相互识别呢?PHPRPC 在这方面做了一些变通。对于支持名空间的语言,它在序列化带有名空间的类时,会将名空间与类名之间的 {{{.}}} 变成 {{{_}}},例如假设 Java 中有这样一个类 {{{MyNameSpace.MySubNameSpace.MyClass}}},那么在 PHP 中这样定义 {{{MyNameSpace_MySubNameSpace_MyClass}}} 就可以跟 Java 中的类进行数据交换了。
<<<
!!发布函数
在[[快速入门]]一章中,我们已经在 [[HelloWorld]] 的例子中看到过如何发布一个函数了,这里我们主要谈一下哪些函数可以作为 PHPRPC 服务发布。
实际上大部分函数都是可以作为 PHPRPC 服务发布的,甚至包括 PHP 中的内置的函数。但如果参数或结果中包含有资源类型(比如 {{{mysql_connect}}},{{{mysql_query}}} 等),那么这种函数就不能够发布。
你可以同时发布多个函数,不论是你自定义的,还是 PHP 内置的都可以。例如:
<code php:firstline[0]>
<?php
include('php/phprpc_server.php');
function hello($name) {
return 'Hello ' . $name;
}
$server = new PHPRPC_Server();
$server->add(array('hello', 'md5', 'sha1'));
$server->add('trim');
$server->start();
?>
</code>
在上面这个例子中我们发布了 4 个方法,其中 {{{hello}}} 是我们自定义的函数,另外三个则是 PHP 带的函数。我们可以把方法名放到数组中,用 {{{add}}} 一次添加多个,也可以以字符串作为 {{{add}}} 方法的参数一个一个的添加。
!!发布方法
PHPRPC for PHP 也支持发布类的静态方法和对象的实例方法,如下例:
<code php:firstline[0]>
<?php
include('php/phprpc_server.php');
class Example1 {
static function foo() {
return 'foo';
}
function bar() {
return 'bar';
}
}
$server = new PHPRPC_Server();
$server->add('foo', 'Example1');
$server->add('bar', new Example1());
$server->start();
?>
</code>
这里 {{{foo}}} 是一个静态方法,所以添加时第二个参数是类名。而 {{{bar}}} 是一个实例方法,所以添加时是一个 Example1 的实例对象。如果你以发布静态方法的方式发布了一个实例方法的话,并不会报告错误,不过在调用时,你可能会得到警告错误。```当然如果你只关注结果的话,你并看不到警告错误,因为它不是像对话框一样弹出来的,而是以程序可见的方式返回一个错误对象,只有当你去查看这个警告错误对象时,你才会发现它。```
现在你可能会有这样的疑问,如果要同时发布 2 个不同类中的同名方法的话,会不会有冲突呢?如何来避免冲突呢?
!!别名机制
确实会遇到这种情况,就是当发布的方法同名时,后添加的方法会将前面添加到方法给覆盖掉,在调用时,你永远不可能调用到先添加的同名方法。不过 PHPRPC 提供了一种别名机制,可以解决这个问题。要用自然语言来解释这个别名机制的话,不如直接看代码示例更直接一些:
<code php:firstline[0]>
<?php
include('php/phprpc_server.php');
function hello($name) {
return 'Hello ' . $name;
}
class Example1 {
static function foo() {
return 'foo';
}
function bar() {
return 'bar';
}
}
class Example2 {
function foo() {
return 'foo, too';
}
function bar() {
return 'bar, too';
}
}
$server = new PHPRPC_Server();
$server->add('hello', NULL, 'hi');
$server->add('foo', 'Example1', 'ex1_foo');
$server->add('bar', new Example1(), 'ex1_bar');
$server->add(array('foo', 'bar'), new Example2(), array('ex2_foo', 'ex2_bar'));
$server->start();
?>
</code>
从上面这个例子,我们就会发现不论是函数还是方法都可以通过别名来发布,别名就是第三个参数。如果要发布的函数使用别名的话,那么第二个参数设置为 {{{NULL}}} 就可以了。同时添加多个方法(或函数)时,别名也应该是多个,并且个数要跟方法(或函数)名个数相同,且一一对应。
最后要注意的一点是,通过别名发布的方法(或函数)在调用时如果用原方法(或函数)名调用是调用不到的,也就是说只能用别名来调用。
!!使用会话(Session)
在上面的例子中,虽然发布了对象的实例方法,但是在那些方法中我们并没有用到 {{{$this}}} 来访问对象中的属性。这样的实例方法在实际中是很少见到的,它们基本上跟静态方法没什么区别```除了以静态方法来调用它们时会发出警告以外```。但如果你真的在实例方法中访问了实例属性后,你会发现你发布的实例方法可能并没有如你预期的那样工作,例如:
<code php:firstline[0]>
<?php
include('php/phprpc_server.php');
class ExampleCounter {
var $_count = 0;
function inc() {
$this->_count += 1;
}
function count() {
return $this->_count;
}
}
$server = new PHPRPC_Server();
$server->add(array('inc', 'count'), new ExampleCounter());
$server->start();
?>
</code>
这里发布了 {{{ExampleCounter}}} 实例对象的 2 个方法,但是不论你在客户端调用了多少次 {{{inc}}} 后再调用 {{{count}}},返回的结果都是 0。
原因在于 PHP 的执行方式,每次远程调用服务器端时,服务器端的页面都是被重新加载执行的。因此,每次都会重新创建一个 {{{ExampleCounter}}} 对象实例,当一个调用结束后,这个实例对象就连同整个页面内容一同自动销毁了。
如果希望能够像在本地调用中一样使用对象,那么你需要将这个对象保存到会话中,例如:
<code php:firstline[0]>
<?php
include('php/phprpc_server.php');
class ExampleCounter {
var $_count = 0;
function inc() {
$this->_count += 1;
}
function count() {
return $this->_count;
}
}
if (!isset($_SESSION['counter'])) {
$_SESSION['counter'] = new ExampleCounter();
}
$server = new PHPRPC_Server();
$server->add(array('inc', 'count'), $_SESSION['counter']);
$server->start();
?>
</code>
这样,通过客户端调用时,就会得到你想要的结果了。你还可以在对象内通过存取 Session 来做到同样的效果:
<code php:firstline[0]>
<?php
include('php/phprpc_server.php');
class ExampleCounter {
function ExampleCounter() {
if (!isset($_SESSION['count'])) {
$_SESSION['count'] = 0;
}
}
function inc() {
$_SESSION['count'] += 1;
}
function count() {
return $_SESSION['count'];
}
}
$server = new PHPRPC_Server();
$server->add(array('inc', 'count'), new ExampleCounter());
$server->start();
?>
</code>
到这里,你可能已经明白了,这跟用 PHP 做动态网页时使用 Session 是没有什么区别的。因此,如果你想要存取整个应用程序生命周期而不是在会话周期内使用的数据的话,就可以利用数据库或者 Memcache 等来作为辅助工具实现了。但是因为数据库和 Memcache 已经超出了 PHPRPC 的范围,所以,我们就不再继续讨论了。
!!服务发布选项
服务器发布还有几个选项,通常情况下,你不需要对它们进行设置,因为它们的默认值就是最佳配置。下面是一个使用这些选项的例子:
<code php:firstline[0]>
<?php
include('php/phprpc_server.php');
function hello($name) {
return 'Hello ' . $name;
}
$server = new PHPRPC_Server();
$server->add('hello');
$server->setCharset('UTF-8');
$server->setDebugMode(true);
$server->setEnableGZIP(true);
$server->start();
?>
</code>
例子中,setCharset 是用来设置字符集的方法,实际上默认值就是 {{{'UTF-8'}}},所以,你只有要设置为其它字符集时才有必要用这个方法。但 ~UTF-8 字符集是在各种语言之间互通性最好的。
setDebugMode 是用来设置服务器是否工作在调试模式下,调试模式下发布的方法会返回更详细的信息错误信息,比如出错的文件、行号等信息。一般来说,在最终发布给调用者时,这个选项应该是关闭的,因为这样可以让你的服务器更安全。所以默认值是 false。
setEnableGZIP 是用来启动用 PHP 压缩输出的开关。启动压缩输出虽然可以让传输的数据量减少,但是它会占用更多的内存和 CPU,因此它默认是关闭的。实际上很多 Web 服务器(比如 Apache、lighttpd、nginx 等)也提供压缩输出的功能,所以一般情况下,没有必要打开这个开关。
!!PHPRPC for PHP 中的日期类
在 PHP 中虽然提供了不少的日期、时间的函数,但是这些函数都是用来操作字符串或数字类型表示的日期时间的。在 PHP 中一直没有提供一个用来表示日期和时间的类,直到 PHP 5.2 之后才加入了一个用来表示日期对象的 ~DateTime 类,而关于这个 ~DateTime 在官方文档中也仅仅是[[一笔带过|http://www.php.net/manual/en/migration52.datetime.php]],并未做详细介绍。
为了能够与其它语言中的日期类型进行数据交换,在 PHPRPC 中将日期类型统一为 ~PHPRPC_Date 这样一个类。在其它语言中实现序列化和反序列化时,就将日期类都按照类名为 ~PHPRPC_Date 的对象进行处理,这样不同的语言就可以直接交换日期类型的数据了。
!!!建立 ~PHPRPC_Date 对象
要获得一个 ~PHPRPC_Date 对象可以通过以下四种方式:
#~PHPRPC_Date 构造函数
#~PHPRPC_Date 静态方法 now
#~PHPRPC_Date 静态方法 today
#~PHPRPC_Date 静态方法 parse
这四种方式有区别又有联系,在不带参数调用 ~PHPRPC_Date 构造函数时,得到的结果跟静态方法 now 的返回结果相同。today 的返回的结果与 now 结果的日期部分相同,只是时间部分都是 0。~PHPRPC_Date 构造函数还支持整数和字符串参数,parse 方法也支持这两种类型的参数。整数是Unix 时间戳,这一点在构造函数和 parse 方法中是相同的。对于字符串参数他们支持的不同,构造函数支持的字符串格式与 strtotime 函数支持的字符串格式相同。而 parse 支持的格式只有以下三种:
| 短格式 |2008-10-21 |
| 长格式 |2008-10-21 12:13:34 |
| 全格式 |2008-10-21 12:13:34.167 |
而对于最后一种,strtotime 是不支持的。另外,parse 的参数还可以是 ~PHPRPC_Date 对象,它将直接返回该参数本身。
!!!~PHPRPC_Date 的实例属性
|{{{year}}}|年|
|{{{month}}}|月|
|{{{day}}}|日|
|{{{hour}}}|时|
|{{{minute}}}|分|
|{{{second}}}|秒|
|{{{millisecond}}}|毫秒|
!!!~PHPRPC_Date 的实例方法
|{{{addMilliseconds($milliseconds)}}} |将对象增加 {{{$milliseconds}}} 毫秒,{{{$milliseconds}}} 是整数,可以是正的,也可以是负的。|
|{{{addSeconds($seconds)}}} |将对象增加 {{{$seconds}}} 秒,{{{$seconds}}} 是整数,可以是正的,也可以是负的。|
|{{{addMinutes($minutes)}}} |将对象增加 {{{$minutes}}} 分,{{{$minutes}}} 是整数,可以是正的,也可以是负的。|
|{{{addHours($hours)}}} |将对象增加 {{{$hours}}} 小时,{{{$hours}}} 是整数,可以是正的,也可以是负的。|
|{{{addDays($days)}}} |将对象增加 {{{$days}}} 天,{{{$days}}} 是整数,可以是正的,也可以是负的。|
|{{{addMonths($months)}}} |将对象增加 {{{$months}}} 月,{{{$months}}} 是整数,可以是正的,也可以是负的。|
|{{{addYears($years)}}} |将对象增加 {{{$years}}} 月,{{{$years}}} 是整数,可以是正的,也可以是负的。|
|{{{after($when)}}} |如果当前对象表示的时间比 {{{$when}}} 要晚,则返回 {{{true}}},否则返回 {{{false}}}。{{{$when}}} 为 {{{parse}}} 可以解析的值。|
|{{{before($when)}}} |如果当前对象表示的时间比 {{{$when}}} 要早,则返回 {{{true}}},否则返回 {{{false}}}。{{{$when}}} 为 {{{parse}}} 可以解析的值。|
|{{{equals($when)}}} |如果当前对象表示的时间与 {{{$when}}} 相同,则返回 {{{true}}},否则返回 {{{false}}}。{{{$when}}} 为 {{{parse}}} 可以解析的值。|
|{{{set($year, $month, $day[, $hour, $minute, $second[, $millisecond]])}}} |设置当前对象的值。|
|{{{time()}}} |返回当前对象的 Unix 时间戳表示。|
|{{{toString()}}} |返回当前对象的字符串表示,它与 {{{parse}}} 支持的全格式相同。|
|{{{dayOfWeek()}}} |返回当前对象表示的日期是星期几,值为 0 - 6,0 表示星期日,1 表示星期一,…… 6 表示星期六。|
|{{{dayOfYear()}}}|返回当前对象表示的日期是当年内的第几天。|
!!!~PHPRPC_Date 的静态方法
|{{{dayOfWeek($year, $month, $day)}}}|返回指定的日期是星期几,值为 0 - 6,0 表示星期日,1 表示星期一,…… 6 表示星期六。|
|{{{dayOfYear($year, $month, $day)}}}|返回指定的日期是年内的第几天。|
|{{{isLeapYear($year)}}}|返回是否是闰年。|
|{{{daysInMonth($year, $month)}}}|返回指定的月份有多少天。|
|{{{isValidDate($year, $month, $day)}}}|指定日期如果是有效日期就返回 true,否则返回 false。|
|{{{isValidTime($hour, $minute, $second)}}}|指定时间如果是有效时间就返回 true,否则返回 false。|
该版本直接解压后就可以使用,其中
*bigint.php
*compat.php
*phprpc_date.php
*xxtea.php
属于公共文件。不论是客户端还是服务器端都需要这些文件。
*phprpc_client.php
是客户端文件,如果你只需要使用客户端,那么只要有上面那些公共文件和这个文件就可以使用了,使用时,直接在你的程序中包含 phprpc_client.php 就可以,公共文件不需要单独包含。
*dhparams
*dhparams.php
*phprpc_server.php
这三个文件是服务器端需要的文件。
其中 dhparams 目录中包含的是加密传输时用来生成密钥的参数,如果你需要更详细的解释,请参见 [[dhparams 详解]]。
dhparams.php 是用来读取 dhparams 目录中文件的类。
phprpc_server.php 是服务器端,如果你要使用 PHP 来发布 PHPRPC 服务,只需要包含这个文件就可以了。公共文件和 dhparams.php 都不需要单独包含。
!!运行环境
*PHP 4.3+、PHP 5、PHP 6
*客户端要求开启 socket 扩展。
*服务器端需要有 IIS、Apache、lighttpd 等可以运行 PHP 程序的 Web 服务器。
*如果服务器端需要加密传输的能力,必须要保证 session 配置正确。
如果需要加密传输能力,最好开启大整数计算扩展,PHPRPC 支持的大整数计算扩展按照计算速度(由快到慢)排名是 gmp、big_int、bcmath。如果这些扩展你一个都没有开启,则密钥交换将使用 PHP 脚本来模拟大整数运算,这并不影响加密传输的功能,只是速度会慢一些。
如果你需要更快的加密处理能力,除了开启上面所说的大整数计算扩展以外,你还可以下载并安装 [[xxtea 的 PECL 扩展|http://www.phprpc.org/pecl/xxtea-1.0.3.zip]],该扩展采用 C 编写,它能够有效的提高加密速度。
!!PECL 版本的 xxtea 扩展安装方法
安装方法有多种,下面介绍最常用的三种安装方法:
#跟 PHP 一同编译安装
#使用 phpize 工具编译安装
#在 Windows 下使用 Microsoft Visual C(.NET 或 6.0)编译安装
!!!方法一:跟 PHP 一同编译安装
#在 PHP 源码文件夹下创建 ext/xxtea 文件夹,将所有文件复制到新创建的文件夹下。
#运行 {{{./buildconf}}} 重新构建 PHP 的配置脚本。
#带选项编译 PHP:{{{--enable-xxtea}}} 将作为 PHP 内置模块编译,{{{--enable-xxtea=shared}}} 将作为动态载入模块编译。
!!!方法二:使用 phpize 工具编译安装
#解压缩该包内容。
#运行 {{{phpize}}} 脚本,为编译 XXTEA 包准备环境。
#运行 {{{./configure --enable-xxtea=shared}}} 生成 makefile。
#运行 {{{make}}} 编译 XXTEA 扩展库,它将被放置在 ./modules 文件夹下。
#运行 {{{make install}}} 安装 XXTEA 扩展库到 PHP。
!!!方法三:在 Windows 下使用 Microsoft Visual C(.NET 或 6.0)编译安装
#在 PHP 源码文件夹下创建 ext/xxtea 文件夹,将所有文件复制到新创建的文件夹下。
#从你所使用的版本的 PHP 文件夹下复制 php4ts.lib(PHP 4 使用)或 php5ts.lib(PHP 5 使用)静态库到 ext/xxtea 文件夹下。
# 打开 php_xxtea.sln(MS VC.NET 的解决方案文件)或者 php_xxtea.dsw(MS VC 6.0 的工作环境文件)。尝试编译 Release_php4 (PHP 4 使用) 或 Release_php5(PHP 5 使用)配置。
#从 ext/xxtea/Release_php4 或 ext/xxtea/Release_php5 下复制 php_xxtea.dll 到你所使用的 PHP 扩展文件夹下。扩展文件夹的路径在 php.ini 中可以找到。
# 在 php.ini 中添加这一行 {{{extension=php_xxtea.dll}}}
你可以直接从本站的[[下载页|http://www.phprpc.org/download]]中下载到每种语言最新版本的 PHPRPC,也可以通过本站[[主页|http://www.phprpc.org]]上左面的 ''[Download Lastest Release]'' 按钮直接下载所有语言的非测试版本的 PHPRPC。
目前所有版本的 PHPRPC 都是提供源代码的,对于一些脚本语言来说,直接解压缩之后就可以使用了,不需要什么安装步骤。另外一些需要编译的语言,则提供了编译用的 shell 文件(Linux/Unix 下使用)和 bat 文件(Windows 下使用),或者直接提供编译好的二进制库文件。
不过为了让读者能够更清楚如何安装,我们还是对每种语言的安装都做详细的讲解,你可以在<<tag 安装>>列表里找到你感兴趣的语言的安装方法。
!!轻量级
PHPRPC 与 SOAP 不同,虽然 SOAP 号称是简单对象存取协议,但是实际上它一点都不简单,甚至可以说,它的协议内容是一般人所无法理解的```如果您确认您真的能够理解 SOAP 协议的所有内容,那么您一定是这方面的专家教授或者是具有同样能力的人了。```。除了复杂的定义,它还具有复杂且混乱的实现,且不要说在异构系统中部署应用,即使在同一种语言下部署,它也绝对算得上是重量级的```可能你会有不同意见,比如在 .NET 环境下,基于 SOAP 的 Web Service 应用开发部署是相当简单的,我承认微软在这点上做的非常出色,因为最初我也是被它所吸引来的。可是在其它语言中却完全不是这么回事了。```。而 PHPRPC 却是轻量级的,它的协议相当简单,从仅靠我一人之力就可以实现出十几种语言的版本这个事实就可以很容易的看出来。它的数据表示也相当高效,不论在处理还是传输上都比 SOAP 要快得多。它在部署和使用时,更加简单,它的 .NET 版本甚至比微软为 .NET 提供的 ~WebService 都容易使用,你甚至不需要借助 Visual Studio 就可以轻松构建 PHPRPC for .NET 的应用。其它语言版本的部署和使用跟 .NET 版本差不多,甚至更加简单。
!!安全
在安全方面,PHPRPC 并没有像 SOAP 那样专门制定一个 [[WS-Security|http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=wss]] 来解决安全性问题。而是内置了加密传输机制,虽然这个加密机制没有 ~WS-Security 那么复杂```一个安全机制在实现或者应用时如果复杂到实现者或者应用者都无法完全理解的程度,那么它很可能会被非安全的实现或应用!```,但绝对是从众多的安全加密机制中精选出来的。它可以保证你的数据在非加密的 HTTP 网络```这里是指非 HTTPS 的网络```上安全传输。
!!跨网际
PHPRPC 是工作于 HTTP 协议之上的远程调用协议,因此它具有与 SOAP 同样的好处——它可以自由的在 Internet 上被广泛的部署应用。在这个互联网应用占据主导地位的年代,它比那些基于私有通讯协议的远程调用在应用上更具优势。
!!跨语言、跨平台、跨环境
PHPRPC 目前支持十几种编程语言,而且这些支持的编程语言都是目前被广泛使用的主流编程语言。这些语言支持不同的平台与环境,因此在跨语言的同时,也实现了跨平台,跨环境。而且随着 PHPRPC 的发展,你将发现 PHPRPC 会支持更多的语言,包括你所见过、使用过,以及从来没有听说过的语言。
!!跨域
PHPRPC 最大的一个亮点就是它支持在浏览器环境中通过 ~JavaScript 来调用服务器端的函数与方法,它将你以前用传统的 ajax 方式做不到或者很难做到的事情变得轻而易举!而在这个亮点中更大的亮点是,它还支持在浏览器环境中直接跨域调用。而且在跨域方面,它还针对不同的应用作了不同的实现。这是 SOAP 所做不到的```至少目前它没有做到```。
!!复杂对象传输
你可以在各种不同的语言之间通过 PHPRPC 自由的交换数据,不论是基本数据类型,还是复杂对象,它都可以帮你轻松传输。虽然 SOAP 也声称可以在各种语言之间交换复杂数据,可是实际应用却完全不是那么回事,在许多语言中你甚至不得不手工去构造那些晦涩难懂的 XML,来传输所谓的自定义类型,这种意义上的自定义类型传输甚至还不如字符串传输更容易被理解。而在 PHPRPC 中你却完全不需要做这种无意义且损伤脑细胞的事情,你会发现你将可以用前所未有的简单方式在不同的语言之间传输复杂对象,简单到就像在同一个程序中传输它们一样。
!!引用参数传递
PHPRPC 还支持引用参数传递,你除了可以通过远程函数或方法的返回值得到结果外,你还可以通过参数来返回数据,尽管大多数情况下你用不到这个特性,不过一旦需要,你会发现非常有用。
!!内容输出重定向
除了通过返回值和参数来得到返回数据以外,你还可以通过输出的方式来返回数据,而且通过该方式返回较大量的数据时,比通过返回值来返回数据还要高效。
!!分级错误处理
在远程调用时,对于某些语言来说,并不是所有的错误都会影响结果,因为这些错误可能只是警告类信息,在这种情况下,PHPRPC 在返回结果的同时,也会返回警告信息。只有当发生致命错误时,才会只返回错误信息。这种处理方式可以让调用者得到更多想要的信息。
!!会话支持
对于支持会话的服务器,你可以在你的远程函数或方法中使用会话,这将十分有效的帮助你对状态进行管理。
到这里,我们已经对 PHPRPC 的特点做了一个大体的介绍,现在你可能非常想要了解 PHPRPC 到底该怎么用了。下面我们就进入[[快速入门]],来对它进行一个快速且实质性的了解吧。
如果不是因为头脑发热心血来潮,那么一定是为了解决某些问题才有了 PHPRPC。好吧,我承认是因为我受够了那些大企业所鼓吹的强大无比的 SOAP(~WebService)之后才开始考虑写 PHPRPC 的。如果你也是一个需要类似于 SOAP 所鼓吹的能力,而实际上又被 SOAP 折磨的痛苦不堪却又无所适从的人的话,或许 PHPRPC 就是你的最佳选择。
<script>
var here=story.findContainingTiddler(place); if (!here) return;
if (here.ondblclick) {
here.setAttribute("editKey","none");
if ("$1"=="shift" || "$1"=="ctrl" || "$1"=="alt")
here.setAttribute("editKey","$1"+"Key");
var trigger=("$2"=="click")?"onclick":"ondblclick";
here.save_dblclick=here.ondblclick;
here.ondblclick=null;
if (here.getAttribute("editKey")!="none")
here[trigger]=function(e) {
var ev=e?e:window.event;
if (ev[this.getAttribute("editKey")])
this.save_dblclick.apply(this,arguments);
}
}
</script>
//{{{
var old_lewcid_splash_restart=restart;
restart = function()
{ if (document.getElementById("SplashScreen"))
document.getElementById("SplashScreen").style.display = "none";
if (document.getElementById("contentWrapper"))
document.getElementById("contentWrapper").style.display = "block";
old_lewcid_splash_restart();
}
//}}}
//{{{
config.footnotesPlugin = {
backLabel: "back",
prompt:"show footnote"
};
config.formatters.unshift( {
name: "footnotes",
match: "```",
lookaheadRegExp: /```((?:.|\n)*?)```/g,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
var tiddler = story.findContainingTiddler(w.output);
if (tiddler) {
if (!tiddler.notes)
tiddler.notes = [];
var title = tiddler.getAttribute("tiddler");
tiddler.notes.pushUnique(lookaheadMatch[1]);
var pos = tiddler.notes.indexOf(lookaheadMatch[1]) + 1;
createTiddlyButton(w.output,pos,config.footnotesPlugin.prompt,function(){var x = document.getElementById(title+"ftn"+pos);window.scrollTo(0,ensureVisible(x)+(ensureVisible(x)<findScrollY()?(findWindowHeight()-x.offsetHeight):0));return false;},"ftnlink",title+"ftnlink"+pos);
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
}
}
});
old_footnotes_refreshTiddler = Story.prototype.refreshTiddler;
Story.prototype.refreshTiddler = function(title,template,force)
{
var tiddler = old_footnotes_refreshTiddler.apply(this,arguments);
if (tiddler && tiddler.notes && tiddler.notes.length)
{
var holder = createTiddlyElement(null,"div",null,"footnoteholder");
var list = createTiddlyElement(holder,"ol",title+"footnoteholder");
for (var i=0; i<tiddler.notes.length; i++)
{
var ftn = createTiddlyElement(list,"li",title+"ftn"+(i+1),"footnote");
wikify(tiddler.notes[i]+" ",ftn);
createTiddlyButton(ftn,"["+config.footnotesPlugin.backLabel+"]",config.footnotesPlugin.backLabel,function(){window.scrollTo(0,ensureVisible(document.getElementById(this.parentNode.id.replace("ftn","ftnlink"))));return false;},"ftnbklink");
}
var count = tiddler.childNodes.length;
for (var j=0; j<count; j++){
if(hasClass(tiddler.childNodes[j],"viewer")){
var viewer = tiddler.childNodes[j];
}
}
viewer.appendChild(holder);
tiddler.notes = [];
}
return tiddler;
};
setStylesheet(
".tiddler a.ftnlink {vertical-align: super; font-size: 0.8em; color:red;}\n"+
".tiddler a.ftnlink:hover, .tiddler .footnoteholder a.ftnbklink:hover{color:#fff;background:red;}\n"+
".tiddler div.footnoteholder{margin:1.8em 1.0em; padding:0.1em 1.0em 0.1em 1.0em ;border-left: 1px solid #ccc;}"+
".tiddler footnoteholder ol {font-size: 0.9em; line-height: 1.2em;}\n"+
".tiddler .footnoteholder li.footnote {margin: 0 0 5px 0;}\n"+
".tiddler .footnoteholder a.ftnbklink{color:red;}\n","FootNotesStyles");
//}}}
@@color(#ff6600):本指南使用 [[TiddlyWiki|http://www.tiddlywiki.com/]] 制作,在 [[Firefox|http://www.mozilla.com/firefox/]] 下可以得到最好的浏览效果。建议使用 [[Firefox|http://www.mozilla.com/firefox/]] 浏览。@@
你可以免费在线阅读或者下载阅读本指南。
未经作者明确授权,禁止发行本指南及其修改版本。
未经作者事先授权,禁止将本作品及其衍生物以标准(纸质)书籍形式发行。
未经作者允许,禁止在学术论文、教材或专著中引用。
如果有兴趣再发行或再版本指南的全部或部分内容,不论修改与否,都请事先联系本指南作者(同时也是版权所有者) andotcn@msn.com。
对 PHPRPC 有任何问题或建议,请进入[[官方论坛|http://www.phprpc.org/forum]]发布相关讨论。
感谢所有对 PHPRPC 关注和支持的朋友!