PHPRPC
perfect high performance remote procedure call
PHPRPC
perfect high performance remote procedure call






PHPRPC - perfect high performance remote procedure call
<!--{{{-->
<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>
<!--}}}-->
<<importTiddlers>>
[[欢迎来到 PHPRPC 世界]]
下面是我们对 [[Diffie-Hellman 密钥交换算法|http://www.rsa.com/rsalabs/node.asp?id=2248]] 一个简要描述:

1、服务器端保存有用于密钥交换的大素数 p 和它的本原根 g。其中 p 的长度决定了生成密钥的长度。
2、客户端请求加密时,服务器端生成一个随机数 Xa,然后计算出 Ya = g^^Xa^^ mod p,将 p、g、Ya 连同登录页面一起发送给客户端。
3、客户端也生成一个随机数 Xb,计算 Yb = g^^Xb^^ mod p,k = Ya^^Xb^^ mod p,然后将 Yb 发送给服务器端。
4、服务器端计算 k’ = Yb^^Xa^^ mod p,密钥交换完成。

其中 k = k‘ = g^^~XaXb^^ mod p。因此 k 和 k' 就是交换完成的密钥。

服务器端生成的随机数 Xa 和客户端生成的随机数 Xb 都不传递给对方。传递的数据只有 p、g、Ya、Yb。
如果你已经把 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 || !readOnly) return;
	var nodes=t.getElementsByTagName("*");
	for (var i=0; i<nodes.length; i++)
		if (hasClass(nodes[i],"toolbar"))
			nodes[i].style.display="none";
</script>
ISerializable 定义如下:
<code delphi>
  {$M+}
  ISerializable = interface
    ['{2523DF0D-A532-4CF9-AA96-C60DA18E21A4}']
    function Serialize: RawByteString;
    procedure UnSerialize(ss: RawByteString);
  end;
  {$M-}
</code>
*@@color(red):''PHPRPC 3.0 用户指南''@@
##[[欢迎来到 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 Groovy]]
*@@color(red):''PHPRPC 3.0 开发指南''@@
##[[PHPRPC 协议概述]]
##[[PHPRPC 数据表示]]
##[[PHPRPC 加密传输]]
##[[PHPRPC 会话管理]]
##[[PHPRPC 其他话题]]
NULL 序列化为:
{{{
N;
}}}
它与各语言之间的对应表如下:

| 语言 | 序列化 | 反序列化 |
| PHP | NULL | NULL |
| Java | null | null |
| C# | null | null |
| VB(.NET) | Nothing | Nothing |
| ~JavaScript | null 或 undefined | null |
| ~ActionScript 2 | null 或 undefined | null |
| ~ActionScript 3 | null 或 undefined | null |
| Delphi | nil | nil |
| Ruby | nil | nil |
| Python | None | None |
* [[PHP 序列化概述]]
* [[NULL 序列化]]
* [[数值序列化]]
* [[字符串序列化]]
* [[容器序列化]]
* [[对象序列化]]
* [[对象自定义序列化]]
* [[引用序列化]]
PHP 序列化格式是一种超轻量级的数据交换格式,易于机器解析和生成。同时也易于人阅读。它最初在 PHP 3.05 中被引入,之后在 PHP 4、PHP 5、PHP 6 中被继承并发展。其中 PHP 3 序列化格式已经过时。PHP 4 序列化格式是 PHP 5 序列化格式的子集,PHP 5 序列化格式是 PHP 6 序列化格式的一个子集,本文所述的 PHP 序列化格式是指 PHP 6 序列化格式,它包含了 PHP 4 和 PHP 5 序列化格式的所有内容。PHP 序列化格式实际上是完全独立于语言的半文本格式,它不但支持 [[JSON|http://www.json.org]] 所支持的所有数据类型和结构,而且支持对象和引用结构。这些特性使 PHP 序列化格式成为更为理想的数据交换格式。

PHP 序列化格式是半文本格式,对于数字,它是采用十进制数字字符串来表示的```因为二进制格式中,数字是按照机器存储格式保存的,存在字节序问题```,因此不存在字节序问题,非常有利于跨平台跨语言实现。但是对字母大小写和空白(空格、回车、换行等)敏感,因此,又非常利于机器解析。

PHP 序列化格式用[[扩充巴科斯-瑙尔范式(ABNF)|http://tools.ietf.org/html/rfc4234]]描述如下:
<code ABNF>
value = null / boolean / double / integer /
        binary-string / escaped-binary-string / unicode-string /
        array / object / custom-object / reference / pointer-reference

null = %x4E ";"

boolean = %x62 ":" BIT ";"

integer = %x69 ":" SINT ";"

double = %x64 ":" (NAN | INF | NINF | SINT ["." 1*DIGIT ["E" SINT]]) ";"

binary-string = %x73 ":" UINT ":" DQUOTE *OCTET DQUOTE ";"

escaped-binary-string = %x53 ":" UINT ":" DQUOTE *(VCHAR / %x5C 2HEXDIG) DQUOTE ";"

unicode-string = %x55 ":" UINT ":" DQUOTE *(VCHAR / %x5C 4HEXDIG) DQUOTE ";"

array = %x61 ":" UINT ":" "{" *(key value) "}"

object = %x4F ":" UINT ":" DQUOTE *OCTET DQUOTE ":" UINT ":" "{" *(name value) "}"

custom-object = %x43 ":" UINT ":" "{" *OCTET "}"

reference = %x72 ":" UINT ";"

pointer-reference = %x52 ":" UINT ";"

UINT = "0" / %x31-39 *DIGIT

SINT = *("+" / "-") UINT

NAN = %x4E.41.4E ; "NAN"

INF = %x49.4E.46 ; "INF"

NINF = "-" INF  ; "-INF"

key = integer / name

name = binary-string / escaped-binary-string / unicode-string
</code>
.NET 是一个功能强大的平台,在其早期的版本中就已经内置了 Web Service、.NET Remoting 等远程调用技术,在其最新的 .NET 3.5 中还引入了 WCF 这个统一的通讯平台。如果你只是在用 .NET 做程序开发的话,这些技术应该已经足够满足你的需求啦!

但是,有时候你不得不采用 .NET 和其它语言结合来做系统,上述的技术就不再那么可爱了,因为你要面对互操作性的很多麻烦。而 PHPRPC for .NET 就是破除这些麻烦的利器!

PHPRPC for .NET 不仅仅为互操作性而生,就单纯的 .NET 平台应用而言,它也是非常优秀的!它可以让你用比 Web Service、.NET Remoting 甚至 WCF 更容易的方式做分布式编程。其执行效率也相当的高,远超 Web Service,接近 .NET Remoting。而统一平台的能力则犹为突出,连 WCF 都无法比拟。它不但可以在所有版本的 .NET Framework 之间互通,还支持所有版本的 .NET Compact Framework 和最新的 ~SliverLight 2.0,对 Mono 平台也是完整支持!

我想现在你一定迫不及待地想要了解 PHPRPC for .NET 的细节了吧,那下面就让我们进入主题吧!

*[[PHPRPC for .NET 的安装]]
*[[PHPRPC for .NET 服务器]]
*[[PHPRPC for .NET 客户端]]
*[[PHPRPC for .NET 工具类]]
*[[PHPRPC for .NET 常见问题解答]]
!!同步调用和异步调用
PHPRPC for .NET 客户端与其他语言不同,PHPRPC for .NET 客户端同时提供了同步调用和异步调用两种方式。不过针对不同版本的 .NET Framework,对同步调用和异步调用的支持也有一些区别,~SilverLight 2.0 不支持同步调用,以下版本不支持异步调用:
* .NET Framework 1.0、1.1
* 所有版本的 .NET Compact Framework
* Mono 1.0(也就是用 mcs 编译的程序)
另外,异步调用仅能用于 ~WinForm 和 ~WebForm 程序中(一般只用于 ~WinForm 程序),不支持在控制台程序中使用异步调用,另外,目前的 GTK# 程序也不支持异步调用。

!!直接调用和代理调用
PHPRPC for .NET 同 PHPRPC for Java 一样,也提供了直接调用和代理调用两种方式,不过 .NET Compact Framework 版本只支持直接调用方式,不支持代理调用方式。

下面我们以控制台程序为例,先来看一下直接方式的同步调用:
<code c#>
using System;
using org.phprpc;
using org.phprpc.util;

namespace console_test
{
    class Program
    {
        static void Main(string[] args)
        {
            PHPRPC_Client client = new PHPRPC_Client("http://127.0.0.1:8080/server.aspx");
            Console.WriteLine(PHPConvert.ToString(client.Invoke("Hi", new Object[] {"Ma Bingyao"})));
            int[] a = new int[10];
            Random r = new Random();
            for (int i = 0; i < 10; i++)
            {
                a[i] = r.Next();
            }
            a = (int[])PHPConvert.ToArray(client.Invoke("Sort", new Object[] {a}), typeof(int[]));
            for (int i = 0; i < 10; i++)
            {
                Console.Write(a[i] + "\r\n");
            }
            Console.ReadLine();
        }
    }
}
</code>
通过这段程序,你会发现直接调用并不难,直接用 {{{PHPRPC_Client}}} 的 {{{Invoke}}} 方法就可以了,第一个参数是远程方法名(大小写无关的),第二个参数就是远程方法的参数列表,因此即使只有一个参数,也要用数组传递。不过处理返回值,需要用 {{{PHPConvert}}} 类中的相应转换方法来进行转型操作,才能够得到想要的结果。总体来说,有两个缺点,第一是不够直观,第二是稍微有些麻烦。因此,就有了下面这种代理调用方式。

下面我们继续以控制台程序为例,来看一下代理方式的同步调用:
<code c#>
using System;
using org.phprpc;

namespace console_test
{
    public interface ITest
    {
        string Hi(string name);
        int[] sort(int[] a);
    }

    class Program
    {
        static void Main(string[] args)
        {
            PHPRPC_Client client = new PHPRPC_Client("http://127.0.0.1:8080/server.aspx");
            ITest test = (ITest)client.UseService(typeof(ITest));
            Console.WriteLine(test.Hi("Ma Bingyao"));
            int[] a = new int[10];
            Random r = new Random();
            for (int i = 0; i < 10; i++)
            {
                a[i] = r.Next();
            }
            a = test.sort(a);
            for (int i = 0; i < 10; i++)
            {
                Console.Write(a[i] + "\r\n");
            }
            Console.ReadLine();
        }
    }
}
</code>

上面程序中,我们首先定义了一个 {{{ITest}}} 接口,这个接口是用来描述远程方法的,但是服务器无需去实现该接口,也无需在服务器端定义该接口。客户端接口中的方法也无需跟服务器端的发布的方法完全一致,因为方法名是不区分大小写的,参数类型只要能够保证相互可以转换就可以。例如上面程序中的 {{{sort}}} 方法,在服务器发布时,是首字母大写的,而这里是首字母小写;服务器端定义的参数和返回值类型都是 {{{List<int>}}} 类型,而这里都是 {{{int[]}}} 类型。因为有了这些特点,跟其它的动态类型语言通讯时就会更加灵活了,也为不同版本的 .NET 框架之间互通提供了保证。

后面 {{{UseService}}} 方法根据参数指定的接口,返回一个实现了该接口的客户端代理对象,再之后,我们就可以通过这个代理对象来进行远程调用了。后面在调用时,我们会发现跟本地调用看上去没什么区别。

当然,这种没区别只是从程序形式上看上去没区别,而且因为是控制台程序,同步调用的结果看上去也很合理。但如果你是在做 ~WinForm 这样的窗体程序,那么同步调用就显得不那么可爱了。因为它会阻塞当前线程,当你按下某个执行同步调用的按钮时,如果网速不是很快,你就会发现窗口假死了,它不再有任何响应,直到远程调用执行完毕。

要解决这个问题,其实也不难,只要单独开一个线程,让同步调用运行在区别于窗体操作线程之外的另一独立线程中,就可以避免这种窗体假死现象了。不过,多线程程序设计并不是一件很容易的事情,毕竟线程之间的数据交换本来就很麻烦,尤其是当要处理的业务非常复杂时,你很可能会被多线程给搞晕。

所以,PHPRPC for .NET 提供了异步调用,有了它之后,你做窗体程序就会变得非常容易了。另外,在 ~SilverLight 2.0 中,PHPRPC 限制了只能使用异步调用,这样你就再也不用担心你的 ~SilverLight 程序会搞“死”用户的浏览器了。

下面我们用一个 WPF 程序来举例说明如何使用代理方式来进行异步调用:

''Window1.xaml''
<code xml>
<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <Button HorizontalAlignment="Right" Name="button1" Width="75" Height="23" VerticalAlignment="Top"
            Click="button1_Click">Button</Button>
        <TextBlock Margin="0,2,81,0" Name="textBlock1" />
    </Grid>
</Window>
</code>

''Window1.xaml.cs''
<code c#>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using org.phprpc;
using org.phprpc.util;

namespace WpfApplication1
{
    public interface ITest {
        void Hi(string name, PHPRPC_Callback callback);
        void Hi(string name, PHPRPC_Callback<String> callback);
        void Sort(List<int> list, PHPRPC_Callback<int[]> callback);
    }

    public partial class Window1 : Window {
        PHPRPC_Client client;
        ITest it;
        public Window1() {
            InitializeComponent();
            client = new PHPRPC_Client();
            it = (ITest)client.UseService("http://127.0.0.1:8080/server.aspx", typeof(ITest));
        }
        private void callback1(Object result, Object[] args, String output, PHPRPC_Error warning) {
            textBlock1.Text += "\r\n" + PHPConvert.ToString(result);
        }
        private void callback2(String result, Object[] args, String output, PHPRPC_Error error, Boolean failure) {
            if (failure) {
                textBlock1.Text += error.ToString();
            }
            else {
                textBlock1.Text += "\r\n" + result + " Generic";
            }
        }
        private void button1_Click(object sender, RoutedEventArgs e) {
            client.KeyLength = 256;
            client.EncryptMode = 1;
            it.Hi("Ma Bingyao", new PHPRPC_Callback(callback1));
            it.Hi("PHPRPC", callback2);
            List<int> list = new List<int>(10);
            Random r = new Random();
            for (int i = 0; i < 10; i++) {
                list.Add(r.Next());
            }
            it.Sort(list, delegate(int[] result, Object[] args, String output, PHPRPC_Error error, Boolean failure) {
                foreach (int i in result) {
                    textBlock1.Text += "\r\n" + i.ToString();
                }
            });
        }
    }
}
</code>

上面这段程序的主代码你几乎可以不加修改的移植到 WPF 浏览器应用程序或者 ~SilverLight 2.0 程序中去。当然在 ~WinForm 程序中用法也差不多,所以就不再单独举 ~WinForm 的例子了。下面来对它做一个简单的说明。

这段程序中也定义了一个 {{{ITest}}} 接口,这个接口跟前面控制台程序中的接口差别很大,每个方法的返回值都变成了 {{{void}}},而在每个方法的最后都定义了一个 {{{PHPRPC_Callback}}} 类型的参数,而且可以是普通的 {{{PHPRPC_Callback}}} 类型,也可以是 {{{PHPRPC_Callback}}} 的泛型。

凡是定义了带有这个 {{{PHPRPC_Callback}}} 类型参数的方法(注意:一定是最后一个参数),都是异步调用的方法。我们再往下看会发现,{{{PHPRPC_Callback}}} 类型其实是一个委托类型,这个委托有四个参数(泛型版本有五个参数):

第一个参数 {{{result}}} 对于普通的 {{{PHPRPC_Callback}}} 类型来说,是 {{{Object}}} 型的,对于 {{{PHPRPC_Callback}}} 泛型来说,它就是泛型指定的类型。这个参数就是服务器端发布的方法的返回值,对于普通的 {{{PHPRPC_Callback}}} 来说,因为这个参数是 {{{Object}}} 类型,所以使用这个参数时,需要自己用 {{{PHPConvert}}} 类中的相应方法进行转型操作,而泛型版本则无需自己转换了,因此为了方便,推荐使用泛型版本。

第二个参数 {{{args}}} 是客户端传到服务器的参数,它以 {{{Object[]}}} 的形式返回,它其中的值有可能与客户端传递时不同,因为在服务器端可能被修改(当调用是引用参数传递时),如果需要取出每一个参数值,你也需要用 {{{PHPConvert}}} 类中的相应方法进行转型操作,而不能直接强制类型转换,否则可能会因为类型不匹配而出错。

第三个参数 {{{output}}} 就是服务器端输出的内容,如果服务器端没有输出,该参数值就为空字符串(不是 {{{null}}})。

第四个参数 {{{warning}}} 是服务器端发生的警告错误。泛型版本该参数({{{error}}})表示调用过程中发生的错误(包括警告错误)。

第五个参数 {{{failure}}} 表示是否是失败性错误,如果是 {{{false}}},则表示警告错误。

{{{PHPRPC_Callback}}} 的普通版本和泛型版本还有一个区别,就是如果远程过程调用中发生错误,普通版本以第一个参数 {{{result}}} 返回,这时 {{{result}}} 是一个 {{{PHPRPC_Error}}} 类型的对象。泛型版本则以第四个参数 {{{error}}} 返回 {{{PHPRPC_Error}}} 类型的对象,并且将第五个参数值设置为 {{{true}}}。

对于这个委托参数,可以传递方法名,也可以使用匿名委托,因为匿名委托支持闭包特性,这会使程序设计更加方便。

异步调用虽然也支持直接调用的方式,但是我们完全没有必要使用它,因为支持异步调用的客户端都完全支持代理调用方式,而代理方式可以实现直接调用方式的所有功能,并且更加简单有效。

!!引用参数传递
对于 .NET 来说,引用参数传递非常简单,只要在代理接口方法中,将引用参数声明为 {{{ref}}} 或 {{{out}}} 参数,就可以实现跟本地调用一样的引用参数传递效果。对于直接方式调用,则需要设置 {{{Invoke}}} 方法的 {{{byRef}}} 参数为 {{{true}}},但通常我们没有必要用这种方式,除非是在使用 .NET Compact Framework 客户端。

!!获取服务器端输出
对于同步调用来说,通过 {{{PHPRPC_Client}}} 的 {{{Outout}}} 属性就可以获得服务器端输出。

!!加密传输
{{{KeyLength}}} 属性用于设置加密传输前[[密钥长度]],默认长度是 128。
{{{EncryptMode}}} 属性用于设置[[加密模式]],默认值为 0。

!! Web 代理服务器
{{{Proxy}}} 属性用于设置 Web 代理服务器。默认为系统默认的代理或者无代理设置(由具体运行环境决定),对于 ~SilverLight 2.0 客户端来说,没有这个属性。
PHPRPC for .NET 中除了服务器和客户端实现之外,还有一些工具类,这些工具类除了在服务器和客户端的实现中被调用之外,你也可以单独使用它们。下面是关于这些工具类的常见问题解答。

!! ~AssocArray 类如何使用?
3.0.1 版中新增加了一个 {{{AssocArray}}} 类,该类主要用于优化反序列化联合数组类型时的效率。3.0.1 版本之前反序列化联合数组时,是根据下标类型来自动判断是 {{{ArrayList}}} 类型还是 {{{Hashtable}}} 类型的,但这样做的缺点是,如果联合数组中包含对其自身的引用,则反序列化将会重复多次递归操作来完成,效率较低。而且其自动判断的类型也不一定准确。3.0.1 版本中的 {{{AssocArray}}} 类就是为解决这个问题而加入的。当反序列化联合数组时,直接反序列化为 {{{AssocArray}}} 类型的对象,而不再进行类型判断和递归,大大提高了反序列化的效率。并且,{{{AssocArray}}} 对象提供了 {{{ToArrayList}}} 和 {{{ToHashtable}}} 两个方法,可以在使用时转换为你需要的对象类型。引入 {{{AssocArray}}} 对象的另一个好处是在某些语言中对于数字下标的数组序列化时并不一定是按照数字顺序排序的,这种情况下,原来的实现将会反序列化为 {{{Hashtable}}} 类型,而原来的 {{{PHPConvert}}} 类中的 {{{ChangeType}}} 方法不能将 {{{Hashtable}}} 转换为 {{{ArrayList}}} 或数组对象,而 {{{AssocArray}}} 对象则可以接受不按数字顺序序列化的数组类型,并且当你将它转换为 {{{ArrayList}}} 时,将自动返回排好序的 {{{ArrayList}}}。

!!~PHPConvert 类如何使用?
PHPRPC for .NET 包中的 {{{org.phprpc.util.PHPConvert}}} 静态类提供了 PHPRPC 中相容类型(关于相容类型的解释请参见[[PHPRPC for .NET 常见问题解答]]部分)的转化功能。其中最重要的方法是 {{{ChangeType}}} 方法,它有几个重载,你可以根据需要来使用它进行类型转换,例如将字节数组转换为字符串,或者将字符串转换字节数组。你也可以通过该方法将 {{{AssocArray}}} 转换为 {{{ArrayList}}}、{{{Hashtable}}}、泛型容器({{{List}}} 或 {{{Dictionary}}})或者数组。

该类是在 PHPRPC 进行远程调用时,进行自动类型转换用的,在使用代理方式调用时,一般不需要使用。但对于直接方式调用或者返回结果是容器类型的情况,则需要用该类转换结果类型或容器元素的类型。

!!~PHPFormatter 类如何使用?
{{{org.phprpc.util.PHPFormatter}}} 是用来序列化和反序列化数据的类,它最主要的两个方法是 {{{Serialize}}} 和 {{{Deserialize}}}。{{{Serialize}}} 方法用于序列化数据,它会把序列化之后的数据写入流中,{{{Deserialize}}} 方法用于反序列化数据,它会从流中读取数据并反序列化为对象。注意操作前后要设置好流的位置({{{Position}}} 属性),这样就可以避免发生无法读取流而产生的错误了。

另外,它还有一个 {{{Encoding}}} 属性,用于设置序列化和反序列化时应用的字符集。
!!哪些类型是相容类型?
PHPRPC 是支持弱类型(不是无类型,这一点一定要区分开)参数传递的,这是它跟 Web Service、.NET Remoting 等远程过程调用机制的一个重要区别。弱类型参数传递的支持对于目前动态脚本语言来说是一个很大的特征,这个特征会让你的程序设计变得更加灵活。

对于弱类型语言来说,相容类型之间是可以根据实际调用情况来自动转换的,而且这个转换是双向的而不是单向的。例如,数字可以转换为字符串,字符串也可以转换为数字。
PHPRPC 中支持的所有简单类型都是相容类型,它们包括整数、实数、布尔类型和字符串。在 PHPRPC for .NET 中,{{{Boolean}}}、{{{Byte}}}、{{{Char}}}、{{{Decimal}}}、{{{Double}}}、{{{Int16}}}、{{{Int32}}}、{{{Int64}}}、{{{SByte}}}、{{{Single}}}、{{{String}}}、{{{UInt16}}}、{{{UInt32}}}、{{{UInt64}}}都是相容类型。

另外,{{{String}}}、{{{StringBuilder}}},{{{Char[]}}} 和 {{{Byte[]}}} 之间是相容类型。{{{Byte[]}}} 类型更适合传输 binary 类型的数据,例如文件、图片等。

数组类型、{{{ArrayList}}}、{{{Hashtable}}}、泛型容器({{{List}}} 和 {{{Dictionary}}}) 和 {{{AssocArray}}} 是相容类型。如果 {{{ArrayList}}} 中存放的类型是单一的数据类型,那么它跟该数据类型的数组是相容的。如果 {{{Hashtable}}} 中的索引键都是数组,并且是从零开始递增的,那么它与 {{{ArrayList}}} 是相容的。{{{ArrayList}}} 和 {{{Hashtable}}} 类型都可以通过 {{{AssocArray}}} 的构造方法转换为 {{{AssocArray}}} 类型,另外,其它的实现了 {{{ICollection}}}、{{{IList}}} 或 {{{IDictionary}}} 接口的类型也都可以通过 {{{AssocArray}}} 的构造方法转换为 {{{AssocArray}}} 类型。

上面所说的这些类型都是可以通过 PHPRPC 直接跟其它语言交互的。

另外,{{{DateTime}}} 类型比较特殊,PHPRPC 会作为一个特殊的对象对它序列化,序列化后的类名为 {{{PHPRPC_Date}}},该类与其他语言中相应的日期类型相容。

!!PHPRPC for .NET 是否支持自定义类型?
PHPRPC for .NET 当然也支持自定义类型,并且自定义类型也可以跟其它语言交互。关于自定义类型需要注意以下几个问题。

自定义类型必须是可序列化的类型。也就是标记了{{{SerializableAttribute}}} 属性的类,并且该类型当中所有字段都是可序列化的类型,例如:
<code c#>
[Serializable]
public class Book {
    public string title;
    public string[] authors;
    public DateTime publishDate;
    public Decimal price;
}
</code>

可序列化的类型可以包含方法,但如果要包含构造方法的话,必须要有一个无参构造方法,否则不能被反序列化。

字段不一定必须是 {{{public}}} 的,也可以是 {{{prvate}}}、{{{protected}}} 的,但如果要跟其它语言交互,必须要保证字段名相同(区分大小写),而字段类型不必一致,只要是相容类型即可。

PHPRPC 不支持 .NET 中实现了 {{{System.Runtime.Serialization.ISerializable}}} 接口的类型。

在 .NET Compact Framework 中,自定义序列化的类型的定义方式相同,虽然 .NET Compact Framework 本身没有 {{{SerializableAttribute}}} 这个类(.NET Compact Framework 3.5 中已经增加了),但是 PHPRPC 实现中提供了这个类的实现,所以可以实现无差别编程。

判断是否是同一个类的对象,PHPRPC 是判断类的全名,即包含了名空间的类名。但是要注意一点,如果将名称空间与类名之间的分隔符(例如 {{{.}}},{{{::}}} 等)全部替换为 {{{_}}} 之后,可以得到相同的名称,则这两个类型在 PHPRPC 中也被认为是可以跨语言传输的类型。例如:.NET 中如果定义了一个 {{{demo.data.MyData}}} 类,那么在 PHP 中应该定义名为 {{{demo_data_MyData}}} 的类来跟它进行交互。并且,应该保证 {{{demo_data_MyData}}} 中的所有字段和 .NET 中的 {{{demo.data.MyData}}} 中的所有字段名称一致。

所以在 .NET 中定义类时要避免通过该规则变换之后出现重名的情况。

!!PHPRPC for .NET 是否支持 .NET 中的其它内建类型?
只要是符合以上规则的 .NET 类,都可以通过 PHPRPC 传递,不管是内建类型还是自定义类型。不过如果要跟其它语言进行交互。你需要在其它语言中实现跟 .NET 中的内建类型定义相同的类,包括内部的各个字段名称和类型都要一致(或相容)才可以。要实现这一点,对用户来说比较困难。因此不推荐使用其它的 .NET 内建类型作为参数或结果类型传递,除非你只打算把这个服务提供给 .NET 客户端来调用。

!!org.phprpc.util.Serializable 这个接口是做什么用的?
PHPRPC 除了支持以上规则定义的序列化类型以外,还支持用户自定义序列化方式的类型。而且用户自定义序列化方式也分两种。这两种都跟 PHP 语言所提供的用户自定义序列化方式类似。{{{org.phprpc.util.Serializable}}} 接口就是其中的一种方式,该方式跟 ~PHP5 中所提供的用户自定义序列化方式兼容。

{{{org.phprpc.util.Serializable}}} 中定义了两个方法,如果一个类实现了标记了 {{{SerializableAttribute}}} 属性(这是一个很关键的前提)并且同时实现了 {{{org.phprpc.util.Serializable}}} 接口中定义的两个方法,那么它就具有了自定义序列化方式的能力。

{{{org.phprpc.util.Serializable}}} 中的 {{{Serialize}}} 方法是在对象被序列化时自动触发的,你可以在 Serialize 方法中对该对象做任何方式的序列化,例如 SOAP、JSON 或者 WDDX 等,只要返回的结果是字节数组就可以了。

{{{org.phprpc.util.Serializable}}} 中的 {{{Deserialize}}} 方法是在对象被反序列化时自动触发的,传入的参数就是前面 {{{Serialize}}} 方法的返回内容。你可以用同样的方式将它反序列化。{{{Deserialize}}} 方法没有返回值。

一般情况下,我们没有必要使用这种方式来自定义序列化,如果你只是想要将类中的部分字段序列化,或者只是想在对象被序列化之前做一些清除操作的话,那么你可以实现 {{{__sleep}}} 和 {{{__wakeup}}} 这两个魔术方法来实现。

!!{{{__sleep}}} 和 {{{__wakeup}}} 这两个魔术方法如何使用?
{{{__sleep}}} 和 {{{__wakeup}}} 方法也是与 PHP 自定义序列化相兼容的一种方式。这两个方法不需要从某个接口继承,直接在类中实现即可。

{{{__sleep}}} 方法也是在对象被序列化之前自动触发,但是它的返回值不是序列化后的内容,而是指定哪些字段需要序列化。它的返回值类型是一个字符串数组,数组的每一个元素是需要序列化的字段名。你除了在 {{{__sleep}}} 方法中返回这些字段之外,你也可以作其他一些操作,比如关闭 socket 连接或者关闭数据库连接等操作。

{{{__wakeup}}} 方法是在对象被反序列化之后自动触发的,它被触发时,所有序列化的字段都已经被反序列化完毕,所以,你在 {{{__wakeup}}} 中要做的是反序列化的事后工作,比如恢复数据库连接或者 socket 连接等。

{{{__sleep}}} 和 {{{__wakeup}}} 方法无需同时实现,你只需把你用到的方法实现即可。

另外,{{{__sleep}}} 和 {{{__wakeup}}} 方法和前面提到的 {{{org.phprpc.util.Serializable}}} 接口中的 {{{Serialize}}} 和 {{{Deserialize}}} 方法都无需定义成 {{{public}}} 方法,定义成 {{{private}}} 即可。

!! 为什么有时候用接口类型来声明发布方法的返回值和参数类型会出错?
不管是在服务器端定义发布方法时,还是在客户端定义代理接口时,都不建议用接口类型来声明返回值和参数类型。因为接口是抽象的不能被实例化,因此对于容器类型的参数和返回结果来说,接口并不能表明究竟该转换为何种具体类型。不过对于自定义类型可以用接口,只要传递的类型实现了该接口即可。

!!为什么有时候 ~UseSevice 不能返回代理接口的对象?
如果在这一步发生错误,请检查代理接口是否明确声明为了 {{{public}}} 接口,因为目前的动态代理类只支持 {{{public}}} 接口。另外,用 {{{UseService}}} 返回接口对象时,不要忘记转型操作。

!!~ArrayList 和 ~HashMap 中的元素数据类型如何转换?
{{{ArrayList}}} 和 {{{HashMap}}} 作为结果返回或者作为参数传递给服务器处理的时候,对它们当中元素类型的处理不要用直接的转型操作,因为数据的真实类型可能不是你所认为的,所以直接转型的操作很可能会失败。最好的方式是使用 {{{PHPConvert}}} 类中的方法来进行转型。
!!PHPRPC for .NET 支持哪些服务器?
只要是支持 aspx 的服务器都可以运行 PHPRPC for .NET 服务器。所以 IIS、apache + mod_mono、xsp 等都支持。

!!如何发布服务?
.NET 中的对象方法或类的静态方法都可以直接作为 PHPRPC 服务来发布。但发布的方法必须是用 public 声明的方法,参数和返回值都必须是基本类型或可序列化的类型(带有 {{{SerializableAttribute}}} 属性的对象),对于泛型容器,只有 List 和 Dictionary 支持。除此之外,没有别的要求。

服务通过 aspx 发布。下面我们举一个简单发布的例子,这个例子我们采用 ASP.NET 2.0 代码分离方式编写:

''server.aspx''
<code xml>
<%@ Page Language="C#" CodeFile="server.aspx.cs" Inherits="server" %>
</code>

''server.aspx.cs''
<code c#>
using System;
using System.Collections.Generic;
using org.phprpc;

class ServerTest {
    public String Hi(String name) {
        return "Hello " + name;
    }
    public static List<int> Sort(List<int> list) {
        list.Sort();
        return list;
    }
}

public partial class server : System.Web.UI.Page {
    protected void Page_Load(object sender, EventArgs e) {
        PHPRPC_Server server = new PHPRPC_Server();
        server.Add("Hi", new ServerTest());
        server.Add("Sort", typeof(ServerTest));
        server.Start();
    }
}
</code>

通过这个例子,你会发现用 PHPRPC 发布服务很简单,只要先创建 {{{PHPRPC_Server}}} 类的一个对象实例,然后执行它的 {{{Add}}} 方法来添加要发布的方法,最后执行它的 {{{Start}}} 方法来启动服务就可以。

另外,提醒大家一点,不要忘记发布服务时,把 org.phprpc.dll 放到服务页面所在的 bin 目录下。服务页面的名字没有要求,server.aspx、index.aspx 或其它什么名字都可以。只要在调用时指定的 URL 与发布时的名字一样就可以了。

!!如何发布整个对象中的方法或整个类中的静态方法?
如果在使用 {{{Add}}} 方法时,只代入一个 {{{Object}}} 类型的参数,则该对象上的所有 {{{public}}} 方法都会作为服务发布。如果只代入一个 {{{Class}}} 类型的参数,则该类上的所有 {{{public static}}} 方法都会作为服务发布。

从 3.0.2 开始,对上述行为作了稍许改变,在 3.0.2 之后,只发布对象所在类或指定类上声明的方法,而祖先类的方法不会被发布,这一修改可以避免发布 {{{Object}}} 上或其它基类上你不希望发布的方法。

!!如何发布一个对象中的一组方法或一个类中一组静态方法?
如果第一个参数不是字符串,而是字符串数组,则所有与该数组中字符串值匹配的 {{{public}}} 方法都会作为服务发布。

!!如何用别名来发布方法?
有时你发布的几个对象或类中的方法可能具有相同的方法名,你也许会希望用不同的名字来发布它们,以便能够将它们区分开来。例如,你可能要同时发布一个 User 类和一个 Catalog 类,但是它们都有 Add、Update、Selete 方法,那么你可以通过这种方式来发布它们:

<code c#>
PHPRPC_Server server = new PHPRPC_Server();

server.Add("Add", typeof(User), "addUser");
server.Add("Update", typeof(User), "updateUser");
server.Add("Delete", typeof(User), "deleteUser");

server.Add(new string[] {"Add", "Update", "Delete"},
           typeof(Catalog),
           new string[] { "addCatalog", "updateCatalog", "deleteCatalog"});

server.Start();
</code>

从上面的代码中我们可以看到,不管是添加一个方法还是添加一组方法,都是支持别名的。

!!如何在发布的方法中使用会话(Session)?
有时,你可能希望服务器端发布的方法支持会话功能,在 .NET 中这很容易做到,只要在要发布的方法中,使用 {{{HttpContext.Current.Session}}} 就能获取到 {{{Session}}} 对象了。另外,你还可以通过 {{{HttpContext.Current.Request}}} 获取 {{{Request}}} 对象,通过 {{{HttpContext.Current.Server}}} 获取 {{{Server}}} 对象。但是不要用 {{{HttpContext.Current.Response}}} 获取 {{{Response}}} 对象并用它做任何输出操作。

!!如何让发布的方法支持输出重定向?
有时,我们定义的方法除了返回结果以外,还会输出一些信息,在远程调用这种方法时,我们可能会希望客户端不但可以得到远程方法的结果,还可以单独得到这些输出信息。PHPRPC 提供了这种能力。

你只需要将最后一个参数设置为 {{{System.IO.TextWriter}}} 或 {{{System.IO.StreamWriter}}} 类型的变量(任选其一,但不能同时定义它们),就可以通过该变量输出信息,并返回给客户端了。客户端在进行远程调用时,不需要代入该参数,而是通过 {{{Output}}} 属性来获取远程的输出信息。

当你需要返回给客户端许多字符串形式的内容时,我们推荐你用这种输出重定向方式来返回,因为它不需要服务器端序列化和客户端反序列化的过程,可以大大提高服务器和客户端的处理速度。

!!如何在服务器端方法执行出错时,返回更多的调试信息?
在发布服务时,只需要设置 {{{DebugMode}}} 熟悉值为 {{{true}}},然后再执行 {{{Start}}} 方法就可以返回更加详细的出错调试信息了。

!!如何发布全局方法?
有很多时候,你希望发布方法能够更加高效一些,而不希望每个请求都去执行方法的发布动作,这很容易,只需要使用{{{AddGlobal}}} 方法就可以了,不过这个方法你需要在 Global.asax 中执行,下面我们来看一个例子:

''Global.asax''
<code xml>
<%@ Application Language="C#" CodeBehind="Global.asax.cs" Inherits="Global" %>
</code>

''Global.asax.cs''
<code c#>
using System;
using org.phprpc;
using System.Collections.Generic;

class Test {
    public double add(double a, double b) {
        return a + b;
    }
    public string add(string a, string b) {
        return a + b;
    }
    public int sub(int a, int b) {
        return a - b;
    }
    public int inc(ref int n) {
        return n++;
    }
    public static string hello(string name, System.IO.TextWriter output) {
        string result = String.Concat("hello ", name);
        output.Write("output: " + result);
        return result;
    }
};

public class Global: System.Web.HttpApplication {
    protected void Application_Start(Object sender, EventArgs e) {
        PHPRPC_Server.AddGlobal(new string[] { "add", "sub", "inc" }, new Test());
        PHPRPC_Server.AddGlobal("hello", typeof(Test));
    }
}
</code>

要注意,全局方法不要跟每个请求中用 {{{Add}}} 发布的方法重名,否则会产生冲突。

这样就给全局的 {{{PHPRPC_Server}}} 类添加上了 add, sub, inc 和 hello 方法了。接下来发布的网页就简单多了,比如发布的网页是 index.aspx 的话,那么它的内容只需要这么点就够了:

''index.aspx''
<code xml>
<%@ Page Language="C#" %>
<% new org.phprpc.PHPRPC_Server().Start(); %>
</code>

是不是很简单啊?
PHPRPC for .NET 提供了完整的源代码,并且同时提供了编译好的所有版本的二进制库文件。一般情况下,你无需自己编译源代码,但是如果你有这个需求(比如对源代码的某些部分做一些特别的修改),只需要安装有相应版本的 .NET 运行时就可以进行编译,无需安装 SDK 和 Visual Studio。

另外,如果要编译 .NET Compact Framework 1.0 版本,至少需要安装 Microsoft .NET Compact Framework 1.0 ~SP3 Developer 和  Microsoft .NET  Framework 1.1 ~SP1。

如果要编译 .NET Compact Framework 2.0 版本,至少需要安装 Microsoft .NET Compact Framework 2.0 ~SP2 和  Microsoft .NET  Framework 2.0 或 3.5。

如果要编译 .NET Compact Framework 3.5 版本,至少需要安装 Microsoft .NET Compact Framework 3.5 和 Microsoft .NET Framework 3.5。

如果要编译 Sliverlight 2.0 版本,至少需要安装 Microsoft .NET Framework 3.5 和 Microsoft Sliverlight SDK。

如果要编译 Mono 版本,可以安装 Mono 1.x 或者 Mono 2.x。

如果要编译 Mono 2.0 版本,至少需要安装 Mono 2.x。

编译方法非常简单,在保证上述软件安装好的情况下,在 Windows 下只需要执行 make.bat 即可完成编译,在 Linux 下只需要执行 make 即可完成编译。

bin 目录下是保存的编译好的二进制库文件:
{{{
1.0---------+-- org.phprpc.dll         // PHPRPC for .NET Framework 1.0 的完整包
 |          +-- org.phprpc.client.dll  // PHPRPC for .NET Framework 1.0 的客户端包
 |
1.1---------+-- org.phprpc.dll         // PHPRPC for .NET Framework 1.1 的完整包
 |          +-- org.phprpc.client.dll  // PHPRPC for .NET Framework 1.1 的客户端包
 |
2.0---------+-- org.phprpc.dll         // PHPRPC for .NET Framework 2.0 的完整包
 |          +-- org.phprpc.client.dll  // PHPRPC for .NET Framework 2.0 的客户端包
 |
3.5---------+-- org.phprpc.dll         // PHPRPC for .NET Framework 3.5 的完整包
 |          +-- org.phprpc.client.dll  // PHPRPC for .NET Framework 3.5 的客户端包
 |
CF1.0---------- org.phprpc.client.dll  // PHPRPC for .NET Compact Framework 1.0 的客户端包
 |
CF2.0---------- org.phprpc.client.dll  // PHPRPC for .NET Compact Framework 2.0 的客户端包
 |
CF3.5---------- org.phprpc.client.dll  // PHPRPC for .NET Compact Framework 3.5 的客户端包
 |
Mono--------+-- org.phprpc.dll         // PHPRPC for Mono 1.x 的完整包
 |          +-- org.phprpc.client.dll  // PHPRPC for Mono 1.x 的客户端包
 |
Mono2-------+-- org.phprpc.dll         // PHPRPC for Mono 2.x 的完整包
 |          +-- org.phprpc.client.dll  // PHPRPC for Mono 2.x 的客户端包
 |
SilverLight2--- org.phprpc.client.dll  // PHPRPC for SilverLight 2.0 的客户端包
}}}

其中完整包中包含了服务器和客户端的实现,而客户端包中只有客户端的实现。你可以根据自己的需要来选择使用哪个包。使用时非常简单,只要把对应版本的 dll 引用到你的项目中就可以了。
PHPRPC for ASP 是使用 ~JScript 语言实现的,但是它支持 ~VBScript 和 ~JScript 两种语言,当然 ~VBScript 的一些类型跟 ~JScript 中的一些类型有所区别,所以在 compat.js 中我们提供了一些函数,通过这些函数可以实现这些类型之间的转换,我们在后面将会对这些函数做详细介绍。

*[[PHPRPC for ASP 的安装]]
*[[PHPRPC for ASP 服务器]]
*[[PHPRPC for ASP 客户端]]
*[[PHPRPC for ASP 工具包]]
*[[PHPRPC for ASP 常见问题解答]]
!!如何调用 PHPRPC 服务

我们先通过一个简单的例子,来介绍如何调用 PHPRPC 服务:

<code vb>
<%@ CodePage = 65001 %>
<script runat="server" language="JScript" type="text/javascript" src="phprpc_client.js"></script>
<script runat="server" language="VBScript">
Dim client
Set client = PHPRPC_Client.create("http://192.168.1.111/test.asp")
client.setProxy("http://10.1.0.1:8000/")
client.setKeyLength(96)
client.setEncryptMode(2)
Response.write(client.add(1, 2))
Response.write(client.sub(1, 2))
Response.write(client.getOutput())
</script>
</code>

上面这段代码是使用 ~VBScript 编写的,大家会发现创建 {{{client}}} 使用的是 {{{PHPRPC_Client}}} 的 {{{create}}} 方法,参数为 PHPRPC 服务器的 URL,如果我们在创建 {{{client}}} 对象时不指定服务器的 URL,后面我们也可以在调用 {{{useService}}} 方法时再指定服务器的 URL。

接下来,通过 {{{client}}} 的 {{{setProxy}}} 方法,我们可以设置连接到服务器所经过的 HTTP 代理服务器,代理服务器的设置除了可以单独指定 IP 地址和端口号以外,还可以直接通过 {{{http://10.1.0.1:8000/}}} 这种 URL 方式来指定代理服务器。另外,{{{setProxy}}} 方法也支持 Basic 方式认证,只要将用户名和密码作为参数带入即可。

{{{PHPRPC_Client}}} 对象的 {{{setKeyLength}}} 和 {{{setEncryptMode}}} 这两个方法是跟加密传输有关的。

{{{setKeyLength}}} 方法用于设置[[密钥长度]]。

{{{setEncryptMode}}} 方法用于设置[[加密模式]]。

上面设置代理、设置密钥长度、加密模式都是可选项,如果你不需要这些功能,可以直接忽略它们。

PHPRPC 3.0 for ASP 客户端与 Java、.NET 客户端不同,它不需要使用 {{{useService}}} 来返回指定接口的远程代理对象,ASP 客户端本身就是一个代理对象。所以,上面例子中 {{{client.add}}} 和 {{{client.sub}}} 这两个调用实际上调用的就是远程方法,对于 ASP 客户端来说,远程方法名不需要事先声明,客户端会自动从服务器端获取到所有的远程方法。但是你可能希望直接在客户端代码中指定所需要调用的远程方法名,而不是让它自动获取,这样至少可以减少一次跟服务器的通讯,这样做也是可以的,只需将方法名字符串数组作为第二个参数传递给 {{{create}}} 方法即可。但需要注意的是,这个方法名字符串数组是 ~JScript 数组,而不是 ~VBScript 中的数组。另外,你也无法在 ~VBScript 中通过 {{{["add", "sub"]}}} 这种方式构造 ~JScript 数组。因此,要调用带有这个参数的 {{{create}}} 方法,用 ~JScript 语言编写会比较方便。但是,如果你一定要用 ~VBScript 编写,也是能够完成的,你可以先创建一个 ~VBScript 的数组,然后给它的各个元素赋值,最后调用 {{{VBArrayToJSArray}}} 方法即可得到 ~JScript 数组。{{{VBArrayToJSArray}}} 是 PHPRPC 3.0 for ASP 中提供的一个 ~VBScript 和 ~JScript 之间容器类型转换的函数,关于更多这方面的内容,我们将在后面详细介绍。

最后,如果服务器端方法有输出重定向内容,你可以用 {{{getOutput}}} 方法来获取它。如果服务器端没有输出内容,该方法会返回空字符串。

通过这个例子,我想你已经可以掌握 PHPRPC for ASP 客户端的基本使用方法了。

!!如何在调用 PHPRPC 服务时,进行引用参数传递?

我们可以直接调用 {{{PHPRPC_Client}}} 对象的 {{{invoke}}} 方法进行引用参数传递的远程过程调用。上面的例子中,我们是通过远程代理方法来进行远程过程调用的,而代理方法本身实际上也是通过调用 {{{PHPRPC_Client}}} 的 {{{invoke}}} 方法来实现远程调用的。下面我们来通过一个简单的例子,看一下如何直接用 {{{invoke}}} 方法来调用远程过程,并实现引用参数传递。这个例子中我们用 ASP 版本的客户端来调用 PHP 版本的服务器端。下面是服务器端的 PHP 代码:

<code php>
<?php
include('phprpc_server.php');
function inc(&$n) {
    $n++;
}
$phprpc_server = new PHPRPC_Server();
$phprpc_server->add('inc');
$phprpc_server->start();
?>
</code>

这个服务器发布了一个 {{{inc}}} 方法,该方法是将参数值加一。这个方法是需要引用参数传递的。下面我们来看看如何在 ASP 中调用这个远程过程:

<code vb>
<%@ CodePage = 65001 %>
<script runat="server" language="JScript" type="text/javascript" src="phprpc_client.js"></script>
<script runat="server" language="VBScript">
Dim client, args
Set client = PHPRPC_Client.create("http://127.0.0.1/inc.php")
args = Array(1)
Set args = VBArrayToJSArray(args)
client.invoke "inc", args, true
args = JSArrayToVBArray(args)
Response.write(args(0))
</script>
</code>

这个例子我们是使用 ~VBScript 编写的,如果用 ~JScript 编写会更简单一些。因为 {{{invoke}}} 方法的第二个参数是 ~JScript 数组对象类型,所以 ~VBScript 的数组是不能直接带入的,需要先用 {{{VBArrayToJSArray}}} 方法转换成 ~JScript 的数组类型,转换后的结果是一个对象,所以需要在赋值时使用关键字 {{{Set}}},否则运行结果将会变得异常。当 {{{invoke}}} 方法的第三个参数设置为 {{{true}}} 时,表示的就是引用参数传递。当 {{{invoke}}} 方法调用完成后,{{{args}}} 中的参数值就已经改变了。但是如果需要显示出来的话,我们还需要把 {{{args}}} 从 ~JScript 的数组类型转换回 ~VBScript 数组类型,否则我们无法在 ~VBScript 中访问 ~JScript 数组中的元素,转换用到的是 {{{JSArrayToVBArray}}} 这个函数,这个函数也是 PHPRPC 3.0 for ASP 中提供的。

除了在引用参数传递时会用到 {{{invoke}}} 方法外,还有一种情况可能会用到它(极其罕见)。那就是当你的远程方法名称与 {{{PHPRPC_Client}}} 对象中的方法名称重名的时候,假如你有一个远程方法叫做 {{{setProxy}}},这时就会跟 {{{PHPRPC_Client}}} 中的 {{{setProxy}}} 方法有冲突了,当遇到这种情况,使用 {{{invoke}}} 方法就可以正确调用到远程方法了。

!!如何来得到远程过程执行的错误信息?

PHPRPC 把错误分为两种,一种是致命错误,一种是警告性错误。

当远程过程执行发生致命错误时,远程过程调用的返回值是一个 {{{PHPRPC_Error}}} 类型的对象,它其中包含了远程过程执行时发生的致命错误信息。

当远程过程执行发生警告性错误时,你可以通过 {{{PHPRPC_Client}}} 对象的 {{{getWarning}}} 方法来得到警告错误,{{{getWarning}}} 方法的返回的值也是 {{{PHPRPC_Error}}} 类型的对象。如果没有发生警告错误,{{{getWarning}}} 方法返回 {{{null}}}。

!!远程过程调用中的超时问题如何解决?

PHPRPC for ASP 客户端在进行远程过程调用时,默认的超时时间为 30 秒,如果你的网络线路不好,或者需要传递的数据量较大,或者服务器端处理数据的时间较长,则在执行过程中会发生超时错误。这时,只需要调用 {{{PHPRPC_Client}}} 对象的 {{{setTimeout}}} 方法来设置超时时间就可以了,单位是毫秒。

{{{setTimeout}}} 设置的超时时间是数据发送和数据接收的总时间,不包含域名解析和与服务器建立连接的时间。

!!远程过程调用中参数长度是否有限制?

PHPRPC 客户端本身没有对参数长度做限制,但是因为 PHPRPC 协议是通过 HTTP 协议进行传输的,如果 PHPRPC 服务器端的设置限制了 HTTP POST 数据的长度,则调用过程中,如果参数序列化之后的长度大于了这个限制,就会调用失败。

另外,如果服务器没有限制 POST 数据的大小,则客户端可以提交任意大小的数据,直到服务器内存耗尽才会出错。

PHPRPC 传输的数据量通常小于同样调用的 Web Service 传输的数据量的。因此,PHPRPC 调用所允许的参数长度一般比 Web Service 调用所允许的参数长度更大。
PHPRPC for ASP 中除了服务器和客户端实现之外,还提供了一些有用的函数和对象,该工具包除了在服务器和客户端的实现中被调用之外,你也可以单独使用它们。下面是关于该工具包的一些常见问题解答。

!!~VBScript 与 ~JScript 的一些转换辅助函数

前面在介绍客户端时,我们已经提到了两个函数 {{{VBArrayToJSArray}}} 和 {{{JSArrayToVBArray}}},这两个函数可以将 ~VBScript 中的数组和 ~JScript 中的数组进行相互转换。这两个函数主要在 ~VBScript 中使用。

当使用 {{{VBArrayToJSArray}}} 时,如果要将结果赋值给 ~VBScript 的变量,需要使用 {{{Set}}} 关键字,而 {{{JSArrayToVBArray}}} 的返回结果不需要。

{{{JSArrayToVBArray}}} 的返回值不能赋值给用 {{{Dim ()}}} 和 {{{Redim ()}}} 定义的 ~VBScript 数组变量。

{{{VBArrayToJSArray}}} 支持将 ~VBScript 中的多维数组转换为 ~JScript 的联合数组。但 {{{JSArrayToVBArray}}} 不能将 ~JScript 的联合数组转换为 ~VBScript 的多维数组,而只能转换为 ~VBScript 的一维数组,这时的 ~VBScript 数组中的元素为 ~JScript 的数组对象。

另外,在 ~VBScript 中因为没有 ~JScript 中的 {{{Object}}} 对象,所以通常使用 {{{Scripting.Dictionary}}} 对象来作为字典容器。但是 {{{Scripting.Dictionary}}} 在 PHPRPC 中不支持直接传递,所以,要传递 {{{Scripting.Dictionary}}} 类型就需要将其跟 ~JScript 的 {{{Object}}} 对象进行相互转换。这时你可能就需要工具包中的这两个函数了:{{{DictionaryToObject}}} 和 {{{ObjectToDictionary}}}。

{{{DictionaryToObject}}} 除了把 {{{Scripting.Dictionary}}} 对象转换为 ~JScript 的 {{{Object}}} 对象以外,还会把 {{{Scripting.Dictionary}}} 对象当中的元素也进行转换,比如元素类型如果是 ~VBScript 的数组,则该数组也会自动转换为 ~JScript 的数组。

但是 {{{ObjectToDictionary}}} 不会对元素类型进行转换。

!!用于 Base64 编码解码的函数

在 Firefox 中,内置了两个用于 Base64 编码解码的函数,它们分别是[[btoa|http://developer.mozilla.org/en/docs/DOM:window.btoa]] 和[[atob|http://developer.mozilla.org/en/docs/DOM:window.atob]]。为了跟 Firefox 中的这两个函数保持一致,在 PHPRPC 3.0 for ASP 中也提供了这两个函数。{{{btoa}}} 的参数是 binray 字符串类型(即字符串中的每个字符值都在 0-255 之间),{{{atob}}} 的结果是 binary 字符串类型。

!!XXTEA 对象如何使用?

{{{XXTEA}}} 对象提供了加密解密数据的方法。{{{encrypt}}} 是加密数据的方法,第一个参数是原文数据,第二个参数是密钥;{{{decrypt}}} 是解密数据的方法,第一个参数是密文数据,第二个参数是密钥。这两个方法的参数和返回值都是 binray 字符串类型。

!!~PHPSerializer 对象如何使用?

{{{PHPSerializer}}} 包含 {{{serialize}}} 和 {{{unserialize}}} 两个方法。{{{serialize}}} 可以将对象序列化,而 {{{unserialize}}} 可以把序列化的内容还原为对象。它除了支持 {{{Number}}}、{{{String}}}、{{{Date}}}、{{{Array}}}、{{{Object}}} 等 ~JScript 的内置类型以外,也支持自定义类型的序列化。您通常不需要直接使用这两个方法,在进行远程过程调用时,PHPRPC 会自动调用它们来完成序列化和反序列化的工作。
!! PHPRPC for ASP 是否支持自定义类型传输?

当然支持!所有在 ~JScript 中通过非匿名 {{{function}}} 定义的类型(对构造函数做 {{{new}}} 操作之后会得到该类型的对象)都可以序列化传输。但在 ~VBScript 中通过 {{{Class}}} 定义的类型不支持序列化。因此当您在 ASP 中使用 PHPRPC 要传递自定义类型的对象时,推荐选择 ~JScript 作为首选语言。

在 ~JScript 中通过匿名 {{{function}}} 定义的类型也支持序列化,但是反序列化后的类型为 {{{Object}}} 类型(对应于其它语言的字典或联合数组类型)。

自定义类型的名称与 {{{function}}} 定义的构造函数名相同。

~JScript 中没有名字空间的概念,不过仍然可以通过模拟的方式实现。但如果要让 PHPRPC 支持这种模拟的名称空间,需要在定义构造函数名时遵守以下约定:将包含名称空间的完整类型名称中的“.”号用“_”替换,将替换之后的字符串作为构造函数名。例如:

<code js>
// top namespace
var MySpace = {};

// sub namespace
MySpace.util = {};

// full type name
MySpace.util.MyType = (function () {
    // Hide MySpace_util_MyType
    return function MySpace_util_MyType() {
        ...
    }
})();
</code>

当这个类型的对象序列化后,就会被序列化为类型名称为 {{{MySpace_util_MyType}}} 的一个对象了,而反序列化时,他会自动寻找 {{{MySpace.util.MyType}}} 这个名字来进行反序列化。这样反序列化后,还可以得到原来该类型中定义的方法和属性,因此通过这种方式,~JScript 中定义的类型就可以与 java、.NET 这种带有名称空间的语言中定义的类型进行交换了。这种命名约定在 PHPRPC 3.0 for ~JavaScript 中同样适用。

PHPRPC 还支持自定义序列化方式,自定义序列化方式有两种,一种是通过在自定义类型中实现 {{{__sleep}}} 和 {{{__wakeup}}} 这两个方法来自定义序列化方式,这种较为简单,但这种方式只能限制对哪些字段进行序列化,而不能选择序列化格式。另一种是在其它支持接口继承的语言中通过为自定义类型实现 {{{Serializable}}} 接口来实现自定义序列化方式,这种较为复杂,允许自定义序列化格式。但是因为 ~JScript 是不支持接口继承的,不过它仍然两种方式都支持。只是第二种方式下,只需要在自定义类型中实现 {{{serialize}}} 和 {{{unserialize}}} 这两个方法就可以了。

下面我们分别对这两种方式进行一下简要的介绍:

{{{__sleep}}} 方法是在对象被序列化之前自动触发的。它的返回值类型是一个字符串数组,数组的每一个元素是需要序列化的字段名。你除了在 {{{__sleep}}} 方法中返回这些字段之外,你也可以作其他一些操作,比如关闭文件或者关闭数据库连接等操作。

{{{__wakeup}}} 方法是在对象被反序列化之后自动触发的,它被触发时,所有序列化的字段都已经被反序列化完毕,所以,你在 {{{__wakeup}}} 中要做的是反序列化的事后工作,比如打开文件或着恢复数据库连接等操作。

{{{__sleep}}} 和 {{{__wakeup}}} 方法无需同时实现,你只需把你用到的方法实现即可。

{{{serialize}}} 方法也是在对象被序列化时自动触发的,你可以在 {{{serialize}}} 方法中对该对象做任何方式的序列化,例如 SOAP、JSON 或者 WDDX 等,只要返回的结果是 binray 字符串就可以了。

{{{unserialize}}} 方法是在对象被反序列化时自动触发的,传入的参数就是前面 {{{serialize}}} 方法的返回内容。你可以用同样的方式将它反序列化。{{{unserialize}}} 方法没有返回值。

这两种方式最多只能任选其一,如果两种方式都实现的话,只有 {{{serialize}}} 和 {{{unserialize}}} 方式生效。
!!PHPRPC for ASP 支持哪些版本的 IIS 服务器?

目前 PHPRPC for ASP 只在 Windows XP/2003 的 IIS 5.1/IIS 6 上进行过完整测试,其它版本未做测试。我们非常欢迎您能够报告您在其他版本上的部署情况,以帮助我们改善该开发包。

!!如何发布服务?

ASP 中的函数或 ~JScript 对象的方法都可以直接作为 PHPRPC 服务来发布。~VBScript 中使用 Class 定义的类的对象方法也可以作为服务发布,但发布时必须定义别名(关于别名我们后面讨论)。

直接使用 ASP 页面就可以发布 PHPRPC 服务,下面是一个简单的例子:

<code javascript>
<%@ CodePage = 65001 %>
<script runat="server" language="JScript" type="text/javascript" src="phprpc_server.js"></script>
<script runat="server" language="JScript">
function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}
var server = new PHPRPC_Server();
server.add("add");
server.add(subtract);
server.start();
</script>
</code>

上面的代码是用 ~JScript 的编写的,发布的也是 ~JScript 的函数。

通过这个例子,你会发现用 PHPRPC 发布服务很简单,只要先创建 {{{PHPRPC_Server}}} 的一个对象实例,然后执行它的 {{{add}}} 方法来添加要发布的方法,最后执行它的 {{{start}}} 方法来启动服务就可以。{{{start}}} 方法没有参数。

添加发布方法,可以使用函数名的字符串表示,也可以直接使用函数引用,但直接使用函数引用仅限于 ~JScript 中定义的函数,~VBScript 中定义的过程和函数都只能用字符串来发布。

服务页面的名字没有要求,rpc.asp、index.asp 或其它什么名字都可以。只要在调用时指定的 URL 与发布时的名字一样就可以了。

另外还要注意一点,上面的代码是 ~JScript 写的,~JScript 是区分大小写的,所以 {{{server}}} 对象这个名字是没有问题的。但是 ~VBScript 不区分大小写,如果在 ~VBScript 中用这个名字,就会跟 ASP 内置的 {{{Server}}} 对象冲突,所以一定要注意这一点。

下面我们来看一下用 ~VBScript 如何来发布服务。
<code vb>
<%@ CodePage = 65001 %>
<script runat="server" language="JScript" type="text/javascript" src="phprpc_server.js"></script>
<script runat="server" language="VBScript">
Function add(a, b)
    add = a + b
End Function
Function subtract(a, b)
    subtract = a - b
End Function
Set rpc_server = PHPRPC_Server.create()
rpc_server.add "add"
rpc_server.add "subtract"
rpc_server.start
</script>
</code>
这个例子,跟上面用 ~JScript 完成的例子差不多,区别主要在于创建 {{{PHPRPC_Server}}} 对象时,使用的是 {{{PHPRPC_Server}}} 的静态方法 {{{create}}},因为在 ~VBScript 中无法使用 {{{New}}} 来直接创建 ~JScript 对象,因此我们提供了这个 {{{create}}} 方法。

另一点区别就是,调用 {{{add}}} 和 {{{start}}} 方法时要遵守 ~VBScript 惯例,也就是过程调用不加括号,否则会报错。还有就是添加的 ~VBScript 函数和过程只能用字符串表示。

我们还会注意到不管是用 ~JScript 还是 ~VBScript,最开始都有
<code xml>
<%@ CodePage = 65001 %>
</code>
这一句。这是因为 PHPRPC for ASP 只支持 ~UTF-8 编码输出。65001 表示的就是 UTF-8。

!!如何发布 ~JScript 对象方法?

~JScript 对象方法跟 ~VBScript 对象方法发布方法不同,这里我们分开讨论。添加 ~JScript 对象的方法较为灵活,可以一次添加一个对象中的所有方法,也可以一次只添加方法。下面这个例子将演示一次添加一个对象中所有方法:
<code js>
<%@ CodePage = 65001 %>
<script runat="server" language="JScript" type="text/javascript" src="phprpc_server.js"></script>
<script runat="server" language="JScript">
function Test() {
    this.add = function (a, b) {
        return a + b;
    }
    this.subtract = function (a, b) {
        return a - b;
    }
}
var rpc_server = new PHPRPC_Server();
var t = new Test();
rpc_server.add(t);
rpc_server.start();
</script>
</code>

也就是说只要把对象直接作为 {{{add}}} 的第一个参数(也是唯一的)传入,就可以添加这个对象上的所有方法了。

那添加 ~JScript 对象方法的第二种方式如下所示:
<code js>
<%@ CodePage = 65001 %>
<script runat="server" language="JScript" type="text/javascript" src="phprpc_server.js"></script>
<script runat="server" language="JScript">
function Test() {
    this.add = function (a, b) {
        return a + b;
    }
    this.subtract = function (a, b) {
        return a - b;
    }
}
var rpc_server = new PHPRPC_Server();
var t = new Test();
rpc_server.add("add", t);
rpc_server.add(t.subtract, t, "sub");
rpc_server.start();
</script>
</code>

这里要注意,如果第一个参数(方法名)是字符串表示的话,只写方法名本身,而不写方法名前缀(这点跟后面介绍的添加 ~VBScript 对象方法的方式正好相反),第二个参数是方法所在对象,这种情况下,第三个参数(别名)可有可无。

而如果第一个参数(方法名)是方法引用表示的话,则第三个参数(别名)一定要写。

!!如何发布 ~VBScript 对象方法?

~VBScript 对象方法发布方式如下:

<code vb>
<%@ CodePage = 65001 %>
<script runat="server" language="JScript" type="text/javascript" src="phprpc_server.js"></script>
<script runat="server" language="VBScript">
Class Test
    Function add(a, b)
        add = a + b
    End Function
    Function subtract(a, b)
        subtract = a - b
    End Function
End Class
Set rpc_server = PHPRPC_Server.create()
Set t = new Test
rpc_server.add "t.add", null, "add"
rpc_server.add "t.subtract", null, "sub"
rpc_server.start
</script>
</code>

只有声明为 {{{Public}}}(默认就是 {{{Public}}})的 ~VBScript 对象方法才可以发布,声明为 {{{Private}}} 的 ~VBScript 对象方法如果发布的话,客户端将无法调用到该方法。

发布 ~VBScript 对象方法时,除了第一个参数是完整的对象方法的字符串表示外,第二个参数必须为 {{{null}}},否则客户端调用时将会产生“'undefined' 为空或不是对象”的错误。另外,别名也是必须的,否则客户端将无法调用该方法。

!!如何发布一个对象中的一组方法?

{{{PHPRPC_Server}}} 对象的 {{{add}}} 方法的第一个参数(方法名)和第三个参数(别名)可以接受数组,这时,数组中的所有的方法都会发布。当第一个参数是 ~JScript 对象方法名的字符串表示的数组时,第三个参数可以省略。其它情况必须指定第三个参数(别名)。当这两个参数都指定时,它们的长度必须相等,各个元素也要一一对应。另外,除了可以接受 ~JScript 数组外,也可以接受 ~VBScript 数组,但第一个参数和第三个参数的类型必须一致。

!!在发布的方法中是否可以使用 ASP 中的内置对象?

ASP 中提供了 {{{Request}}}、{{{Response}}}、{{{Session}}}、{{{Server}}}、{{{Application}}} 这几个主要内置对象,这些内置对象当中,除了 {{{Response}}} 不能直接使用以外,其它的对象都可以直接使用。效果与在普通的 ASP 程序中使用相同。

!!如何在发布的方法中支持输出重定向?

因为 ASP 中的 {{{Response}}} 对象不具有获取输出到缓存中数据的能力,因此,你无法直接通过 {{{Response}}} 来输出内容并重定向到客户端。但是 PHPRPC 3.0 for ASP 提供了一种变通的方法。在 PHPRPC 3.0 for ASP 中,提供了一个 {{{FakeResponse}}} 对象,如果你要发布的函数中有使用 {{{Response}}} 的话,只需要先定义一个局部变量 {{{Response}}} 给它赋值为 {{{FakeResponse}}},然后在使用 {{{Response}}} 就可以了,例如:

<code vb>
<%@ CodePage = 65001 %>
<script runat="server" language="JScript" type="text/javascript" src="phprpc_server.js"></script>
<script runat="server" language="VBScript">
Dim Response
Set Response = FakeResponse
Class Test
    Function add(a, b)
        add = a + b
    End Function
    Function subtract(a, b)
        subtract = a - b
        Response.Write "hah"
    End Function
End Class
Set rpc_server = PHPRPC_Server.create()
Set t = new Test
rpc_server.add "t.add", null, "add"
rpc_server.add "t.subtract", null, "sub"
rpc_server.start
</script>
</code>

这里要注意,{{{Response}}} 的首字母在 {{{Dim}}} 时不能够小写,如果是使用 ~JScript 而不是 ~VBScript,则 {{{Response}}} 这个局部变量必须在函数当中定义而不能在全局环境下定义,否则将于 ASP 的内置 {{{Response}}} 对象冲突,客户端调用将不会得到正确结果。
PHPRPC for ASP 下载之后,在 asp 目录下的是所有的 PHPRPC for ASP 的源代码。为了方便开发者调用,asp/compressed 目录下我们放置了已经打包压缩好的脚本。所以部署时,只需要把 asp/compressed 下的 2 个文件复制到你的项目目录中即可,另外,不要忘记把 dhparams 目录(一定要连同目录)也一起复制到你的项目目录中,该目录中存放的是加密传输时,生成密钥的参数。如果忘记复制该目录,则服务器端在加密传输情况下将无法工作。
Flash 中尽管有提供基于 amf 的 Flash Remoting 技术可以实现远程调用,但是 Flash Remoting 的 Java 和 .NET 服务器并不是开源免费的,而开源免费的 PHP 服务器又相当难用。最大的问题是,Flash Remoting 被限制在了 Flash 这个应用平台上,利用 Flash Remoting 技术很难做到一个服务器服务于多种类型的客户端。而有了 PHPRPC 之后,这一切麻烦都消失了。它不但可以有效的取代 Flash Remoting,而且比 Flash Remoting 使用更方便(即使是在 Flash 中),功能更强大,并可以服务于多种类型的客户端。

下面我们就来看一下,在 Flash/Flex 中如何来使用 PHPRPC 开发应用吧。

*[[PHPRPC for ActionScript 的安装]]
*[[PHPRPC for ActionScript 客户端]]
*[[PHPRPC for ActionScript 常见问题解答]]
PHPRPC for ~ActionScript 2.0 和 PHPRPC for ~ActionScript 3.0 用法基本一致,但也有不同之处,先让我们从最基本的开始说起吧。

!!如何调用 PHPRPC 服务

我们先通过一个简单的例子,来介绍如何调用 PHPRPC 服务。

下面是 ~ActionScript 2.0 中的调用:
<code as3>
import org.phprpc.PHPRPC_Error;
import org.phprpc.PHPRPC_Client;
var client:PHPRPC_Client = new PHPRPC_Client('http://localhost:8080/index.aspx', ['add', 'sub']);
client.keyLength = 256;
client.encryptMode = 2;
client.add(1, 2, function (result, args, output, warning) {
    trace(result.toString());
});
client.sub(1, 2, function (result, args, output, warning) {
    trace(result.toString());
});
</code>

下面是 ~ActionScript 3.0 中的调用:
<code as3>
import org.phprpc.PHPRPC_Error;
import org.phprpc.PHPRPC_Client;
var client:PHPRPC_Client = new PHPRPC_Client('http://localhost:8080/index.aspx', ['add', 'sub']);
client.keyLength = 256;
client.encryptMode = 2;
client.add(1, 2, function (result:*, args:Array, output:String, warning:PHPRPC_Error):void {
    trace(result.toString());
});
client.sub(1, 2, function (result:*, args:Array, output:String, warning:PHPRPC_Error):void {
    trace(result.toString());
});
</code>

大家会发现上面这两段代码唯一不同的地方就是 ~ActionScript 3.0 版本的回调函数参数类型更明确了,当然这也是语言本身要求的,因此也可以说它们在使用上是没有区别的。

{{{PHPRPC_Client}}} 对象的 {{{keyLength}}} 和 {{{encryptMode}}} 这两个属性是跟加密传输有关的。

{{{keyLength}}} 方法用于设置[[密钥长度]]。

{{{encryptMode}}} 方法用于设置[[加密模式]]。

上面设置密钥长度、加密模式都是可选项,如果你不需要这些功能,可以直接忽略它们。

PHPRPC 3.0 for ~ActionScript 客户端与 Java、.NET 客户端不同,它不需要使用 {{{useService}}} 来返回指定接口的远程代理对象,~ActionScript 客户端本身就是一个代理对象。所以,上面例子中 {{{client.add}}} 和 {{{client.sub}}} 这两个调用实际上调用的就是远程方法,对于 ~ActionScript 客户端来说,远程方法名不需要事先声明,但是还是建议像上面那样直接在客户端代码中指定所需要调用的远程方法名,这样可以避免当你使用一个不存在的方法时要到服务器端做检查,而不是在客户端做检查。

回调函数有四个参数,你可以认为它们是服务器端方法执行之后返回的内容。

第一个参数 {{{result}}} 是服务器端方法(函数)的返回值,它可以是任意类型。但如果服务器端返回字符串类型的数据的话,一定要用 {{{toString()}}} 方法进行明确转换才能得到正确结果,否则得到的是 {{{ByteArray}}} 类型的数据```包括数组、对象中的字符串类型数据也是一样要用 {{{toString()}}} 明确转换的。```。

第二个参数 {{{args}}} 是方法调用的参数,如果这个调用是一个引用参数传递的调用,参数也有可能被修改,这时候,你可以通过 {{{args}}} 来获得修改后的参数,关于引用参数传递的调用我们后面会做进一步说明。

第三个参数 {{{output}}} 是服务器端输出的内容,它是字符串类型的,无需用 {{{toString()}}} 方法明确转换为字符串。

第四个参数 {{{warning}}} 是服务器端发生的警告错误(目前只有 PHP 服务器会产生警告错误),一般只调试过程中可能会用到。

通过这个例子,我想你已经可以掌握 PHPRPC for ~ActionScript 客户端的基本使用方法了。

!!如何在调用 PHPRPC 服务时,进行引用参数传递?

引用参数传递实际上非常简单,看下面这个例子,首先来看 PHP 的服务器端:
<code php>
<?php
include('phprpc_server.php');
function inc(&$n) {
    $n++;
}
$phprpc_server = new PHPRPC_Server();
$phprpc_server->add('inc');
$phprpc_server->start();
?>
</code>

这个服务器发布了一个 {{{inc}}} 方法,该方法是将参数值加一。这个方法是需要引用参数传递的。下面我们来看看如何在 ActionScript 中调用这个远程过程:

''~ActionScript 2.0''
<code as3>
import org.phprpc.PHPRPC_Error;
import org.phprpc.PHPRPC_Client;
var client:PHPRPC_Client = new PHPRPC_Client('http://localhost/index.php', ['inc']);
client.inc(1, function (result, args, output, warning) {
    trace(args[0]);
}, true);
</code>

''~ActionScript 3.0''
<code as3>
import org.phprpc.PHPRPC_Error;
import org.phprpc.PHPRPC_Client;
var client:PHPRPC_Client = new PHPRPC_Client('http://localhost/index.php', ['inc']);
client.inc(1, function (result:*, args:Array, output:String, warning:PHPRPC_Error):void {
    trace(args[0]);
}, true);
</code>

其实很简单,只要在回调函数之后跟一个 {{{true}}} 参数就可以了。这个 {{{true}}} 就是表示启用引用参数传递。

!!如何来得到远程过程执行的错误信息?

PHPRPC 把错误分为两种,一种是致命错误,一种是警告性错误。

当远程过程执行发生致命错误时,远程过程调用的返回值是一个 {{{PHPRPC_Error}}} 类型的对象,它其中包含了远程过程执行时发生的致命错误信息。

当远程过程执行发生警告性错误时,你可以通过回调函数的第四个参数 {{{warning}}} 得到警告错误,{{{warning}}} 的值也是 {{{PHPRPC_Error}}} 类型的对象。如果没有发生警告错误,warning 为 {{{null}}}。
!!PHPRPC for ~ActionScript 支持 Flash Lite 吗?
支持 Flash Lite 2.0 以上的版本。不过因为 Flash Lite 本身现在还不支持 ~ActionScript 3.0,所以你只要使用 PHPRPC for ~ActionScript 2.0 就可以开发 Flash Lite 程序了。

!!PHPRPC for ~ActionScript 支持 AIR 吗?
支持,不论在 Flash 还是 Flex 中都可以使用 PHPRPC for ~ActionScript 3.0 开发 AIR 程序。因为 PHPRPC for ~ActionScript 3.0 功能比 2.0 要完备,可以算是完美支持 AIR 程序开发!

!!PHPRPC for ~ActionScript 支持传输自定义类型的对象吗?
PHPRPC for ~ActionScript 3.0 可以完美支持传输自定义类型的对象。PHPRPC for ~ActionScript 2.0 对于自定义对象作为字典传输,接收自定义对象也按照字典接收,但包含一个 name 属性,其值为该对象的类名。所以,一般情况下,推荐使用 PHPRPC for ~ActionScript 3.0。除非是做 Flash Lite 开发,否则完全没有必要使用 PHPRPC for ~ActionScript 2.0。

!!PHPRPC for ~ActionScript 3.0 如何支持自定义类型的对象?
PHPRPC for ~ActionScript 3.0 支持动态类和非动态类对象都支持,但是只能传输 public 成员和动态成员,不能传输 private 和 protected 成员```因为 ~ActionScript 3.0 不支持反射私有和保护成员```。例如下面这个类:
<code as3>
package {
	public dynamic class Test {
		public var a:String = "Hello";
		protected var b:Boolean = true;
		private var _x:int = 1;
		public function get x() {
			return _x;
		}
		public function set x(value:int) {
			_x = value;
		}
	}
}
</code>
只有字段 {{{a}}} 和属性 {{{x}}} 会被传输,{{{b}}} 和 {{{_x}}} 不会被传输。对于属性来说,必须满足可读、可写并且都是 public 的才可以被传输。

关于类名的规定与 PHPRPC for Java 类似:将完整名(包名+类名)中的分隔符({{{.}}} 和 {{{::}}})全部替换为 {{{_}}} 后,如果得到的名称相同则认为是相同的类。

PHPRPC for ~ActionScript 3.0 支持 {{{__sleep}}} 和 {{{__wakeup}}} 这两个与序列化相关的魔术方法。同样还支持通过实现 {{{org.phprpc.util.Serializable}}} 接口来实现自定义序列化。因为这两个特性跟 PHPRPC for Java 相似,而且很少用到,这里就不再详细讲解,感兴趣的读者可以参见 PHPRPC for Java 相关章节的内容。

!!PHPRPC for ~ActionScript 支持字符集设置吗?
PHPRPC for ~ActionScript 2.0 不支持字符集设置,只能用 ~UTF-8 字符集与服务器通讯。PHPRPC for ~ActionScript 3.0 支持字符集设置,可以以任何字符集跟服务器通讯。但仍然建议用 ~UTF-8,因为更通用效率也更高。
PHPRPC for ~ActionScript 有两个版本,它们分别是针对 ~ActionScript 2.0 和 ~ActionScript 3.0 的。在安装方面,它们基本上没有什么区别,所以这里按开发环境来分别介绍。

!!Flash 8 到 Flash ~CS3 的安装方法
首先确认你已经安装了相应版本的 [[Extension Manager|http://www.adobe.com/cn/exchange/em_download/]],然后安装 PHPRPC for ~ActionScript 2.0 只需要双击 ~PHPRPC_AS2.mxp 即可完成安装,安装 PHPRPC for ~ActionScript 3.0 只需要双击 ~PHPRPC_AS3.mxp 即可完成安装。安装之后可以在组件面板的 Data 组中找到,直接把它拖到你的库中就可以在你的程序中使用了。

!!Flash ~CS4 的安装方法
同样首先确认你已经安装了Adobe Extension Manager ~CS4,一般来说,安装 Flash ~CS4 时就会一同安装好的。然后安装 PHPRPC for ~ActionScript 2.0 只需要双击 ~PHPRPC_AS2_CS4.mxp 即可完成安装,安装 PHPRPC for ~ActionScript 3.0 只需要双击 ~PHPRPC_AS3_CS4.mxp 即可完成安装。安装之后可以在组件面板的 Data 组中找到,直接把它拖到你的库中就可以在你的程序中使用了。

!!Flex Builder 下的安装方法
实际上,在 Flex Builder 下无需安装,只要在你创建程序工程时,将 ~PHPRPC_AS2.swc 或 ~PHPRPC_AS3.swc```根据你的程序使用哪个版本的 ~ActionScript 来决定包含那个 swc 文件,不要同时包含。``` 添加到库中就可以使用了。
本章所述的 PHPRPC for Delphi 是针对 Delphi 6 - 2009 原生程序开发版本的。如果你想将 PHPRPC 用于 Delphi.NET,请参见 [[PHPRPC for .NET]] 章节的相关内容。

*[[PHPRPC for Delphi 的安装]]
*[[PHPRPC for Delphi 客户端]]
*[[PHPRPC for Delphi 对容器类型的支持]]
*[[PHPRPC for Delphi 对自定义类型的支持]]

另外,PHPRPC 还提供了 Lazarus(Free Pascal)版本,Lazarus 与 Delphi 很像,你可以认为它是一个开源版本的 Delphi。只不过现在的 Lazarus 还没有 Delphi 那样稳定。PHPRPC for Lazarus(Free Pascal)的用法与 for Delphi 版本的用法基本一致。这里就不再单独介绍了。
为了方便讲解,这里给出的演示是控制台程序:

<code delphi>
program Example;

{$APPTYPE CONSOLE}

uses
  PHPRPC;

var
  client: TPHPRPC_Client;
  clientProxy: Variant;
  intArray: array of Integer;
  arraylist: TArrayList;
  vhashmap: Variant;
  ohashmap: THashMap;
  i: Integer;
begin
  // 创建客户端对象,如果是图形界面程序,直接拖放组件到 Form 即可。
  client := TPHPRPC_Client.Create;
  // 返回远程代理对象,通过它就可以直接调用远程方法了。
  // 因为实际上该代理对象是 client 对象的 Variant 包装,所以该代理对象不需要单独释放。
  clientProxy := client.useService('http://localhost/index.aspx');

  // 设置交换密钥的长度(可选,默认 128)
  client.KeyLength := 256;
  // 设置加密模式(0 - 不加密、1 - 单向加密、2 - 双向加密、3 - 双向且输出加密),默认为 0
  client.EncryptMode := 1;

  // 数字参数
  writeln(clientProxy.add(1, 2));

  // 英文字符串参数
  writeln(clientProxy.add('foo', 'bar'));

  // Delphi 7 中文字符串参数要转换为UTF8编码
  writeln(UTF8ToAnsi(clientProxy.Hello(AnsiToUTF8('马秉尧'))));

  // Delphi 2009 中文字符串参数默认为UnicodeString
  // writeln(UTF8ToAnsi(clientProxy.Hello('马秉尧')));

  // 中文的服务器输出的也是UTF8编码,本地打印要转换为本地编码
  writeln(UTF8ToAnsi(client.Output));

  // 传递动态数组参数
  setLength(intArray, 10);
  // 主要不要越界
  for i := 0 to 9 do intArray[i] := Random(100000);
  // 返回值是 Variant 包装的 THashMap
  vhashmap := clientProxy.sort(Variant(intArray));
  // 这里不能用索引方式访问
  for i := 0 to 9 do writeln(vhashmap.get(i));
  writeln;
  // 使用完之后释放掉, 以免造成内存泄漏
  // 但动态数组不需要显式释放,它会在程序运行结束后自动释放
  vhashmap.Free;

  // 传递数组列表参数
  // 注意这里初始化了10个元素的空间,但是实际用了11个元素
  // 因为数组列表有自增长的特性,因此不会发生越界访问的情况
  // 但上面的动态数组则不可以越界访问,否则会发生意想不到的错误
  arraylist := TArrayList.Create(10);
  for i := 0 to 10 do arraylist[i] := Random(100000);

  vhashmap := clientProxy.sort(arraylist.ToVariant);
  // 这里不能用索引方式访问
  for i := 0 to 10 do writeln(vhashmap.get(i));
  writeln;

  // 可以显式通过 FromVariant 方法转换成 THashMap 对象
  ohashmap := THashMap(THashMap.FromVariant(vhashmap));
  // 这里不能用get方法访问
  for i := 0 to 10 do writeln(ohashmap[i]);
  writeln;

  // 释放掉原来的未排序数组
  arraylist.Free;

  // 将返回结果转换为 TArrayList 类型
  arraylist := ohashmap.ToArrayList;
  for i := 0 to 10 do writeln(arraylist[i]);
  writeln;

  vhashmap.Free;
  // vhashmap 释放后 ohashmap 也一起释放,反之亦然
  // 因此下面注释掉的语句如果执行的话会出错(或让程序崩溃)
  // for i := 0 to 10 do writeln(ohashmap[i]);

  // 但通过 ToArrayList 转换得到的是新对象,所以仍然可以使用
  for i := 0 to 10 do writeln(arraylist[i]);

  // 因此不要忘记释放资源,以免造成内存泄漏
  arraylist.Free;
  client.Free;
  readln;
end.
</code>

因为 Delphi 2009 中引入了 {{{UnicodeString}}},所以不需要 {{{AnsiToUTF8}}} 就可以正确按照 ~UTF-8 编码来传输 {{{string}}} 了。上面的程序中关于中文字符串部分已经给出了在不同版本下的解决方案,但是 Delphi 7 的写法在 Delphi 2009 上也可以继续使用。不过目前的 PHP 4/5 还不支持 Delphi 中传输的 {{{WideString}}} 字符串,但将来的 PHP 6 会支持。目前其它所有语言的 PHPRPC 服务器都已经支持 Delphi 中传输的 {{{WideString}}} 字符串。

 其它需要注意的问题都已经在注释中写清楚了,这里就不再重复了。
尽管 Delphi 的 VCL/CLX 中提供了一些容器类型(比如 {{{TList}}},{{{TObjectList}}},{{{TStringList}}} 等),但是这些容器要么是针对指针的(无所不包),要么是针对对象的(基本类型就无法存了),甚至是针对某种特殊类型的(这样就限制就更大了)。所以这些容器类型与其它语言中的列表或字典类容器进行直接交换就存在一些问题了,不是包括的范围太大就是范围太小。

所以,在 PHPRPC for Delphi 中,专门提供了一组用于跟其它语言交换数据的又方便操作的容器类型。下面就对这些类型做一下详细的介绍。

首先第一个要介绍的是 Delphi 的动态数组类型,这个不是 PHPRPC for Delphi 中提供的,而是 Delphi 自己提供的。比如 {{{TIntegerDynArray}}},{{{TWordDynArray}}},{{{TDoubleDynArray}}} 等,这些是在 Delphi 的 {{{Types}}} 单元中定义的,如果需要直接引用这个单元就可以使用这些类型。

不过有两点要注意,第一是 {{{TStringDynArray}}}({{{array of string}}}),它在通过 PHPRPC 传输的时候可能并不像你希望的那样工作,因为 Delphi 本身会将其中的字符串转换成 {{{OLEStr}}} 来进行传递,如果你的 String 是二进制字符串,而不是本地编码的文本,最后的结果可能就不是你期望的了。所以在跟其它语言进行数据交互时,不推荐使用动态字符串数组类型。

第二是 {{{TByteDynArray}}}({{{array of Byte}}}),为了优化该类型传输,该类型会以 {{{AnsiString}}}(RawByteString)方式序列化传输。在被反序列化时,也会被作为 {{{AnsiString}}} 类型被反序列化。如果希望结果被直接反序列化为 {{{TByteDynArray}}} 类型,则可以将 TPHPRPC_Client 对象的 {{{StringAsByteArray}}} 属性设置为 {{{True}}} 即可。但对于自定义类型的属性,不论是 {{{AnsiString}}} 还是 {{{TByteDynArray}}} 类型,都无需设置 {{{StringAsByteArray}}},PHPRPC 会自动按照正确的类型反序列化。

PHPRPC for Delphi 除了支持动态数组外,还提供了更加高级的容器类型,它们是 TArrayList,THashedArrayList 和 THashMap。另外,还有一个字符串操作的帮助类 TStringBuffer。

TArrayList 类似于动态数组 TVariantDynArray,可以保存的元素也是 Variant 类型,但是 TArrayList 提供了一些方法允许你方便的添加、删除、查找、修改、移动元素,并且容器大小是可自动增长的。

THashedArrayList 是 TArrayList 的一个子类,它跟 TArrayList 实现的操作是一样的,不过它在存取下标不连续的数组时,效率更高。{{{IndexOf}}} 操作效率也更高,THashedArrayList 效率为 O(1),而 TArrayList 为 O(n)。

THashMap 可以让你通过一个 {{{Variant}}} 变量来索引另一个 {{{Variant}}} 变量,不过通常用来做索引的是整数或者字符串。如果要通过 PHPRPC 传递的话,那么索引必须是整数或字符串类型,其他类型的索引会被忽略。

通过 PHPRPC 调用其它语言发布的服务时,如果其它语言返回的类型是数组、列表或者字典等类型的数据,在 Delphi 中接收到之后都会作为 THashMap 返回,如果接收到的是一个对象类型,当这个对象类型在 Delphi 中没有对应的定义时,也将作为 THashMap 返回。如果你希望以 TArrayList 来操作返回值,可以用 THashMap 的 {{{ToArrayList}}} 方法将结果转换为 TArrayList 类型,这个 {{{ToArrayList}}} 返回的是一个独立的 TArrayList 对象,使用后注意要用 {{{Free}}} 方法将它释放,否则会产生内存泄漏。

TStringBuffer 就不用详细介绍了,它没有什么特别的,就是一个用来可以方便修改二进制字符串的帮助类,通过它的 {{{ToString}}} 方法可以得到最后的字符串。

这些类型都是 TPHPObject 的子类,都可以转化成 {{{Variant}}} 类型。了解了这些容器类型以后,用 PHPRPC for Delphi 写程序就会方便多了。
PHPRPC for Delphi 中除了支持基本数据类型、容器类型传递以外,同样也支持自定义类型的传递,而能够进行传递的自定义类型的基类就是 TPHPObject。这个类是在 PHPRPC 单元中定义的。下面我们就对这个类进行一下深入的剖析。

首先第一个问题,为什么要以 TPHPObject 作为 PHPRPC 传输的自定义类型的基类,而不是 {{{TPersistent}}} 呢?

因为 PHPRPC 支持传输的数据类型除了对象类型外,还有基本数据类型,为了可以使它们用同一种方式传输,最简单的方式就是把他们都变成 {{{Variant}}} 类型。而 {{{TPersistent}}} 类是不能转换成 {{{Variant}}} 类型的,而且 {{{TPersistent}}} 提供的持久化方式也与 PHPRPC 所支持的序列化方式不同,因此才没有使用 {{{TPersistent}}},而是通过定义一个新的类 TPHPObject 作为 PHPRPC 支持的可传输自定义类型的基类。

TPHPObject 有一个无参 {{{Create}}} 方法,因为在反序列化该对象时,是需要调用这个无参构造方法来创建对象的。另外,它还有一个继承自 {{{TComponent}}} 的带有 {{{AOwner}}} 参数的 {{{Create}}} 方法。一般情况下,无需覆盖这两个方法,除非你需要初始化一些数据。

该类中提供的其它方法如果没有特殊需求,一般也都不需要覆盖,只需要定义需要序列化的属性就可以了,例如:

<code delphi>
TUser = class(TPHPObject)
private
  FId: Integer;
  FName: AnsiString;
  FPassword: AnsiString;
  FBirthday: TDateTime;
published
  property ID: Integer read FId write FId;
  property Name: AnsiString read FName write FName;
  property Password: AnsiString read FPassword write FPassword;
  property Birthday: TDateTime read FBirthday write FBirthday;
end;
</code>

这个类中,{{{ID}}},{{{Name}}},{{{Password}}} 和 {{{Birthday}}} 这四个属性在传输时就会自动被序列化了,如果某个属性你不希望它被序列化(比如它是一个只读属性),使用 {{{stored}}} 指令将其设置为 {{{False}}} 就可以了。注意,要序列化的属性必须是 {{{published}}} 的,而不能是 {{{public}}} 的。

另外,TPHPObject 有一个 {{{Properties}}} 属性(它不是 {{{published}}} 的,所以无需担心它在传输时会被一起序列化),通过该属性,你可以通过属性名的字符串表示来访问属性值,返回值都是 {{{Variant}}} 类型的。

是否这样定义之后就可以了在 PHPRPC 中传输它了呢?不,还需要做一步小工作,那就是注册它。TPHPObject 提供了一个类方法 {{{RegisterClass}}} 用于注册类自身。例如:

<code delphi:nocontrols>
TUser.RegisterClass('User');
</code>

就将 {{{TUser}}} 类注册成了别名为 User 的类。

那这个别名是干啥的呢?因为在其它语言中定义类的命名规则可能跟 Delphi 中不同,例如 PHP 或者 .NET 中,是没有在类名前加 ''T'' 这个约定的,但是在 Delphi 的类基本上都是以 ''T'' 开头的,为了能够跟其它语言互通,所以就提供了这样一个别名机制,注册之后就可以跟其它语言中的具有同样属性(或字段)的 User 类进行交互了。但是在 Delphi 中,你仍然使用的是 {{{TUser}}} 这个类。

另外,向 .NET 有名空间的概念,Java 也有包的概念,比如你定义的 {{{TUser}}} 如果要跟 .NET 或 Java 中的 {{{myNameSpace.mySubNameSpace.User}}} 那么在注册时,注册为:

<code delphi:nocontrols>
TUser.RegisterClass('myNameSpace_mySubNameSpace_User');
</code>

就可以了,注意这里把 {{{.}}} 变成了 {{{_}}}。

如果类名与其它语言相同是否可以省略这个注册过程呢?不可以的,因为在反序列化时只有注册过的类才能被查找到,否则就不能够被反序列化了(简单的说就是没有经过注册的类可以作为参数传出,但不能作为结果返回)。不过这种同名的注册可以简化一下,直接执行不带参数的 {{{RegisterClass}}} 方法就可以了。```如果在定义这些类的单元的 {{{initialization}}} 段中加入这些注册语句的调用的话,就可以避免在使用时忘记注册它们了。```

如何创建 {{{TUser}}} 的对象呢?当然你可以使用 {{{Create}}} 来创建了,但是这样创建的对象是一个普通的对象,而不是一个 {{{Variant}}} 对象。那么怎样才能将它变成一个 {{{Variant}}} 对象呢?TPHPObject 提供了两种方式。一种是使用 {{{New}}} 方法(注意:这不是一个运算符),另一种是使用 {{{ToVariant}}} 方法。实际上 {{{New}}} 方法所做的就是在调用了 {{{Create}}} 方法之后又调用了 {{{ToVariant}}} 方法。因此如果你定义了带参的 {{{Create}}} 构造方法,可以自己同时定义一个带参的 {{{New}}} 方法,这样创建可传递的 {{{Variant}}} 对象就方便多了。

不过有一点大家要注意,Delphi 是没有自动垃圾回收机制的,因此当你创建了对象之后,在你不再需要它时,记得调用 {{{Free}}} 方法将它释放。TPHPObject 的 {{{Variant}}} 类型,在赋值时或传参时是引用传递的,而不是克隆对象,因此在你 {{{Free}}} 之后,它的所有副本也都不可以再用。

那如何将一个 {{{Variant}}} 表示的 TPHPObject 子类对象变成一个普通对象呢?也很简单,TPHPObject 提供了一个 {{{FromVariant}}} 类方法,可以用它来完成这个工作,例如:

<code delphi>
var
  user: TUser;
  vuser: Variant;
begin
  vuser := TUser.New;
  user := TUser(TUser.FromVariant(vuser));
  user.Free;
end;
</code>

上面这个例子中,你发现我是在 {{{user}}} 上调用的 {{{Free}}} 方法,实际上在 {{{vuser}}} 上调用 {{{Free}}} 方法也可以得到同样的效果。

那是否可以给这种自定义的类型添加我们自己的方法呢?当然可以。但是你可能会发现,通过 {{{Create}}} 创建出来的对象,可以调用你加的这些方法,而通过 {{{New}}} 创建出来的 {{{Variant}}} 对象却不能像上面的 {{{Free}}} 方法那样调用。如何才能使我们自己定义的方法也可以在 {{{Variant}}} 对象上直接被调用呢?也很简单,TPHPObject 有两个保护方法:{{{DoFunction}}} 和 {{{DoProcedure}}},只要覆写(override)它们就可以了。下面是 TStringBuffer 中对这两个方法的覆写(override):

<code delphi>
function TStringBuffer.DoFunction(var Dest: TVarData; const Name: string;
  const Arguments: TVarDataArray): Boolean;
var
  Ident: string;
begin
  Ident := LowerCase(Name);
  Result := True;
  if Ident = 'readstring' then
    Variant(Dest) := ReadString(Variant(Arguments[0]))
  else if Ident = 'seek' then
    Variant(Dest) := Seek(Variant(Arguments[0]), Variant(Arguments[1]))
  else
    Result := inherited DoFunction(Dest, Name, Arguments);
end;

function TStringBuffer.DoProcedure(const Name: string;
  const Arguments: TVarDataArray): Boolean;
var
  Ident: string;
begin
  Ident := LowerCase(Name);
  Result := True;
  if Ident = 'insertstring' then
    InsertString(RawByteString(Variant(Arguments[0])))
  else if Ident = 'writestring' then
    WriteString(RawByteString(Variant(Arguments[0])))
  else
    Result := inherited DoProcedure(Name, Arguments);
end;
</code>

大家可以以这个为模板在在自己的类中覆写(override)它们,就可以直接以 {{{Variant}}} 对象的形式来直接调用自定义的方法了。

如果有特殊需求的话,你可能会覆写的方法大约有这几个:{{{__sleep}}},{{{__wakeup}}},{{{ToBoolean}}},{{{ToDate}}},{{{ToDouble}}}},{{{ToInt64}}},{{{ToInteger}}} 和 {{{ToString}}}。其中,{{{__sleep}}} 和 {{{__wakeup}}} 与序列化和反序列化有关。{{{ToXXX}}} 的方法是用来进行类型转换的。一般情况下,你自定义的类是不会转换为这些类型的({{{ToString}}} 也许是个例外)。

{{{__sleep}}} 方法的返回值是一个 {{{TStringDynArray}}} 类型,它用来做一些序列化之前的工作。如果它的返回值为空({{{nil}}}),则按照默认的方式(就是前面介绍的方式)序列化对象,如果它的返回值不为空,则只序列化那些与返回值中包含的名称相同的属性(当然这些属性必须要出现在 {{{published}}} 声明部分,否则就会出错了)。

{{{__wakeup}}} 方法是在反序列化之后被调用,它没有参数也没有返回值,它一般用于反序列化之后的扫尾工作。

{{{ToBoolean}}},{{{ToDate}}},{{{ToDouble}}},{{{ToInt64}}} 默认是抛出异常,当然它们都是 {{{protected}}} 的方法,如果你不覆写它们,你也不会调用到它们。

{{{ToInteger}}} 也是 {{{protected}}} 方法,它返回的是对象的指针地址的整数表示,不要改写它,它是有特殊用途的。

{{{ToString}}} 默认是返回该对象序列化后的 {{{AnsiString}}} 表示,你当然可以把它覆写成你需要的形式,覆写它不会影响序列化的正常工作。

另外,还有一些虽然也是 {{{virtual}}} 的方法,但是你一定不要去试图覆写它们,除非你知道你在做什么。这些方法包括 {{{DoSerialize}}},{{{DoUnSerialize}}},{{{GetProperty}}},{{{SetProperty}}},{{{Equal}}},{{{HashCode}}} 和 {{{ToVariant}}}。

最后,再补充说明一下 ISerializable 接口。如果你的自定义类型继承自 TPHPObject,同时实现了 ISerializable 接口的话,那么这个类型就可以使用你自定义的序列化方式传输了。关于这种自定义序列化方式,可以参见 PHP 手册中关于 {{{Serializable}}} 接口的说明,它们的意义和实现的功能是完全相同的,这里就不再详细叙述了,因为你可能一辈子都不会用到它。
PHPRPC for Delphi 是以组件形式提供的,目前只提供了客户端组件,它是非可视化的组件,所以不论是在窗口程序还是控制台程序中你都可以使用它。

直接在 Delphi 中安装该组件包后就可以使用了,不过目前只提供了 Delphi 7 和 Delphi 2009 的组件安装包。如果你是用其它版本的 Delphi 可以通过自己创建组件包来安装,或者将现有版本的组件包转换为你所使用版本的组件包。

安装组件包非常简单。如果你是用的是 Delphi 7,打开 ~PHPRPC_D7.dpk 编译安装即可。如果你使用的是 Delphi 2009,打开 ~PHPRPC_D2009.dproj 编译安装即可。按照之后就可以直接从 Internet 面板上找到 ~PHPRPC_Client 组件了。
Groovy = Java + Scripting
Groovy 自 2004 年发展至今,尝试撷取其他直译式语言的部分优点,加上 JDK 十余年优良传统的加持,它试图在开发的领域上多一些乐趣及简捷。并且把类似 Python Ruby 等的强大功能带到 Java 的世界里。

他是完全贴近 Java Platform 的脚本语言。Groovy 可以采用直译方式以便于开发,或是编译成字节码提升执行效能。终极理想是支援所有的 Java 功能,让使用者可以同时使用两者的撰写方式及程序库,并将两者的优点发挥到极致。

*[[PHPRPC for Groovy 的安装]]
*[[PHPRPC for Groovy 客户端]]
PHPRPC for Groovy 和 PHPRPC for Java 用法基本一致,先让我们从最基本的开始说起吧。

!!如何调用 PHPRPC 服务

我们先通过一个简单的例子,来介绍如何调用 PHPRPC 服务。

<code java>
import org.phprpc.*
import org.phprpc.util.*

class A implements java.io.Serializable {
    public String a
}
class B implements java.io.Serializable {
    public Integer a
    public List b
    public String c

}
interface IHello {
    public String get_str(str)
    public Integer add(Integer a, Integer b)
    public Integer get_int()
    public List get_array()
    public HashMap get_hash()
    public String send_obj(A obj)
    public B get_obj()
}

client = new PHPRPC_Client("http://phprpc-example.appspot.com/")
client.setKeyLength(512)                   // 加密传长度
client.setEncryptMode(1)                   // 加密模式

clientProxy = client.useService(IHello)

println clientProxy.get_str('Hello World') // 传回 PHPRPC Example.Hello World
println clientProxy.add(1,2)               // 调用远程函数 add,将打印 3
println clientProxy.get_int()              // 调用远程函数 get_int,将打印12345
println clientProxy.get_array()            // 调用远程函数 get_array,将打印 [1, 2, 3, 4, 5]
println clientProxy.get_hash()             // 调用远程函数 get_hash,将打印 [a:1, c:3, b:2]

obj_a = new A()
obj_a.a = 'test'
println clientProxy.send_obj(obj_a)        //调用远程函数 send_obj,将打印 A.a = test

obj_b = clientProxy.get_obj()              //调用远程函数 get_obj,将打印 B object
println obj_b.a                            //值为 1
println obj_b.b                            //值为 [1, 2, 3, 4, 5]
println obj_b.c                            //值为 PHPRPC Example.
</code>

setKeyLength 用于设置[[密钥长度]]。
setEncryptMode 用于设置[[加密模式]]。

看完以上范例,相信很快就能使用安全又有效率的 PHPRPC 了!
须要更详细的说明,请参考 [[PHPRPC for Java 客户端]]
直接使用 [[PHPRPC for Java]] 的源码编译出来的 phprpc_client.jar 放置 Groovy 的 lib 目录下即安装完毕。
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 for Java 客户端支持异步调用吗?
前面介绍的方法都是同步调用,在大多数情况下,同步调用即可满足您的需要。但是有 2 种情况下用异步调用会更加方便一些:

首先是当做图像界面编程时,要防止单线程下同步调用出现卡死界面的现象,可以使用异步调用,但这种情况下,采用同步调用加多线程技术也可以解决。

另一种情况是,当使用同步调用加多线程时,如果你要获取服务器端输出重定向的内容或者要获取警告错误,则可能或获取到错误的内容,原因是另外的线程执行时也有可能改写 {{{output}}} 和 {{{warning}}} 属性的值。这种情况下,使用异步调用可以轻松解决。

因此,在 3.0.2 版本之后我们增加了对异步调用的支持。下面我们就看看如何实现异步调用。

!!如何实现异步调用?
异步调用跟同步调用类似,你可以通过接口方式,也可以通过直接 {{{invoke}}} 方式完成。不同的是,异步调用时,可以通过接口方式实现引用参数传递和为某个异步调用指定加密方式。而同步调用在接口方式下不支持这一点。

下面我们来看一下异步调用的例子(这个例子中也有同步调用):
<code java>
import org.phprpc.*;

interface IHello {
    // 同步调用
    public String hello(String name);
    // 异步调用
    public void hello(String name, PHPRPC_Callback callback);
    // 可以设置引用参数传递的异步调用
    public void hello(String name, PHPRPC_Callback callback, boolean byRef);
    // 可以设置引用参数传递和指定加密方式的异步调用
    public void hello(String name, PHPRPC_Callback callback, boolean byRef, int encryptMode);

}
public class HelloWorld {
    public static void main (String [] args) {
        final PHPRPC_Client client = new PHPRPC_Client ("http://127.0.0.1:8080/index.aspx");
        final IHello clientProxy = (IHello) client.useService(IHello.class);
        client.setKeyLength(1024);
        client.setEncryptMode(2);

        // 回调方法的定义(handler1 到 handler4 都是回调方法)
        PHPRPC_Callback callback = new PHPRPC_Callback() {
            public void handler1(String result) {
                System.out.println("handler1:");
                System.out.println(result);
                System.out.println();
            }
            public void handler2(String result, Object[] args) {
                System.out.println("handler2:");
                System.out.println(result);
                System.out.println(args[0]);
                System.out.println();
            }
            public void handler3(String result, Object[] args, String output) {
                System.out.println("handler3:");
                System.out.println(result);
                System.out.println(args[0]);
                System.out.println(output);
                System.out.println();
            }
            public void handler4(String result, Object[] args, String output, PHPRPC_Error warning) {
                System.out.println("handler4:");
                System.out.println(result);
                System.out.println(args[0]);
                System.out.println(output);
                System.out.println(warning);
                System.out.println();
            }
            // 专门用于错误处理的回调方法,默认行为是什么都不做
            public void errorHandler(Throwable error) {
                System.out.println(error.toString());
            }
        };

        // 异步调用
        clientProxy.hello("World1", callback);
        // 功能同上
        clientProxy.hello("World2", callback, false);
        // 加密级别设为三级
        clientProxy.hello("World3", callback, false, 3);
        // 引用参数传递
        clientProxy.hello("World4", callback, true);
        // 引用参数传递,加密级别设为无
        clientProxy.hello("World5", callback, true, 0);

        // 采用直接 invoke 方式的异步调用
        client.invoke("hello", new Object[] {"World6"}, callback, false, 3);

        // 同步调用
        System.out.println(clientProxy.hello("World7"));
    }
}
</code>

上面这个例子中,{{{IHello}}} 接口定义了 1 个同步调用方法,3 个异步调用方法,当然这 4 个方法对于的服务器端方法都是同一个 {{{hello}}}。

后面在回调方法定义部分,我们采用了创建匿名类对象的方式,其中 {{{handler1}}} 到 {{{handler4}}} 这些方法名都是随意取的,它们叫什么名字都无所谓(比如叫 {{{abc}}}、{{{efg}}} 都是可以的),回调方法的个数也不是固定,回调方法可以一个也不定义,也可以定义许多个,没有个数限制,所以这些方法都会作为回调方法被执行。回调方法的参数个数是 1 到 4 个,参数顺序分别是 {{{result}}}、{{{args}}}、{{{output}}}、{{{warning}}}。

{{{result}}} 是服务器执行返回的结果,它的类型声明跟服务器端的类型应该相容,否则会产生转型错误,关于错误处理后面我们再介绍。

{{{args}}} 是方法的参数,当方法为异步调用时,参数值可能会有所改变。它的元素的实际类型与发起调用时也不一定相同,应该用 {{{org.phprpc.util.Cast}}} 的 {{{cast}}} 方法转换元素到实际类型。

{{{output}}} 是服务器端输出重定向的内容,它是字符串类型的。

{{{warning}}} 是服务器端发生的经过错误,它是 {{{PHPRPC_Error}}} 类型的。

在回调函数参数列表中,上述参数可以不用全部指定,但必须按上述顺序指定,中间不能跳过。

而 {{{errorHandler}}} 是一个特殊的回调方法,它只在发生错误时被执行,它的参数是 {{{Throwable}}} 类型的,你可以不用重新定义它,不过它的默认行为是什么都不做。

上面的程序在正确的情况下执行结果可能是这样的:

{{{
handler1:
hello World5

handler2:
hello World5
[B@70329f3d

handler3:
hello World5
[B@70329f3d
output: hello World5
handler1:
hello World6


handler4:
hello World5
handler2:
hello World6
World6

[B@70329f3d
output: hello World5
null

handler3:
hello World6
World6
output: hello World6

handler4:
hello World6
World6
output: hello World6
null

handler1:
hello World4

handler2:
hello World4
[B@b749757

handler3:
hello World4
[B@b749757
output: hello World4

handler4:
hello World4
[B@b749757
output: hello World4
null

handler1:
hello World3

handler2:
hello World3
World3

handler3:
hello World3
World3
output: hello World3

handler4:
hello World3
World3
output: hello World3
null

handler1:
hello World1

hello World7
handler2:
hello World1
World1

handler3:
hello World1
World1
output: hello World1

handler4:
hello World1
World1
output: hello World1
null

handler1:
hello World2

handler2:
hello World2
World2

handler3:
hello World2
World2
output: hello World2

handler4:
hello World2
World2
output: hello World2
null
}}}

因为是异步调用,所以你会发现输出的顺序并不是按照执行的顺序来的。另外,你会注意到 World4 和 World5 的调用在输出 {{{args[0]}}} 时,变成了一个字节数组地址,原因就是引用参数传递后,数据类型也是可以发生了变化的。

现在我们应该理解如何来进行异步调用了。

另外,在 {{{J2ME}}} 版本中,异步调用也不支持代理方式。另外,回调方法也只能定义一个,且名字和参数个数,参数类型都是固定的,具体格式参见 {{{J2ME}}} 版本的 {{{PHPRPC_Callback}}} 的定义。

!!远程过程调用中参数长度是否有限制?
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 中除了服务器和客户端实现之外,还有一些工具类,这些工具类除了在服务器和客户端的实现中被调用之外,你也可以单独使用它们。下面是关于这些工具类的常见问题解答。

!!~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 中相容类型(关于相容类型的解释请参见[[PHPRPC for Java 常见问题解答]]部分)的转化功能。其中最重要的方法是 {{{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 支持方法的重载,但是有几点问题需要注意。

最重要的一点是最好避免发布重载的方法。因为服务器处理重载方法比非重载方法效率要低。另外,如果你不发布重载的方法,也就不需要注意下面这些问题了。

首先,应尽量避免重载参数个数相同且参数类型相容的方法。原因是 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 类型的数据,例如文件、图片等。

数组类型、所有实现了 {{{List}}}、{{{Map}}}、{{{Set}}}、{{{Collection}}} 接口的类型(比如 {{{ArrayList}}}、{{{HashMap}}})和 {{{AssocArray}}} 是相容类型。如果 {{{Collection}}}(包括 {{{List}}}、{{{Set}}} 及其它们的子类)中存放的类型是单一的数据类型,那么它跟该数据类型的数组是相容的。如果 {{{Map}}} 中的索引键都是整数,并且是从零开始递增的,那么它与 {{{Collection}}}(包括 {{{List}}}、{{{Set}}} 及其它们的子类)是相容的。所有实现了 {{{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 客户端来调用。

!!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}}} 方法。

!!~ArrayList 和 ~HashMap 中的元素数据类型如何转换?
{{{ArrayList}}} 和 {{{HashMap}}} 作为结果返回或者作为参数传递给服务器处理的时候,对它们当中元素类型的处理最好不要用直接的转型操作,因为数据的真实类型可能不是你所认为的,所以直接转型的操作很可能会失败。最好的方式是使用 {{{Cast}}} 类的 {{{cast}}} 方法来进行转型。

!! 为什么有时候用接口类型来声明发布方法的返回值和参数类型会出错?

不管是在服务器端定义发布方法时,还是在客户端定义代理接口时,都不建议用接口类型来声明返回值和参数类型。因为接口是抽象的不能被实例化,因此对于容器类型的参数和返回结果来说,接口并不能表明究竟该转换为何种具体类型。不过对于自定义类型可以用接口,只要传递的类型实现了该接口即可。

不过在 3.0.2 版之后,对 {{{List}}}、{{{Map}}}、{{{Set}}}、{{{Collection}}} 这四个容器接口也提供了支持,如果使用这四个接口类型来接收数据,那么 {{{List}}}、{{{Collection}}} 会被转换成 {{{ArrayList}}} 类型,{{{Set}}} 会被转换成 {{{HashSet}}} 类型,{{{Map}}} 会被转成 {{{HashMap}}} 类型。

!!客户端是否支持 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、~GlassFish 和 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}}} 方法都会作为服务发布。

从 3.0.2 开始,对上述行为作了稍许改变,在 3.0.2 之后,只发布对象所在类或指定类上声明的方法,而祖先类的方法不会被发布,这一修改可以避免发布 {{{Object}}} 上或其它基类上你不希望发布的方法。

例如:
<code java>
class A {
    public void a() {}
}

class B extends A {
    public void b() {}
}

class C extends B {
    public void c() {}
}

C o = new C();


server.add(o);    // 只发布 c();
server.add(o, B); // 只发布 b();
server.add(o, A); // 只发布 a();
</code>

以上代码中,{{{server}}} 为 {{{PHPRPC_Server}}} 的实例对象。而在 3.0.2 之前:

<code java>
server.add(o);    // 会把 C、B、A 以及 Object 上的所有方法都发布。
</code>

这种行为显然不是我们期望的,因此,如果您想发布整个对象或类上的方法,推荐使用 3.0.2 及其之后的版本。

!!如何发布一个对象中的一组方法或一个类中一组静态方法?
如果第一个参数不是字符串,而是字符串数组,则所有与该数组中字符串值匹配的 {{{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 有 2 个版本,一个是 for ~J2SE、~J2EE 的版本,另一个是 for ~J2ME 的版本。 后面我们所说的 PHPRPC for Java 就是指 for ~J2SE、~J2EE 的版本,而不包含 for ~J2ME 的版本。

PHPRPC for Java 支持在 JDK 1.2 及其更高版本上编译。编译前,你需要将你使用的 ~J2EE 服务器中的 servlet.jar(不同的服务器,这个文件名称也不同,比如 Tomcat 中叫做 servlet-api.jar)复制到 PHPRPC for Java 目录下,取代默认的 servlet.jar,然后执行 make.bat(Windows 下)或者 make(unix/linux 下),就会自动生成 phprpc.jar 和 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 对 ~JavaScript 的支持是非常早的,最初协议的设计中对数据的编码方式,返回数据的格式以及回调参数这些内容都充分考虑了 ~JavaScript 的特性。大部分 PHPRPC 用户最初也是把 PHPRPC 作为一个优秀的 Ajax 方案来使用的。但是 PHPRPC for ~JavaScript 不同于其它那些专门用于 Ajax 的 RPC 方案(如 DWR、xajax 等),PHPRPC for ~JavaScript 客户端与服务器是松散耦合的,它不依赖于某种特定语言编写的服务器。并且它还可以同目前许多优秀的 Ajax 框架(如 ~JQuery、YUI、~MooTools 等)一起使用,而不会有任何冲突。并且它还有一个最大的好处,那就是可以让你轻松实现跨域调用。

下面我们就一起开始 PHPRPC for ~JavaScript 之旅吧!

*[[PHPRPC for JavaScript 的安装]]
*[[PHPRPC for JavaScript 客户端]]
*[[PHPRPC for JavaScript 常见问题解答]]
PHPRPC for ~JavaScript  的 js 版本和 ajs 版本除了前面安装部分所介绍的引入方法有所不同之外,在用法上基本没有区别。下面我们先来看一下基本用法。

!!如何调用 PHPRPC 服务

我们先通过一个简单的例子,来介绍如何调用 PHPRPC 服务。

<code js>
var client = new PHPRPC_Client('http://localhost:8080/index.aspx', ['add', 'sub']);
client.setKeyLength(256);
client.setEncryptMode(2);
client.add(1, 2, function (result, args, output, warning) {
    alert(result);
});
client.sub(1, 2, function (result, args, output, warning) {
    alert(result);
});
</code>

{{{PHPRPC_Client}}} 对象的 {{{setKeyLength}}} 和 {{{setEncryptMode}}} 这两个方法是跟加密传输有关的。

{{{setKeyLength}}} 方法用于设置[[密钥长度]]。

{{{setEncryptMode}}} 方法用于设置[[加密模式]]。

上面设置密钥长度、加密模式都是可选项,如果你不需要这些功能,可以直接忽略它们。

PHPRPC 3.0 for ~JavaScript 客户端与 Java、.NET 客户端不同,它不需要使用 {{{useService}}} 来返回指定接口的远程代理对象,~JavaScript 客户端本身就是一个代理对象。所以,上面例子中 {{{client.add}}} 和 {{{client.sub}}} 这两个调用实际上调用的就是远程方法,对于 ~JavaScript 客户端来说,远程方法名虽然可以不事先声明,但这样只能在 {{{client.onready}}} 事件发生后或者 {{{client.getReady()}}} 等于 true 时才能进行调用,但这种做法是为了保持与旧版本兼容而提供的,属于过时的方法,所以不推荐这种做法,而是强烈建议像上面那样直接在客户端代码中指定所需要调用的远程方法名。

回调函数有四个参数,你可以认为它们是服务器端方法执行之后返回的内容。

第一个参数 {{{result}}} 是服务器端方法(函数)的返回值,它可以是任意类型。

第二个参数 {{{args}}} 是方法调用的参数,如果这个调用是一个引用参数传递的调用,参数也有可能被修改,这时候,你可以通过 {{{args}}} 来获得修改后的参数,关于引用参数传递的调用我们后面会做进一步说明。

第三个参数 {{{output}}} 是服务器端输出的内容,它是字符串类型的。

第四个参数 {{{warning}}} 是服务器端发生的警告错误(目前只有 PHP 服务器会产生警告错误),一般只调试过程中可能会用到。

通过这个例子,我想你已经可以掌握 PHPRPC for ~JavaScript 客户端的基本使用方法了。

!!如何在调用 PHPRPC 服务时,进行引用参数传递?

引用参数传递实际上非常简单,看下面这个例子,首先来看 PHP 的服务器端:
<code php>
<?php
include('phprpc_server.php');
function inc(&$n) {
    $n++;
}
$phprpc_server = new PHPRPC_Server();
$phprpc_server->add('inc');
$phprpc_server->start();
?>
</code>

这个服务器发布了一个 {{{inc}}} 方法,该方法是将参数值加一。这个方法是需要引用参数传递的。下面我们来看看如何在 JavaScript 中调用这个远程过程:

<code js>
var client = new PHPRPC_Client('http://localhost/index.php', ['inc']);
client.inc(1, function (result, args, output, warning) {
    alert(args[0]);
}, true);
</code>

其实很简单,只要在回调函数之后跟一个 {{{true}}} 参数就可以了。这个 {{{true}}} 就是表示启用引用参数传递。

!!如何来得到远程过程执行的错误信息?

PHPRPC 把错误分为两种,一种是致命错误,一种是警告性错误。

当远程过程执行发生致命错误时,远程过程调用的返回值是一个 {{{PHPRPC_Error}}} 类型的对象,它其中包含了远程过程执行时发生的致命错误信息。

当远程过程执行发生警告性错误时,你可以通过回调函数的第四个参数 {{{warning}}} 得到警告错误,{{{warning}}} 的值也是 {{{PHPRPC_Error}}} 类型的对象。如果没有发生警告错误,warning 为 {{{null}}}。
!! PHPRPC for ~JavaScript 是否支持自定义类型传输?

当然支持!所有在 ~JavaScript 中通过非匿名 {{{function}}} 定义的类型(对构造函数做 {{{new}}} 操作之后会得到该类型的对象)都可以序列化传输。

在 ~JavaScript 中通过匿名 {{{function}}} 定义的类型也支持序列化,但是反序列化后的类型为 {{{Object}}} 类型(对应于其它语言的字典或联合数组类型)。

自定义类型的名称与 {{{function}}} 定义的构造函数名相同。

~JavaScript 中没有名字空间的概念,不过仍然可以通过模拟的方式实现。但如果要让 PHPRPC 支持这种模拟的名称空间,需要在定义构造函数名时遵守以下约定:将包含名称空间的完整类型名称中的“.”号用“_”替换,将替换之后的字符串作为构造函数名。例如:

<code js>
// top namespace
var MySpace = {};

// sub namespace
MySpace.util = {};

// full type name
MySpace.util.MyType = (function () {
    // Hide MySpace_util_MyType
    return function MySpace_util_MyType() {
        ...
    }
})();
</code>

当这个类型的对象序列化后,就会被序列化为类型名称为 {{{MySpace_util_MyType}}} 的一个对象了,而反序列化时,他会自动寻找 {{{MySpace.util.MyType}}} 这个名字来进行反序列化。这样反序列化后,还可以得到原来该类型中定义的方法和属性,因此通过这种方式,~JavaScript 中定义的类型就可以与 java、.NET 这种带有名称空间的语言中定义的类型进行交换了。这种命名约定在 PHPRPC 3.0 for ASP 中同样适用。

PHPRPC 还支持自定义序列化方式,自定义序列化方式有两种,一种是通过在自定义类型中实现 {{{__sleep}}} 和 {{{__wakeup}}} 这两个方法来自定义序列化方式,这种较为简单,但这种方式只能限制对哪些字段进行序列化,而不能选择序列化格式。另一种是在其它支持接口继承的语言中通过为自定义类型实现 {{{Serializable}}} 接口来实现自定义序列化方式,这种较为复杂,允许自定义序列化格式。但是因为 ~JavaScript 是不支持接口继承的,不过它仍然两种方式都支持。只是第二种方式下,只需要在自定义类型中实现 {{{serialize}}} 和 {{{unserialize}}} 这两个方法就可以了。

下面我们分别对这两种方式进行一下简要的介绍:

{{{__sleep}}} 方法是在对象被序列化之前自动触发的。它的返回值类型是一个字符串数组,数组的每一个元素是需要序列化的字段名。你除了在 {{{__sleep}}} 方法中返回这些字段之外,你也可以作其他一些操作,比如关闭文件或者关闭数据库连接等操作。

{{{__wakeup}}} 方法是在对象被反序列化之后自动触发的,它被触发时,所有序列化的字段都已经被反序列化完毕,所以,你在 {{{__wakeup}}} 中要做的是反序列化的事后工作,比如打开文件或着恢复数据库连接等操作。

{{{__sleep}}} 和 {{{__wakeup}}} 方法无需同时实现,你只需把你用到的方法实现即可。

{{{serialize}}} 方法也是在对象被序列化时自动触发的,你可以在 {{{serialize}}} 方法中对该对象做任何方式的序列化,例如 SOAP、JSON 或者 WDDX 等,只要返回的结果是 binray 字符串就可以了。

{{{unserialize}}} 方法是在对象被反序列化时自动触发的,传入的参数就是前面 {{{serialize}}} 方法的返回内容。你可以用同样的方式将它反序列化。{{{unserialize}}} 方法没有返回值。

这两种方式最多只能任选其一,如果两种方式都实现的话,只有 {{{serialize}}} 和 {{{unserialize}}} 方式生效。

!!PHPRPC for ~JavaScript 是如何实现跨域调用的?
PHPRPC for ~JavaScript 的两个版本都可以实现跨域调用,但是它们采用了不同的方法。

对于纯 ~JavaScript 版本,你只需要一个 phprpc_client.js (压缩版本的)文件就可以使用它,它采用的是动态添加和删除 script 标签的方法实现的跨域调用,该方法支持目前大多数现代浏览器,例如 IE 5.0+、Netscape 7.0+、Mozilla 1.0+、Firefox 1.0+、Opera 7.5+、Safari 2.0+、Konqueror 4.0+、Chrome 等。不过,采用该方法实现跨域调用时,传递的参数长度会有所限制,一旦长度超过浏览器(或服务器)的限制后,将会调用失败。因此,它比较适合用于做查询操作的远程调用,而不适合做提交数据和修改数据操作的远程调用。

对于 ~JavaScript 和 Flash 混合实现的版本,你需要一个 phprpc_client.js(压缩版本的)文件和一个 flashrequest.swf 文件,还需要在网页里包含 phprpc_flash.js 文件来插入 flashrequest.swf。另外,要实现跨域调用,服务器端还必须要在根目录下部署 crossdomain.xml。原理是用 Flash 代替 ~XMLHttpRequest 来实现跨域调用,虽然看上去比纯 ~JavaScript 实现的要稍微麻烦一些,但是它支持所有的支持 ~FlashPlayer 9、10 的浏览器。而且对于参数长度没有限制,另外,服务器端的 crossdomain.xml 文件还可以对跨域做特殊限制。如果你需要跨域提交较大的数据,采用这个版本是一个比较合适的选择。
PHPRPC for ~JavaScript 有 2 个版本的实现,一个是纯 ~JavaScript 实现的版本(js 版本),另一个是 Flash 与 ~JavaScript 混合实现的版本(ajs 版本```这里 ajs 的意思是 Another ~JavaScript 的意思,你也可以理解为 ~ActionScript & ~JavaScript 的意思。```)。

PHPRPC for ~JavaScript 两个版本的安装使用方法略有不同,我们下面分别来介绍一下。

!!js 版本的安装

js 目录下是纯 ~JavaScript 版本的源代码,其中的 compressed 目录下是压缩后的版本,压缩后的版本有 2 个,直接存放在 compressed 目录下的是不兼容 Windows IE 5.0 的版本(但兼容 Windows IE 5.5 及其以上版本),ie5compat 目录下是兼容 Windows IE 5.0 浏览器的版本。通常情况下,您不需要使用 ie5compat 的版本,这并不是因为 ie5compat 版本更大一些,运行效率更低一些。而是因为现在还在使用 IE 5.0 的用户基本上已经不存在了。另一个更重要的原因是你页面中的 HTML 部分可能根本就无法在 IE 5.0 上正常显示,其它的(除了 PHPRPC 以外的)脚本也无法在 IE 5.0 上面正常运行。因为 IE 5.0 对 HTML 的显示和 ~JavaScript 的支持实在是相当差劲。

你如果要在你的页面中引用 PHPRPC for ~JavaScript,只需要把 compressed 下的 phprpc_client.js 复制到你的 Web 目录的脚本目录下,然后在你的页面中像引用其它外部脚本一样引用该脚本文件就可以了,引用的代码看上去像下面这样:
<code html>
<script type="text/javascript" src="scripts/phprpc_client.js"></script>
</code>
上面这句,你可以放在 head 中,也可以放在 body 中,只要放在你创建 ~PHPRPC_Client 对象之前就可以了。

!!ajs 版本的安装

ajs 目录下是 ~JavaScript 和 Flash 混合实现版本的源代码。同样,你只需要关心 compressed 下的文件(如果你不打算修改源代码的话)。你需要做的同样是将 phprpc_client.js 复制到你的 Web 目录的脚本目录下,同时把 phprpc_flash.js 也复制过去,而 flashrequest.swf 文件你可以放到 Web 目录下的任何你觉得合适的目录下。然后你需要将 phprpc_flash.js 中的 flashrequest.swf 路径修改为你实际的 Web 路径,修改后的代码可能会像这样:
<code js>
document.write(['<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" ',
                'type="application/x-shockwave-flash" ',
                'codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" ',
                'width="0" height="0" id="flashrequest_as3">',
                '<param name="movie" value="/scripts/flashrequest.swf" />',
                '<param name="allowScriptAccess" value="always" />',
                '<param name="quality" value="high" />',
                '<embed src="/scripts/flashrequest.swf" type="application/x-shockwave-flash" ',
                'width="0" height="0" name="flashrequest_as3" allowScriptAccess="always" />',
                '</object>'].join(''));
</code>
最后,在你的页面中引用这两个脚本,其中 phprpc_client.js 可以放在 head 或者 body 中,而 phproc_flash.js 需要放到 body 中,但不要放到 form 中。
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 来进行调用,只是看上去不那么直观。但是当你真正需要通过字符串表示的方法名来调用远程方法时,它就是你最好的选择了。
!!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。|
!!问:
<<<
为何发布服务器时出现
{{{
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 信息的编辑器来重新保存你的程序。
<<<

----

!!问:
>如何使用输出重定向?

!!答:
>服务器端很简单,只要发布的方法中有 {{{echo}}}、{{{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 等)也提供压缩输出的功能,所以一般情况下,没有必要打开这个开关。
该版本直接解压后就可以使用,其中
*bigint.php
*compat.php
*phprpc_date.php
*xxtea.php
属于公共文件。不论是客户端还是服务器端都需要这些文件。

*phprpc_client.php
是客户端文件,如果你只需要使用客户端,那么只要有上面那些公共文件和这个文件就可以使用了,使用时,直接在你的程序中包含 phprpc_client.php 就可以,公共文件不需要单独包含。

*dhparams
*dhparams.php
*phprpc_server.php
这三个文件是服务器端需要的文件。
其中 dhparams 目录中包含的是加密传输时用来生成密钥的参数,如果你需要更详细的解释,请参见 [[PHPRPC 加密传输]]。
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}}}
2008 年至今最热的话题应该就是云计算 (cloud computing)了,Google、Amazon、Alibaba、IBM、Microsoft 都提出了其各自的云计算方案,目前最便宜、最好用的云计算当属 Google 的 Google App Engine(免费额度足以让你营运每个月 500 万 PV)。不过要用纯 Python```Google App Engine 目前只提供对 Python 的支持。``` 开发完整的云端应用,也确实有些困难。不过现在有了 PHPRPC for Python,你也可以轻松漫步在云端,体验云计算了~

让我们开始漫步在云端吧~

*[[PHPRPC for Python 的安装]]
*[[PHPRPC for Python 服务器]]
*[[PHPRPC for Python 客户端]]
*[[PHPRPC for Python 常见问题解答]]
!!客户端实例
<code python>
from phprpc import PHPRPC_Client
client = PHPRPC_Client('http://bawbaw.icittys.com/rpc_server.php')
clientProxy = client.useService()
print client.add(1, 2) # 将显示3
print clientProxy.add(1, 2) # 另一种呼叫方法
</code>

这里 {{{clientProxy}}} 唯一的用处就是当服务器端发布的方法与客户端 {{{client}}} 提供的方法或属性重名的时候,{{{clientProxy}}} 可以让你方便且正确的调用远程方法。

!!加密传输
<code python>
from phprpc import PHPRPC_Client
client = PHPRPC_Client('http://bawbaw.icittys.com/rpc_server.php')
client.keylength = 256 # 加密长度
client.encryptmode = 2 # 双向加密
print client.add(1, 2) # 将显示3
</code>

keylength 用于设置[[密钥长度]]。
encryptmode 用于设置[[加密模式]]。

!!Google App Engine 上的 PHPRPC 客户端

由于 Google App Engine 禁用了{{{urllib}}} 等 HTTP Client,因此在 Google App Engine 中引入 {{{PHPRPC_Client}}} 的方法有所不同。
<code python>
from phprpc.GoogleAppEngine import PHPRPC_Client
</code>

另外,Google App Engine 的 {{{PHPRPC_Client}}} 不支持 HTTP 代理服务器设置功能,而普通的 {{{PHPRPC_Client}}} 支持。

不过从 Google App Engine 1.1.9 开始,已经提供了对 Python 标准 web 请求 {{{urllib}}},{{{urllib2}}} 和 {{{httplib}}} 的支持,因此普通的 {{{PHPRPC_Client}}} 可以直接在 Google App Engine 1.1.9 及其之后的版本中使用,而无需从 {{{phprpc.GoogleAppEngine}}} 名空间中导入。
!!PHPRPC for Python 是否支持自定义类型?

支持,只要名称空间(模块名)、类名和实例变量定义可以与其它语言中定义的相对应,就可以跨语言传递。例如下面这段 python 程序
<code python>
class MyClass:
    def __init__(self):
        self.name = 'PHPRPC'
        self.age = 3
        self.ok = True
</code>
如果它定义在 ~MyModule.py 中,则它的模块名就是 {{{MyModule}}},那么这个 {{{MyClass}}} 与下面这段 Java 程序
<code java>
package MyModule;

class MyClass implements java.io.Serializable {
    private String name;
    protected int age;
    public boolean ok;
    MyClass() {
        name = "PHPRPC";
        age = 3;
        ok = true;
    }
}
</code>
中的 {{{MyClass}}} 就属于 PHPRPC 支持的可以跨语言传输的自定义数据类型。

上面你还会注意到,Java 中不管是私有成员、保护成员还是公共成员都是可以跟 Python 中的实例变量进行数据交换的。

当然,更复杂一些,即使名称空间和类名不相同,但是符合以下规则也可以进行跨语言传输:
如果将名称空间与类名之间的分隔符(例如 {{{.}}},{{{::}}} 等)全部替换为 {{{_}}} 之后,可以得到相同的名称,则这两个类型在 PHPRPC 中也被认为是可以跨语言传输的类型。

之所以这样设计是为了可以与不支持名称空间的语言进行交互,例如下面这段 python 程序
<code python>
class MyClass:
    def __init__(self):
        self.name = 'PHPRPC'
        self.age = 3
        self.ok = True
</code>
我们仍然假设它定义在 ~MyModule.py 中,它的模块名是 {{{MyModule}}},那么这个 {{{MyClass}}} 就可以跟下面这段 PHP 程序
<code php>
class MyModule_MyClass {
    var $name;
    var $age;
    var $ok;
}
</code>
中的 {{{MyModule_MyClass}}} 通过 PHPRPC 进行相互交换了。

!!PHPRPC for Python 如何与 Java 中实现了 org.phprpc.util.Serializable 接口的自定义类型进行交互?

目前 PHPRPC 所支持的语言中,对所有支持接口的语言都有类似于 Java 中 {{{org.phprpc.util.Serializable}}} 接口的机制。而 Python 语言本身并没有接口机制,那么是否意味着将不能与实现了这种机制的语言交换这种类型的数据了呢?

不是的,在 PHPRPC for Python 中你同样可以实现这种自定义序列化机制,只不过不再需要接口,而是利用鸭子类型的机制。也就是说,只要在你定义的类中,实现 {{{serialize}}} 和 {{{unserialize}}} 方法,就可以与其它实现了这种自定义序列化机制的语言进行这种类型的数据交互了。

同样,{{{__sleep}}} 和 {{{__wakeup}}} 方法的序列化机制也支持。

!!为何从客户端传来的列表或元组参数在服务器端会变成字典呢?
!!为何从服务器端返回的列表或元组到客户端也会变成字典呢?
!!我该如何让它们变回列表或元组?

这三个问题实际上是同一个问题,因为 PHP 序列化会将列表、元组和字典序列化为同一种类型的数据表示,因此在反序列化时都会被反序列化为字典类型的数据。对于其它强类型语言来说,可以根据参数类型或返回结果类型声明来自动进行类型转换。但是 Python 是弱类型语言,因此无法获知参数或结果是何种类型,因此只能以不丢失结果的字典类型来返回。

如果你希望得到列表,那么你可以使用 {{{phpformat}}} 中的 {{{dict_to_list}}} 方法来得到。如果你希望得到元组,那么你可以使用 {{{phpformat}}} 中的 {{{dict_to_tuple}}} 方法来得到。
!!最简单的独立 PHPRPC 服务器
<code python>
from phprpc import PHPRPC_Server # 引入 PHPRPC Server
import datetime

def helloworld():
    return 'helloworld'

def hi(name):
    return 'hi %s' % name

server = PHPRPC_Server()
server.add(helloworld)
server.add('hi')
server.add(hi, 'hello')
server.add(datetime.datetime.now)
server.debug = True
server.start()
</code>

你会发现它跟Ruby、PHP 版本的服务器代码基本上一样。不是吗?

下面来看一下更通用的 {{{PHPRPC_WSGIApplication}}} 如何使用。

!!以 WSGI 方式发布 PHPRPC 服务

下面这个例子中我们使用一下 [[flup|http://trac.saddi.com/flup/wiki/FlupMiddleware]] 这个中间件,它提供了 Session 支持的能力(有了 Session 支持才能提供加密传输的能力)。当然你还可以使用其它的 WSGI 的 Session 中间件(比如 [[beaker|http://pypi.python.org/pypi/Beaker]] 或者其它兼容的 Session 中间件),这里就不在举例。

<code python>
from flup.middleware.session import MemorySessionStore, SessionMiddleware
from flup.middleware.gzip import GzipMiddleware
from phprpc import PHPRPC_WSGIApplication, UrlMapMiddleware, PHPRPC_Server
import datetime

def helloworld():
    return 'helloworld'

def hi(name):
    return 'hi %s' % name

app = PHPRPC_WSGIApplication()
app.add(helloworld)
app.add('hi')
app.add(hi, 'hello')
app.add(datetime.datetime.now)
app.debug = True

app = UrlMapMiddleware([('/', app)])

sessionStore = MemorySessionStore()
app = SessionMiddleware(sessionStore, app)

app = GzipMiddleware(app)

PHPRPC_Server(app = app).start()
</code>

上面这个 WSGI 的 app 最终还是使用 {{{PHPRPC_Server}}} 来发布的。但是因为这个 app 是个一个标准的 WSGI 应用程序,所以,你还可以用任何支持 WSGI 的服务器发布它,比如下面的 Google App Engine。

!!在 Google App Engine 上发布 PHPRPC 服务

<code python>
from phprpc import PHPRPC_WSGIApplication, UrlMapMiddleware
from google.appengine.ext.webapp.util import run_wsgi_app
import datetime

def helloworld():
    return 'helloworld'

def hi(name):
    return 'hi %s' % name

app = PHPRPC_WSGIApplication()
app.add(helloworld)
app.add('hi')
app.add(hi, 'hello')
app.add(datetime.datetime.now)
app.debug = True

app = UrlMapMiddleware([('/', app)])
run_wsgi_app(app)
</code>

这里有一点要注意,Google App Engine 实际上是以分布式的 CGI 方式来运行的,所以你不能使用 flup 的基于内存 Session 中间件,但你可以使用其它支持 Google App Engine 的 Session 中间件。

!!发布函数

如上面代码中所看到的,你可以如此发布函数
<code python>
app.add(helloworld)
app.add('hi')
</code>
直接用函数名或者函数名的字符串表示即可。

!! 发布方法
你可以发布系统内置对象的方法,也可以是自定义的类或对象的方法。

系统内置对象的方法:
<code python>
app.add(datetime.datetime.now)
</code>

自定义类的方法:
<code python>
class Test(object):
    def __init__(self):
        pass
    def hi(self, name):
        return 'hi : ' + name

test = Test()
app.add(test.hi)
</code>

除了可以一个一个的添加外,还可以把方法名、函数名(或其字符串表示)放到数组中,用 add 一次添加多个。例如:
<code python>
app.add([helloworld, "hi", datetime.datetime.now])
</code>

!!将函数或方法以别名方法发布

<code python>
app.add(hi, 'hello') # 将 hi 以别名 hello 发布
</code>

!!如何发布 lambda
<code python>
app.add(lambda x,y: x + y, 'add')
</code>

!!如何在发布的函数或方法中使用 Session

只要你发布的函数或方法的参数列表中,最后一个参数是 session 命名的参数,当客户端调用该函数或方法时,服务器就会自动将 Session 对象传入了。不过要注意,客户端不要传入这个 session 参数。

现在就开始和 PHPRPC 云端漫步吧!
直接从
{{{
http://www.phprpc.org/download/
}}}
或
{{{
http://pypi.python.org/pypi/phprpc/
}}}
下载 PHPRPC for Python 源码,之后解压将 py25 里的档案放置到项目中即可。
近期的 Web 开发领域出现一项开发利器 ~RoR(Ruby on Rails),但是 Rails 学习曲线及新旧版不兼容问题很令人头痛!!!
在此风潮下,顺势推出 PHPRPC for Ruby,现在不用学习 Rails 也可以使用 Ruby 开发 Web 了~
PHPRPC for Ruby 可以让你更开心,更快乐的编程。

*[[PHPRPC for Ruby 的安装]]
*[[PHPRPC for Ruby 服务器]]
*[[PHPRPC for Ruby 客户端]]
*[[PHPRPC for Ruby 常见问题解答]]
客户端使用更加简单,这里直接以代码实例进行说明:

!!客户端程序代码
<code ruby>
#!/usr/bin/env ruby

require 'rubygems'
require ‘phprpc’

rpc_url="http://bawbaw.icittys.com/rpc_server.php"
client = PHPRPC::Client.new(rpc_url) # 初始化一个PHPRPC client

puts client.add(1, 2) # 调用远程函数add(1,2)将打印出 3
puts client.sub(1, 2) # 调用远程函数sub(1,2)将打印出 -1
puts client.hello('Ma Bingyao') #调用远程函数 hello('Ma Bingyao') 将打印出 hello Ma Bingyao
</code>

!!加密传输
<code ruby>
#!/usr/bin/env ruby

require 'rubygems'
require ‘phprpc’

rpc_url="http://bawbaw.icittys.com/rpc_server.php"
client = PHPRPC::Client.new(rpc_url) #初始化一个PHPRPC client

client.keylength = 256 # 交换密钥长度 256
client.encryptmode = 2 # 双向加密

puts client.add(1, 2) # 调用远程函数 add(1,2) 将打印出 3
puts client.sub(1, 2) # 调用远程函数 sub(1,2) 将打印出 -1
puts client.hello('Ma Bingyao') # 调用远程函数 hello('Ma Bingyao') 将打印出 hello Ma Bingyao
</code>

keylength 用于设置[[密钥长度]]。
encryptmode 用于设置[[加密模式]]。

没看过这么简单的 Web Service 吧?安全又快速!
!!PHPRPC for Ruby 是否支持自定义类型?

支持,只要名称空间(模块或上层类名)、类名和实例变量定义可以与其它语言中定义的相对应,就可以跨语言传递。例如下面这段 ruby 程序
<code ruby>
module Test1
    class Test2
        class Test3
            attr_accessor :a, :b, :c
        end
    end
end
</code>
中的 {{{Test3}}} 与下面这段 Java 程序
<code java>
package Test1.Test2;

class Test3 implements java.io.Serializable {
    private int a;
    protected String b;
    public boolean c;

    Test3() {
        a = 1;
        b = "Hello";
        c = true;
    }
}
</code>
中的 {{{Test3}}} 就属于 PHPRPC 支持的可以跨语言传输的自定义数据类型。

上面你还会注意到,Java 中不管是私有成员、保护成员还是公共成员都是可以跟 Ruby 中的实例变量进行数据交换的。

当然,更复杂一些,即使名称空间和类名不相同,但是符合以下规则也可以进行跨语言传输:
如果将名称空间与类名之间的分隔符(例如 {{{.}}},{{{::}}} 等)全部替换为 {{{_}}} 之后,可以得到相同的名称,则这两个类型在 PHPRPC 中也被认为是可以跨语言传输的类型。

例如:
例如下面这段 ruby 程序
<code ruby>
module Test1
    class Test2
        class Test3
            attr_accessor :a, :b, :c
        end
    end
    class Test2_Test3
        attr_accessor :a, :b, :c
    end
end
</code>
中的 {{{Test3}}} 和 {{{Test2_Test3}}} 在通过 PHPRPC 传输时将被认为是相同的类型,因此在同一个程序中定义类名时要注意避免这种情况发生。

之所以这样设计是为了可以与不支持名称空间的语言进行交互,例如下面这段 ruby 程序
<code ruby>
module Test1
    class Test2
        class Test3
            attr_accessor :a, :b, :c
        end
    end
end
</code>
中的 {{{Test3}}} 是可以跟下面这段 PHP 程序
<code php>
class Test1_Test2_Test3 {
    var $a;
    var $b;
    var $c;
}
</code>
{{{Test1_Test2_Test3}}} 通过 PHPRPC 进行相互交换的。

!!PHPRPC for Ruby 如何与 Java 中实现了 org.phprpc.util.Serializable 接口的自定义类型进行交互?

目前 PHPRPC 所支持的语言中,对所有支持接口的语言都有类似于 Java 中 {{{org.phprpc.util.Serializable}}} 接口的机制。而 Ruby 语言本身并没有接口机制,那么是否意味着将不能与实现了这种机制的语言交换这种类型的数据了呢?

不是的,在 PHPRPC for Ruby 中你同样可以实现这种自定义序列化机制,只不过不再需要接口,而是利用鸭子类型的机制。也就是说,只要在你定义的类中,实现 {{{serialize}}} 和 {{{unserialize}}} 方法,就可以与其它实现了这种自定义序列化机制的语言进行这种类型的数据交互了。

同样,{{{__sleep}}} 和 {{{__wakeup}}} 方法的序列化机制也支持。

!!为何从客户端传来的数组参数在服务器端会变成 Hash 呢?
!!为何从服务器端返回的数组到客户端也会变成 Hash 呢?
!!我该如何让他们变回数组?

这三个问题实际上是同一个问题,因为 PHP 序列化会将数组和 Hash 序列化为同一种类型的数据表示,因此在反序列化时都会被反序列化为 Hash 类型的数据。对于其它强类型语言来说,可以根据参数类型或返回结果类型声明来自动进行类型转换。但是 Ruby 是弱类型语言,因此无法获知参数或结果是何种类型,因此只能以不丢失结果的 Hash 类型来返回。

如果你希望得到数组,那么你可以使用 Hash 对象的 values 方法来得到。
PHPRPC for Ruby 服务器支持 mongrel、thin、ebb 或 webrick 这四种独立服务器,同时还支持以 cgi、fcgi、scgi、lsapi 方式运行。但是服务器端程序的写法都是一样的,不同的仅仅是启动参数。所以这里主要以独立服务器方式运行来介绍。

Webrick 是 Ruby 自带的一个 Web 服务器,调试程序时可以使用它,但如果要在实际环境中部署则推荐 Mongrel 和 Thin 这两个 Web 服务器,虽然 ebb 服务器号称是最快的(比 Thin 还要快),但是目前的 ebb 还不够稳定。另外,在 Windows 下推荐 mongrel,Linux/Unix 下则推荐 Thin。因为 Thin 在 Windows 下也是不够稳定的。

如未安装 Mongrel 或 Thin 先安装(择一安装或两种都安装)
{{{
gem install mongrel
gem install thin
}}}

!!发布函数

实际上大部分函数都是可以作为 PHPRPC 服务发布的,甚至包括 Ruby 中的内置的函数。你可以同时发布多个函数,不论是你自定义的,还是 Ruby 内置的都可以。例如:

<code ruby>
#!/usr/bin/env ruby

require 'rubygems'
require 'phprpc'

def add(a, b)
a + b
end

def sub(a, b)
a - b
end

def hello(s)
"hello: " << s
end


server = PHPRPC::Server.new
server.debug = true
server.add(["add", "sub", "hello"]) #将上列的function发布
server.start
</code>

我们可以把方法名放到数组中,用 add 一次添加多个,也可以以字符串作为 add 方法的参数一个一个的添加。

!!如何启动 PHPRPC for Ruby 服务器

1、使用 Mongrel 发布 PHPRPC

{{{ruby server.rb mongrel}}}

2、使用 Thin 发布 PHPRPC

{{{ruby server.rb thin}}}

如何?很简单吧。

此时发布网址为 http://localhost:3000/

如果想要改变一些参数,请用

{{{ruby server.rb mongrel --help}}}
或
{{{ruby server.rb thin --help}}}

就可以知道如何改变及设定启动参数

除了提供基本函数发布外,也可以将类中的方法或者对象方法发布,另外还可以将函数或方法以别名方式发布。

!!将 function 以别名方法发布

假设要将 hello 以另一个名称 hihi 发布方法,就使用
<code ruby:nocontrols>
server.add("hello",nil,"hihi")
</code>

!!将类或对象方法发布

先写一个名为 Test 的 class:
<code ruby>
class Test
  def initialize()
  end
  def hi(str)
    "hi " << str
  end
  def time
    Time.now
  end
  def self.showtime
    'it is a show time'
  end
end
</code>

此时要将 Test 的 hi 和 time,showtime 发布很简单,看下面示例:
<code ruby>
server.add("time", Test.new)
server.add("hi", Test.new)
server.add("showtime", Test) #不用将Test实例化
server.add("hi", Test.new, 'hi2') #别名
</code>

!!如何发布 block 以及 lambda

直接可以发布区块的匿名方法,直观又不会命名冲突,是个不错的选择:
<code ruby:nocontrols>
  server.add("good") {|str| str<<" is good"}
</code>

假设有一个 lambda 是要计算两个数的总合:
<code ruby:nocontrols>
sum = lambda {|a,b| a+b}
</code>

此时要使用 PHPRPC for Ruby 发布只要使用:
<code ruby:nocontrols>
server.add("sum",nil,nil,&sum)
</code>

使用&符号在 sum 前头,这样子就可以发布了。

!!如何在发布的函数或方法中使用 Session

只要你发布的函数或方法的参数列表中,最后一个参数是 session 命名的参数,当客户端调用该函数或方法时,服务器就会自动将 Session 对象传入了。不过要注意,客户端不要传入这个 session 参数。

现在大家就动手测试吧!
从 Gem 安装,在命令行中输入:

{{{gem install phprpc}}}

如果你看到输出:

{{{
Successfully installed phprpc-3.0.4
1 gem installed
}}}

就说明已经安装好,可以用了!没错就是这么的简单!!
PHPRPC 的加密传输是依赖会话保存密钥的,因此会话对 PHPRPC 协议有着重要的作用。本章就来介绍 PHPRPC 协议中关于会话管理的内容。

!!依赖于 Cookie 的会话
PHPRPC 是以 HTTP 协议作为传输协议的,一般的 Web 服务器都提供依赖于 Cookie 的会话机制,即利用 Cookie 来保存和传递会话标示(~SessionID)的会话机制。因此不论是 PHPRPC 服务器还是客户端只要按照 HTTP 协议来处理 Cookie 即可使用依赖于 Cookie 的会话。

!!Cookie-less 会话
但是对于某些特殊的客户端来说,在某些情况下可能无法使用 Cookie。例如运行在浏览器中的 ~JavaScript 客户端会受到浏览器的限制,如果浏览器禁用了 Cookie```许多手机上的浏览器默认就是禁用 Cookie 的```,则 ~JavaScript 客户端就无法使用基于 Cookie 的会话了。在这种情况下,如果 PHPRPC 仅提供依赖于 Cookie 的会话机制,则无法满足这种特殊情况下的需要。

因此为了使 PHPRPC 的浏览器客户端在禁用 Cookie 的情况下仍然能够进行密钥交换和加密传输,PHPRPC 提供了对 Cookle-less 会话的支持。其实现过程是:

服务器在密钥交换第一阶段,如果检测到客户端可能不支持基于 Cookie 的会话(例如可以检测建立的是否是一个新的会话)时,在输出密钥交换参数({{{phprpc_encrypt}}})和密钥长度({{{phprpc_keylen}}})选项后,再增加一项输出 {{{phprpc_url}}},例如:
{{{
phprpc_url="http://localhost:8080/test/test.jsp;jsessionid=896CE4204292E70D28D27FE7C4503232";
}}}
其中 {{{phprpc_url}}} 的值是在原服务地址上增加了服务器会话标示(~SessionID)的地址。会话标示的附加方式由服务器端根据实际情况来决定,如果原客户端地址本身带有 url 请求参数,应该保留原有参数。例如,如果原服务器地址为 {{{http://localhost:8080/test/test.jsp?serverid=2342&action=do}}},则响应中的 phprpc_url 应为如下格式:
{{{
phprpc_url="http://localhost:8080/test/test.jsp;jsessionid=896CE4204292E70D28D27FE7C4503232?serverid=2342&action=do";
}}}
服务器仅应该过滤掉请求地址中以 {{{phprpc_}}} 开头的请求参数。另外,该值的编码方式与 {{{phprpc_errstr}}} 的编码方式相同,即由请求中 {{{phprpc_encode}}} 的值来决定。该响应仅在密钥交换第一阶段返回。

客户端在收到该响应后,应使用该地址替换原来的服务地址。之后所有的调用(包括密钥交换第二阶段)都使用新的地址,从而实现在 Cookie 被禁用的情况下,仍然可以使会话一直保持下去,保证密钥交换和加密传输顺利进行。

!!避免浏览器客户端之间的会话共享造成的密钥冲突
对于在浏览器中同一个页面上或者不同页面但在同一个会话中的多个客户端(例如 ~JavaScript 客户端、Flash 客户端、~SilverLight 客户端等)来说,如果使用加密传输,而没有特殊方法处理的话,会产生密钥冲突(即一个客户端的密钥会破坏掉另一个客户端的密钥)。为了避免这种情况的发生,故在请求中增加一个参数:{{{phprpc_id}}},该参数的值由客户端生成,用于唯一标示该客户端。服务器端根据该值来区别同一会话中的多个客户端。对于同一个客户端来说该参数在所有的请求中都必须存在,并保持一致。但对于非共享会话的其它客户端来说,请求中无需包含该参数。

该参数的值没有统一的生成规则,但下面是推荐的生成规则:

*首先以客户端类型开头,例如:~JavaScript 客户端可以以 js 开头,~SilverLight 客户端可以以 sl 开头。
*之后是时间戳的数字字符串表示。
*最后是随机整数,随机数取值范围越大越好,尽量保证不重复。
*三者之间用下划线分割。

这样基本上就可以保证标示的唯一性了。

服务器通过该标示来存取密钥交换信息和最终密钥。
!!服务器端发布的函数列表
PHPRPC 不同于 Web Service,它没有 WSDL 这样的服务定义描述文件,也不需要这样的东西。这样既方便了服务器的部署,又方便了弱类型与强类型语言之间的互通。但是你仍然可以得到服务器端发布的函数列表,但是这个函数列表中仅包含函数名,而不包含有函数参数与结果的描述,因为这样可以更方便的支持变参传递和弱类型传递。当客户端请求中不包含发起调用或密钥交换的必要参数时,则服务器端应返回发布的函数列表,其格式为:
{{{
phprpc_functions="<functions_list>";
}}}
其中<functions_list>为序列化并编码后的函数名数组。其编码规则取决于请求中 {{{phprpc_encode}}} 的值,如果请求中包含有 {{{phprpc_callback}}} 参数,也应按照调用时做同样处理。

!!版本号
PHPRPC 客户端可以通过 HTTP 头~User-Agent 来标示自己的版本。但服务器不应该将其作为识别客户端版本的依据。服务器端只需依据客户端的请求的行为来做成正确的响应即可。

PHPRPC 服务器通过 HTTP 头 ~X-Powered-By 来设置版本号,其格式为:PHPRPC Server/3.0。

客户端可以以此作为服务器版本的判断依据,但不应作为服务器端支持特征的判断依据。客户端应以服务器响应的行为作为服务器支持特性的判断依据,例如如果客户端发起密钥交换请求时,服务器端返回上面所提到的服务器端发布的函数列表,而不是返回用于生成密钥的信息,客户端便可以以此来判断服务器端不支持加密传输。

!!字符集
无论是客户端还是服务器端对字符串与字节数组之间的转换(例如 Java、.NET 等双字节字符编码的语言实现的服务器)都是以服务器端的字符集设置为准。客户端的请求中可以包含字符集设置,但是应与服务器端设置相同,在与服务器端设置不同的情况下,按照服务器端设置的字符集进行处理。客户端的字符集选项仅用于第一次与服务器通讯前,在客户端未知服务器字符集的情况下,让用户去设置与服务器端相同的字符集。另外,建议统一使用 ~UTF-8 字符集,在这种情况下,可以保证所有服务器与客户端正常通讯,如设置其它字符集,只能保证两个相同字符集设置的服务器和客户端可以正常通讯。

字符集通过 HTTP 头 ~Content-Type 来设置。

!!跨域存取 Cookie
确切的说,这不是 PHPRPC 协议范围中的内容。但却对 PHPRPC 实现跨域访问很有必要。对于非 IE 浏览器来说,跨域存取 Cookie 是没有问题了,但是对于 IE 浏览器来说,需要服务器端发送:
{{{
P3P: CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV"
}}}
这样一个 HTTP 头才可以允许客户端接收跨域的 Cookie。因此,在实现 PHPRPC 服务器时,建议服务器端发送该头。

!!缓存
PHPRPC 是不允许缓存的,因为远程过程调用的结果是未知可变的,也就是说远程过程调用不能保证是幂等性(idempotency)操作,大多数情况下都是非幂等性操作的。

因此 PHPRPC 建议服务器在输出响应时,应该发送禁止缓存的头信息,例如:
{{{
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
}}}
!!加密调用

前面所描述的是在非加密调用情况下的请求和响应,而在加密调用的情况下,则请求中的 {{{phprpc_args}}} 会在序列化之后,先进行加密,再进行编码,即:B(C(S(参数数组))):其中 B 为 Base64 编码,C 为加密操作,S 为 [[PHP 序列化]]。

响应中的某些信息在加密调用的情况下也会增加加密操作,但是究竟要对那些信息进行加密是根据请求中的 {{{phprpc_encrypt}}} 参数决定的。{{{phprpc_encrypt}}} 表示加密模式。

<<tiddler [[加密模式]]>>

默认值为 0,也就是非加密调用。

当 {{{phprpc_encrypt}}} 为 1 时,响应中的 {{{phprpc_args}}} 在序列化之后,先进行加密,再进行编码,即:E(C(S(参数数组))),其中 E 为编码操作(Base64 或 ~JavaScript 字符串编码),C 为加密操作,S 为 [[PHP 序列化]]。

当 {{{phprpc_encrypt}}} 为 2 时,响应中的 {{{phprpc_args}}} 同上。响应中的 {{{phprpc_result}}} 也同上,即:E(C(S(结果值))),E,C,S 含义同上。

当 {{{phprpc_encrypt}}} 为 3 时,响应中的 {{{phprpc_args}}} 和 {{{phprpc_result}}} 同上。响应中的 {{{phprpc_output}}} 先加密,再编码,即:E(C(输出信息)),E,C 含义同上。

这里的加密操作使用的是 [[XXTEA 加密算法]]。

!!密钥交换第一阶段

但加密操作的前提是要有密钥,因此当我们开始第一次加密调用之前,还需要有一个生成密钥的过程。在 PHPRPC 协议中,密钥生成是通过 [[Diffie-Hellman 密钥交换算法]]完成的。下面是 DH 密钥交换算法在 PHPRPC 协议中实现的过程:

当客户端要发起一个加密调用时,如果还没有生成密钥,则首先向服务器发起一个密钥交换的请求,请求中最主要的参数也是 {{{phprpc_encrypt}}},它的值为 true。如果服务器端支持加密传输,则返回用于密钥交换的信息:
{{{
phprpc_encrypt="<info>";
}}}
其中 <info> 为包含了 [[Diffie-Hellman 密钥交换算法]]中的 p、g、Ya 的三个元素的数组,它们的下标分别为 p、g、y,其值为由十进制数字组成的字符串。该数组也是通过序列化并编码的方式表示的,编码方式也由请求中的 {{{phprpc_encode}}} 的值所决定。

你还可以指定以下三个可选参数:

其中 {{{phprpc_encode}}} 在上面刚刚提到,这里就不在重复。

{{{phprpc_callback}}} 参数同远程过程调用时的作用一样,也是为了让 ~JavaScript 客户端可以进行回调操作,所以服务器端的对该参数的处理方式是相同的。

{{{phprpc_encode}}} 和 {{{phprpc_callback}}} 在密钥交换第一阶段和第二阶段都会出现。

{{{phprpc_keylen}}} 参数仅在密钥交换的第一阶段出现,它表示密钥交换的位数,默认为 128。例如:{{{phprpc_keylen=512}}} 表示客户端要求与服务器端进行 512 位的密钥交换。如果服务器支持客户端所指定的位数,则进行密钥交换时,除了在响应中返回用于密钥交换的数据外,还要在响应中返回相同的[[密钥长度]]参数,但格式按照响应的格式返回,例如:
{{{
phprpc_keylen="512";
}}}
如果服务器端不支持客户端所指定的位数,则选择支持的最接近客户端所指定的位数进行密钥交换,并且在响应中给出实际的位数,例如:
{{{
phprpc_keylen="160";
}}}
如果服务器端使用 128 位来进行密钥交换,则响应中可以不给出密钥交换的位数,客户端也应该按照 128 位来进行处理。

!!密钥交换第二阶段

当客户端收到第一阶段服务器的响应后,根据 p、g、y 及客户端生成的随机数 Xb 来生成 Yb 和密钥 k。Yb 的值(由十进制数字组成的字符串)直接作为密钥交换第二阶段请求的 {{{phprpc_encrypt}}} 参数值进行提交。

当服务器接收到密钥交换第二阶段请求时,根据请求中 {{{phprpc_encrypt}}} 所包含的 Yb 和服务器端保存的 Xa```Xa 通常保存在会话(session)当中,关于会话管理的内容将在下一章详细描述。```来生成密钥 k‘。如果密钥交换成功,则客户端的密钥 k 将与服务器端的密钥 k' 完全相同。

密钥交换成功的响应信息中仅包含请求中 {{{phprpc_callback}}} 解码后内容。如果密钥交换失败(例如服务器端会话丢失),则返回 {{{phprpc_errno}}},{{{phprpc_errstr}}} 这两项并在最后附加请求中 {{{phprpc_callback}}} 解码后内容,其中 {{{phprpc_errstr}}} 的编码取决于请求中 {{{phprpc_encode}}}  的值。

但目前已有的客户端实现并不会理会这次请求后服务器的应答信息,即使服务器端密钥生成失败。因为在紧接着发起的加密调用中,会因为这次的密钥交换失败而导致调用的失败,客户端实现将在那时再处理错误。

!!密钥生成

前面的密钥交换算法生成的密钥需要做一些转化后才能生成最终用于 [[XXTEA 加密算法]] 的密钥,下面是用于加密解密的密钥生成的规则:

如果密钥交换采用的是 128 位(默认长度),则交换后的密钥(长整数)按照小端字节序(little endian byte order)转化为字节数组,如果字节数组长度不足 16 个字节,则在前面补 \0,直到长度等于 16 字节。

如果密钥交换采用其它长度,将交换后的密钥首先转化为 十进制数字字符串,然后对其进行 ~MD5 操作生成 16 字节的字节数组。

通过上面的方法,就得到了用于 [[XXTEA 加密算法]] 的密钥。服务器端和客户端都采用上述方法来生成密钥。之后就可以进行加密传输了。
PHPRPC 是一个的远程过程调用协议。你可以认为它类似于 [[XMLRPC|http://www.xmlrpc.com/]],但是它们的工作方式有很大的不同。

PHPRPC 3.0 是使用 HTTP 协议作为传输协议的。以后的版本可能会提供更多传输协议的绑定(如 TCP、UDP 等),但 3.0 及其以前的版本只提供了 HTTP 传输绑定的工作方式。

以下所介绍的内容全部是针对 PHPRPC 3.0 协议的,所以不再单独指明版本号。

PHPRPC 仅使用 HTTP 的 GET 和 POST 两种请求方式,不对 HTTP 协议的其它请求方式(如 HEAD、PUT、DELETE 等)提供支持。

PHPRPC 推荐的请求方式是 POST,仅在无法使用 POST 进行请求操作时,才应该考虑使用 GET。因为 PHPRPC 请求不保证是幂等性(idempotency)操作,使用 GET 请求做非幂等性操作,会破坏 GET 的原始语义。尽管在 PHPRPC 中,HTTP 协议仅作为传输协议使用,它的原始语义并不是最重要的,但如果在能够满足它原始语义的条件下,最好还是不要破坏。

远程过程调用中,参数和返回结果是通过 [[PHP 序列化]]形式表示的。传输编码采用 Base64 或 ~JavaScript 字符串方式编码。密钥交换采用 [[Diffie-Hellman 密钥交换算法]]。数据加密采用 [[XXTEA 加密算法]]。

请求的格式采用标准的 application/x-www-form-urlencoded 格式,因为这种格式对于任何 HTTP 客户端和服务器来说都是容易构造和解析的,且可以保持 GET 和 POST 请求的构造和解析的一致性。

响应的格式采用纯文本,所以服务器可以设置响应的 ~Content-Type 为 text/plain,并设置明确的字符集。

响应格式类似于多行 ~JavaScript 赋值语句,例如:
{{{
phprpc_result="b:1;";
phprpc_errno="0";
phprpc_errstr="";
phprpc_output="";
}}}

唯一的例外是,如果请求中包含 phprpc_callback 参数,则响应的尾部会附加上 phprpc_callback 解码后的值。但此参数仅在 ~JavaScript 客户端中使用。

这种响应格式为 ~JavaScript 客户端实现跨域调用提供了必要的基础。

对于请求和响应的细节,我们在后面章节中再详细说明。
!!序列化与编码

远程过程调用当中,我们需要传递的参数和得到的返回值可能是整数、浮点数、字符串这样的基本类型数据,也可能是数组、字典或者自定义对象这样的复合类型数据。要想将这些数据通过网络传输,就需要将它们序列化为一个文本或者字节流。序列化的方式多种多样,比如 XML 方式```XMLRPC、SOAP、Burlap 等都是采用 XML,不过它们所定义的 XML 格式是不同的。```,JSON 方式,二进制字节流形式``` Java RMI、.NET Remoting、Hessian 等都是采用二进制字节流,不过它们对二进制字节流定义的格式也不同。```。

PHPRPC 采用的序列化方式是 [[PHP 序列化]]。你可以认为它是一种半文本格式,因为文本和二进制字节流的特征它都具有。比如表示数字时,它采用的是数字的文本格式,而不是二进制的机器存储格式,这样它就可以在不同的平台上传递而无须担心字节序引起的解析问题。而它又像二进制字节流那样不允许在数据当中插入多余的空白(空格、回车、换行等字符),并且对于字符串、数组、字典、对象还有长度描述信息。这些特征决定了它既有纯文本格式的通用性,又具有二进制字节流的快速解析能力。

PHPRPC 是以 HTTP 协议作为底层传输协议并且以 application/x-www-form-urlencoded 格式提交请求的,而传输的内容在序列化甚至再加密之后将会包含有很多需要 urlencode 的特殊字符,这样编码之后的长度将会增加 2 - 3 倍,并且还可能因为字符集的问题,造成在服务器无法正确解析。因此,我们在进行 urlencode 之前,先对序列化或加密后的数据进行 Base64 编码,然后再将 Base64 编码之后的数据中的 {{{+}}} 替换为 {{{%2B}}} 即可完成 urlencode。经过这种变化后,长度仅增加 1/3,并且可以避免字符集引起的解析问题。

同样为了避免因为字符集而可能引起的对序列化数据中二进制字符串和加密内容解析错误,对于响应中的需要序列化或加密的内容,我们也要做 Base64 编码。

不过虽然请求中数据编码必须使用 Base64 方式,但是作为响应内容,PHPRPC 除了提供 Base64 编码方式以外,还提供了另外一种编码方式,那就是 ~JavaScript 字符串编码方式。这种编码类似于 C 语言的字符转义表示,比如:{{{\r}}} 表示回车,{{{\0x12}}} 表示 ASCII 码值为 18(十六进制为 12)的字符,{{{\012}}} 表示 ASCII 码值为 10(八进制为 12)的字符。这种编码格式对于 ~JavaScript 客户端来说无需再另外解析。因此这种格式可以大大加快 ~JavaScript 客户端处理响应的速度。由此可见,PHPRPC 协议本身已经针对 ~JavaScript 客户端做了充分优化。

下面我们来具体说明一下在基本调用中的请求参数和响应内容。

!!请求参数

调用中的请求参数共有 7 个,其中只有 1 个是必须的,其它 6 个都是可选的。它们是:{{{phprpc_func}}},{{{phprpc_args}}},{{{phprpc_callback}}},{{{phprpc_encode}}},{{{phprpc_ref}}},{{{phprpc_encrypt}}} 和 {{{phprpc_id}}}。其中 {{{phprpc_encrypt}}} 跟加密传输有关,{{{phprpc_id}}} 跟会话管理有关,我们后面在相关章节再介绍它们。这里我们只介绍前 5 个。

|!参数名称|!取值|!默认值|!说明|
|{{{phprpc_func}}}| 字符串 | 无 |该值为要调用的远程函数名,函数名为明文字符串,无大小写区分(建议用小写),函数名后不跟括号。|
|{{{phprpc_args}}}| B(S(数组)) | B(S(空数组)) |表示所调用的远程函数所需的参数,参数以序列化并 Base64 编码的数组格式表示,第一个参数的数组下标为 0,第二个参数的数组下标为 1,以此类推。|
|{{{phprpc_callback}}}| B(字符串) | 空字符串 |仅用于 ~JavaScript 客户端,表示远程过程调用后,客户端执行的 ~JavaScript 语句。其值为经过 Base64 编码的 ~JavaScript 语句。|
|{{{phprpc_encode}}}| 布尔 | true |仅用于 ~JavaScript 客户端,表示远程过程调用后,响应内容是否进行 Base64 编码。true 为 Base64 编码,false 为 ~JavaScript 字符串编码。|
|{{{phprpc_ref}}}| 布尔 | true |表示是否引用参数传递。设为 true 时,响应中包含远程过程调用后的参数信息,设为 false 时不包含。|

上表中的 {{{B()}}} 表示 Base64 编码,{{{S()}}} 表示 [[PHP 序列化]]。布尔值为 true 或 false 的字符串形式。

{{{phprpc_func}}} 是必须的。可选项缺省时,服务器端应按照它们的默认值处理。

!!响应内容

调用中的响应包含有五项,它们分别是:{{{phprpc_result}}},{{{phprpc_args}}},{{{phprpc_errno}}},{{{phprpc_errstr}}},{{{phprpc_output}}}。

|{{{phprpc_result}}}|远程过程调用的返回结果,结果首先被序列化,之后以请求中 {{{phprpc_encode}}} 指定的方式来进行编码。|
|{{{phprpc_args}}}|远程过程调用的参数,参数在调用过程中可能会被修改,该响应用来返回修改后的参数值。响应中的参数与请求中的参数一样,都是数组,该数组首先被序列化,之后以请求中 {{{phprpc_encode}}} 指定的方式来进行编码。如果请求中 {{{phprpc_ref}}} 被设置为 false,则响应中不包含该项。|
|{{{phprpc_errno}}}|远程过程调用中的错误码,没有错误时为 0。发生错误时为非 0,具体错误号跟服务器端的语言相关,PHPRPC 协议中不做规定。因为错误号为纯数字,因此不对其做序列化处理,也不进行编码。|
|{{{phprpc_errstr}}}|远程过程调用中的错误信息,没有错误时为空字符串。发生错误时为错误信息,具体错误信息跟服务器端的语言相关,PHPRPC 协议中不做规定。错误信息不做序列化处理,但会以请求中 {{{phprpc_encode}}} 指定的方式来进行编码。|
|{{{phprpc_output}}}|远程过程调用中产生的输出信息,没有输出时为空字符串。输出信息不做序列化处理,但会以请求中 {{{phprpc_encode}}} 指定的方式来进行编码。|

当调用过程发生致命错误(非警告错误)时,响应中不包含 {{{phprpc_result}}} 和 {{{phprpc_args}}} 这两项。

另外,如果请求中包含 {{{phprpc_callback}}} 参数,则响应的尾部会附加上 {{{phprpc_callback}}} 解码后的值。
你可以直接从本站的[[下载页|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 就是你的最佳选择。
RawByteString 是在 Delphi 2009 中引入的类型,它与之前版本的 ~AnsiString 同义。在 Delphi 2009 中它的定义为:
<code delphi>
type
  RawByteString = type AnsiString($FFFF);
</code>

为了与 Delphi 2009 程序兼容,我们在 PHPRPC for Delphi 中为 Delphi 2009 之前的版本也引入了这一定义:
<code delphi>
type
{$IFNDEF DELPHI2009}
  RawByteString = type AnsiString;
{$ENDIF}
</code>

这样,就可以无差别的使用二进制字符串了。
<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();
}
//}}}
TArrayList 定义如下:
<code delphi>
  TArrayList = class(TPHPObject)
  private
    FCount: Integer;
    FCapacity: Integer;
    FList: TVariantDynArray;
  protected
    function DoFunction(var Dest: TVarData; const Name: string;
      const Arguments: TVarDataArray): Boolean; override;
    function DoProcedure(const Name: string;
      const Arguments: TVarDataArray): Boolean; override;
    function DoSerialize(const Buffer: TStringBuffer = nil;
      const ObjectContainer: TArrayList = nil): RawByteString; override;
    procedure DoUnSerialize(const Buffer: TStringBuffer;
      const ObjectContainer: TArrayList; StringAsByteArray: Boolean); override;
    function Get(Index: Integer): Variant; virtual;
    function GetList: TVariantDynArray; virtual;
    procedure Grow; virtual;
    procedure Put(Index: Integer; const Value: Variant); virtual;
    procedure SetCapacity(NewCapacity: Integer); virtual;
    procedure SetCount(NewCount: Integer); virtual;
    procedure SetList(const Value: TVariantDynArray); virtual;
  public
    constructor Create; overload; override;
    constructor Create(AOwner: TComponent); overload; override;
    constructor Create(Capacity: Integer; AOwner: TComponent = nil); reintroduce; overload; virtual;
    constructor Create(const ArrayList: TArrayList; AOwner: TComponent = nil); reintroduce; overload; virtual;
    destructor Destroy; override;
    class function New(Capacity: Integer): Variant; overload;
    class function New(const ArrayList: TArrayList): Variant; overload;
    function Add(const Value: Variant): Integer; virtual;
    procedure AddAll(const ArrayList: TArrayList); overload; virtual;
    procedure AddAll(const Container: Variant); overload; virtual;
    procedure Clear; virtual;
    function Contains(const Value: Variant): Boolean; virtual;
    function Delete(Index: Integer): Variant; virtual;
    procedure Exchange(Index1, Index2: Integer); virtual;
    function IndexOf(const Value: Variant): Integer; virtual;
    procedure Insert(Index: Integer; const Value: Variant); virtual;
    procedure Move(CurIndex, NewIndex: Integer); virtual;
    function Remove(const Value: Variant): Integer; virtual;
    property Items[Index: Integer]: Variant read Get write Put; default;
  published
    property Count: Integer read FCount write SetCount;
    property Capacity: Integer read FCapacity write SetCapacity;
    property List: TVariantDynArray read GetList write SetList;
  end;
</code>
THashMap 定义如下:
<code delphi>
  THashMap = class(TPHPObject)
  private
    FRefCount: Integer;
    FKeys: TArrayList;
    FValues: TArrayList;
    function GetCount: Integer;
  protected
    function DoFunction(var Dest: TVarData; const Name: string;
      const Arguments: TVarDataArray): Boolean; override;
    function DoProcedure(const Name: string;
      const Arguments: TVarDataArray): Boolean; override;
    function DoSerialize(const Buffer: TStringBuffer = nil;
      const ObjectContainer: TArrayList = nil): RawByteString; override;
    procedure DoUnSerialize(const Buffer: TStringBuffer;
      const ObjectContainer: TArrayList; StringAsByteArray: Boolean); override;
    function Get(const Key: Variant): Variant; virtual;
    procedure Put(const Key, Value: Variant); virtual;
  public
    procedure AfterConstruction; override;
    constructor Create; overload; override;
    constructor Create(AOwner: TComponent); overload; override;
    constructor Create(Capacity: Integer; AOwner: TComponent = nil); reintroduce; overload; virtual;
    constructor Create(const ArrayList: TArrayList; AOwner: TComponent = nil); reintroduce; overload; virtual;
    constructor Create(const HashMap: THashMap; AOwner: TComponent = nil); reintroduce; overload; virtual;
    constructor Create(const Container: Variant; AOwner: TComponent = nil); reintroduce; overload; virtual;
    destructor Destroy; override;
    procedure Clear; virtual;
    function ContainsKey(const Key: Variant): Boolean; virtual;
    function ContainsValue(const Value: Variant): Boolean; virtual;
    class function New(Capacity: Integer): Variant; overload;
    class function New(const ArrayList: TArrayList): Variant; overload;
    class function New(const HashMap: THashMap): Variant; overload;
    class function New(const Container: Variant): Variant; overload;
    procedure PutAll(const ArrayList: TArrayList); overload; virtual;
    procedure PutAll(const HashMap: THashMap); overload; virtual;
    procedure PutAll(const Container: Variant); overload; virtual;
    function Delete(const Key: Variant): Variant; virtual;
    function ToArrayList: TArrayList; virtual;
    property Items[const Key: Variant]: Variant read Get write Put; default;
  published
    property Count: Integer read GetCount;
    property Keys: TArrayList read FKeys;
    property Values: TArrayList read FValues;
  end;
</code>
THashedArrayList 定义如下:
<code delphi>
  THashedArrayList = class(TArrayList)
  private
    FHashBucket: THashBucket;
    function IndexCompare(Index: Integer; const Value: Variant): Boolean;
  protected
    procedure Put(Index: Integer; const Value: Variant); override;
    procedure SetList(const Value: TVariantDynArray); override;
  public
    constructor Create(Capacity: Integer; AOwner: TComponent = nil); overload; override;
    constructor Create(const ArrayList: TArrayList; AOwner: TComponent = nil); overload; override;
    destructor Destroy; override;
    function Add(const Value: Variant): Integer; override;
    procedure Clear; override;
    function Delete(Index: Integer): Variant; override;
    procedure Exchange(Index1, Index2: Integer); override;
    function IndexOf(const Value: Variant): Integer; override;
    procedure Insert(Index: Integer; const Value: Variant); override;
  end;
</code>
TPHPObject 定义如下:
<code delphi>
  {$M+}
  TPHPObject = class(TComponent)
  private
    function GetProp(const Name: String): Variant;
    procedure SetProp(const Name: String; const Value: Variant);
  protected
    function DoFunction(var Dest: TVarData; const Name: string;
      const Arguments: TVarDataArray): Boolean; virtual;
    function DoProcedure(const Name: string;
      const Arguments: TVarDataArray): Boolean; virtual;
    function DoSerialize(const Buffer: TStringBuffer = nil;
      const ObjectContainer: TArrayList = nil): RawByteString; virtual;
    procedure DoUnSerialize(const Buffer: TStringBuffer;
      const ObjectContainer: TArrayList; StringAsByteArray: Boolean); virtual;
    function GetProperty(var Dest: TVarData;
      const Name: string): Boolean; virtual;
    function SetProperty(const Name: string;
      const Value: TVarData): Boolean; virtual;
    function __sleep: TStringDynArray; virtual;
    procedure __wakeup; virtual;
    function ToBoolean: Boolean; virtual;
    function ToDate: TDateTime; virtual;
    function ToDouble: Double; virtual;
    function ToInt64: Int64; virtual;
    function ToInteger: Integer; virtual;
  public
    function Equal(const Right: TPHPObject): Boolean; overload; virtual;
    class function Equal(const Left, Right: Variant): Boolean; overload;
    constructor Create; reintroduce; overload; virtual;
    class function New: Variant;
    class function FromVariant(const V: Variant): TPHPObject;
    class function AliasName: string;
    class function GetClass(const AliasName: string): TPHPClass;
    class procedure RegisterClass(const AliasName: string = '');
    procedure MoveComponentsTo(AComponent: TComponent);
    function HashCode: Integer; virtual;
    function ToString: string; {$IFDEF DELPHI2009}override{$ELSE}virtual{$ENDIF};
    function ToVariant: Variant; virtual;
    property Properties[const Name: string]: Variant read GetProp write SetProp; default;
  end;
  {$M-}
</code>
TPHPRPC_Client 的定义如下:

<code delphi>
  TPHPRPC_Client = class(TPHPObject)
  private
    FIdHTTP: TIdHTTP;
    FURL: string;
    FKey: RawByteString;
    FKeyLength: Integer;
    FEncryptMode: Integer;
    FCharset: string;
    FOutput: RawByteString;
    FWarning: TPHPRPC_Error;
    FVersion: Currency;
    FStringAsByteArray: Boolean;
    procedure SetKeyLength(Value: Integer);
    procedure SetEncryptMode(Value: Integer);
    procedure SetCharset(const Value: string);
    function GetProxy: TIdProxyConnectionInfo;
    procedure SetProxy(const Value: TIdProxyConnectionInfo);
    function GetTimeout: Integer;
    procedure SetTimeout(const Value: Integer);
    procedure SetURL(const Value: string);
    procedure KeyExchange;
    function Post(const ReqStr: RawByteString): THashMap;
    function Decrypt(const Data: RawByteString; Level: Integer): RawByteString;
    function Encrypt(const Data: RawByteString; Level: Integer): RawByteString;
  protected
    function DoFunction(var Dest: TVarData; const Name: string;
      const Arguments: TVarDataArray): Boolean; override;
  public
    constructor Create(); overload; override;
    constructor Create(AOwner: TComponent); overload; override;
    constructor Create(const AURL: string; AOwner: TComponent = nil); reintroduce; overload;
    destructor Destroy; override;
    function UseService(const AURL: string): Variant; overload;
    function Invoke(const FuncName: string; const Args: TVariantDynArray; byRef: boolean = False): Variant;
    property Output: RawByteString read FOutput;
    property Warning: TPHPRPC_Error read FWarning;
  published
    property KeyLength: Integer read FKeyLength write SetKeyLength default 128;
    property EncryptMode: Integer read FEncryptMode write SetEncryptMode default 0;
    property Charset: string read FCharset write SetCharset;
    property Proxy: TIdProxyConnectionInfo read GetProxy write SetProxy;
    property Timeout: Integer read GetTimeout write SetTimeout;
    property URL: string read FURL write SetURL;
    property StringAsByteArray: Boolean read FStringAsByteArray write FStringAsByteArray default False;
  end;
</code>
TStringBuffer 定义如下:
<code delphi>
  TStringBuffer = class(TPHPObject)
  private
    FDataString: RawByteString;
    FPosition: Integer;
    FCapacity: Integer;
    FLength: Integer;
    procedure Grow;
    procedure SetPosition(NewPosition: Integer);
    procedure SetCapacity(NewCapacity: Integer);
  protected
    function DoFunction(var Dest: TVarData; const Name: string;
      const Arguments: TVarDataArray): Boolean; override;
    function DoProcedure(const Name: string;
      const Arguments: TVarDataArray): Boolean; override;
    function DoSerialize(const Buffer: TStringBuffer = nil;
      const ObjectContainer: TArrayList = nil): RawByteString; override;
    procedure DoUnSerialize(const Buffer: TStringBuffer;
      const ObjectContainer: TArrayList; StringAsByteArray: Boolean); override;
  public
    constructor Create; overload; override;
    constructor Create(Capacity: Integer); reintroduce; overload;
    constructor Create(const AString: RawByteString); reintroduce; overload;
    class function New(Capacity: Integer): Variant; overload;
    class function New(const AString: RawByteString): Variant; overload;
    function Read(var Buffer; Count: Longint): Longint;
    function ReadString(Count: Longint): RawByteString;
    function Write(const Buffer; Count: Longint): Longint;
    procedure WriteString(const AString: RawByteString);
    function Insert(const Buffer; Count: Longint): Longint;
    procedure InsertString(const AString: RawByteString);
    function Seek(Offset: Longint; Origin: Word): Longint;
    function ToString: string; override;
    {$IFDEF DELPHI2009}
    function ToRawByteString: RawByteString;
    {$ENDIF}
  published
    property Position: Integer read FPosition write SetPosition;
    property Length: Integer read FLength;
    property Capacity: Integer read FCapacity write SetCapacity;
    property DataString: RawByteString read FDataString;
  end;
</code>
TVariantDynArray 定义如下:
<code delphi>
type
  TVariantDynArray = array of Variant;
</code>
XXTEA 加密算法是微型加密算法(TEA)目前最为安全的一个变种。

TEA 及其相关变种(XTEA,Block TEA,XXTEA) 都是分组加密算法,它们很容易被描述,实现也很简单(典型的几行代码)。

TEA 算法最初是由剑桥计算机实验室的 David Wheeler 和 Roger Needham 在 1994 年设计的。该算法使用 128 位的密钥为 64 位的信息块进行加密,它需要进行 64 轮迭代,尽管作者认为 32 轮已经足够了。该算法使用了一个神秘常数(Magic Number)&delta; 作为倍数,它来源于黄金比率,以保证每一轮加密都不相同。但 &delta; 的精确值似乎并不重要,TEA 把它定义为 <html>&delta;=「(&radic;<span style="text-decoration: overline;">5</span> - 1)2<sup>31</sup>」</html>(具体数值为 0x9E3779B9)。

之后 TEA 算法被发现存在缺陷,作为回应,设计者提出了一个 TEA 的升级版本——XTEA(有时也被称为“tean”)。XTEA 跟 TEA 使用了相同的简单运算,但它采用了截然不同的顺序,为了阻止密钥表攻击,四个子密钥(在加密过程中,原 128 位的密钥被拆分为 4 个 32 位的子密钥)采用了一种非正规的方式进行混合,但速度较之 XTEA 略慢。

在与描述 XTEA 算法的同一份报告中,还介绍了另外一种被称为 Block TEA 算法的变种,它可以对 32 位大小任意倍数的变量块进行操作。该算法将 XTEA 轮循函数依次应用于块中的每个字,并且将它附加于它的邻字。该操作重复多少轮依赖于块的大小,但至少需要 6 轮。该方法的优势在于它无需操作模式(CBC,OFB,CFB 等),密钥可直接用于信息。对于长的信息它可能比 XTEA 更有效率。

在 1998 年,~Markku-Juhani Saarinen 给出了一个可有效攻击 Block TEA 算法的代码,但之后很快 David J. Wheeler 和 Roger M. Needham 就给出了 Block TEA 算法的修订版,这个算法被称为 [[XXTEA|http://www.cix.co.uk/~klockstone/xxtea.pdf]]。XXTEA 使用跟 Block TEA 相似的结构,但在处理块中每个字时利用了相邻字。它利用一个更复杂的 MX 函数代替了 XTEA 轮循函数,MX 使用了 2 个输入量。

XXTEA 算法的具体描述可以参见上面链接的文档,也可以参见 PHPRPC 已有实现中的代码,这里不再单独给出。

但是 XXTEA 算法只定义了如何对 32 位的信息块数组(实际上是 32 位无符号整数数组)进行加密,而并没有定义二进制字节流的加密方法,因此我们需要定义一个将二进制字节流安全转换为 XXTEA 算法支持的 32 位无符号整数数组的方法。这里所说的安全是指能够完整的相互转换,而不仅仅是单向转换。

XXTEA 算法中,要加密的数据的 32 位无符号整数数组至少要有 2 个元素,作为密钥的 32 位无符号整形数组为 4 个元素。因此,我们将要加密的二进制字节流转为 32 位无符号整数数组时,首先记录下原二进制字节流的长度 L,该长度用一个 32 位无符号整型数表示。之后通过在二进制字节流尾部添加 \0(ASCII 码值为 0 的字节)的方式将长度补齐到 4 的倍数,接下来按照小端字节序(little endian byte order)方式转换为无符号整数数组,并保证数组的长度 La 与充填后二进制字节流的长度 L' 满足以下关系:
{{{
La = (L' / 4) + 1
}}}
最后,将原二进制字节流的长度值 L,充填到数组的最后一个元素中(按照上面的方法生成的数组,最后正好会保留一个用于充填该值的元素)。

通过这种方式,只要二进制字节流不为空,则至少会生成 2 个元素的 32 位无符号整数数组。而如果为空,则不进行上述操作,直接退出,也就是不对空数据进行加密。

因为数据中包含了长度信息,因此解密后,可以根据长度信息还原为原始长度的二进制字节流。并且还可以根据解密后最后一个元素的信息来大致判断是否解密成功。

因为密钥不涉及到解密还原的问题,因此对密钥的转换我们采用直接补齐的到 16 个字节,然后按照小端字节序(little endian byte order)方式转换为无符号整数数组,即可得到含有 4 个元素的 32 位无符号整数数组。

在实现 XXTEA 算法时,密钥也是采用在二进制字节流''尾部''添加 \0 的方式补齐。但是要注意的是在 PHPRPC 中,调用 XXTEA 算法前,必须首先将密钥通过''开头''补 \0 的方式充填到 16 个字节。因此,在 XXTEA 算法中密钥''尾部''补 \0 对 PHPRPC 来说没有作用,但在实现 XXTEA 算法时,推荐仍然按照''尾部''补 \0 方式实现,以便于在非 PHPRPC 应用中使用 XXTEA 算法加密数据时也可以正常交互。
//{{{
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/]] 浏览。@@

你可以免费在线阅读或者[[下载|http://www.phprpc.org/download/phprpc_3.0_docs.zip]]阅读本指南。
未经作者明确授权,禁止发行本指南及其修改版本。
未经作者事先授权,禁止将本作品及其衍生物以标准(纸质)书籍形式发行。
未经作者允许,禁止在学术论文、教材或专著中引用。
如果有兴趣再发行或再版本指南的全部或部分内容,不论修改与否,都请事先联系本指南作者(同时也是版权所有者) andotcn@msn.com。
对 PHPRPC 有任何问题或建议,请进入[[官方论坛|http://www.phprpc.org/forum]]发布相关讨论。
感谢所有对 PHPRPC 关注和支持的朋友!
PHPRPC 将调用加密分为四种模式(或者叫做级别也可以),它们分别用 0 - 3 这四个整型数字表示。

0 表示不加密传输。
1 表示调用的参数加密传输(不管是传到服务器端还是从服务器端传回,都是加密的)。
2 表示调用的参数和服务器端方法返回的结果加密传输。
3 表示调用的参数、服务器端方法返回的结果以及服务器端的输出重定向内容都加密传输。
字符串类型序列化方式分为三种:

# 非转义二进制字符串序列化
# 转义二进制字符串序列化
# Unicode 字符串序列化

其中第一种是 PHP 4、PHP 5 提供的序列化方式,也是最好的序列化方式,因为它可以非常快速的构造和解析,占用的空间也最少。后两种是 PHP 6 中将要引进的序列化方式,其中转义二进制字符串序列化是用来取代  PHP 4、PHP 5 非转义二进制字符串序列化的。但该序列化方式不管从构造还是解析方面都没有第一种方式高效,而且占用空间也更大。不过 PHP 6 仍然支持对第一种方式的反序列化。Unicode 序列化也是在 PHP 6 中将要引入的序列化方式,因为在 PHP 6 之前字符串都是以二进制字符串存储的,而 PHP 6 中将提供 Unicode 字符串类型,因此序列化方式也提供了一种新的 Unicode 序列化形式,但是该形式与 PHP 4、PHP 5 均不兼容。因此对于其它语言来说,只需要实现第一种方式的序列化即可,对后两种方式只要提供反序列化支持,就可以跟 PHP 6 兼容。不推荐在其它语言中实现后两种方式的序列化,因为它们与 PHP 4 和 PHP 5 是不兼容的,并且效率也更低。

!!非转义二进制字符串序列化
非转义二进制字符串序列化形式为:
{{{
s:<length>:"<value>";
}}}
其中 <length> 是 <value> 的长度,<length> 是非负整数。<value> 为字符串值,这里的每个字符都是单字节字符,其范围与 ASCII 码的 0 - 255 的字符相对应。每个字符都表示原字符含义,没有转义字符,<value> 两边的引号("")是必须的,但不计算在 <length> 当中。这里的 <value> 相当于一个字节流,而 <length> 是这个字节流的字节个数。

它与各语言的对应关系如下表:

| 语言 | 序列化 | 反序列化 |
| PHP | string(PHP 4 & PHP 5) | string |
| Java | byte[]/char/char[]/String/~StringBuffer | byte[]/String |
| C# | byte[]/char/char[]/string/~StringBuilder | byte[]/string |
| VB(.NET) | Byte()/Char/Char()/String/~StringBuilder | Byte()/String |
| ~JavaScript | String | String |
| ~ActionScript 2 | ~ByteArray/String | ~ByteArray/String |
| ~ActionScript 3 | ~ByteArray/String | ~ByteArray/String |
| Delphi | ~TByteDynArray/~RawByteString/~AnsiString | ~RawByteString |
| Ruby | String/Symbol | String |
| Python | String | String |

上表中有些语言的反序列化有两种对应类型,那是因为在这些语言中,如果要反序列化的是对象属性名或者数组键名的话,就反序列化为字符串类型,否则就反序列化为字节数组。这些语言中都已经提供了将字节数组转换为字符串、字符数组等类型的工具(有些是 PHPRPC 提供的),所以你可以根据需要将反序列化的值转换为你希望的类型。

!!转义二进制字符串序列化
转义二进制字符串序列化形式为:
{{{
S:<length>:"<value>";
}}}

其中 <length> 是原始字符串的长度,而非 <value> 的长度。<length> 是非负整数。<value> 为经过转义之后的字符串。

转义规则:对于 ASCII 码小于 128 的字符(但不包括 \),按照单个字节写入(与非转义二进制字符串序列化相同),对于 128~255 的字符和 \ 字符,则将其 ASCII 码值转化为 16 进制编码的字符串,以 \ 作为开头,后面两个字节分别是这个字符的 16 进制编码,顺序按照由高位到低位排列,也就是第 8-5 位所对应的16进制数字字符(abcdef 这几个字母是小写)作为第一个字节,第 4-1 位作为第二个字节。依次编码下来,得到的就是 <value>。

目前,只有 PHP 6 支持该方式序列化,其它语言只提供反序列化支持。

| 语言 | 序列化 | 反序列化 |
| PHP 4/ PHP 5 | 无 | 无 |
| PHP 6 | string | string |
| Java | 无 | byte[]/String |
| C# | 无 | byte[]/string |
| VB(.NET) | 无 | Byte()/String |
| ~JavaScript | 无 | String |
| ~ActionScript 2 | 无 | ~ByteArray/String |
| ~ActionScript 3 | 无 | ~ByteArray/String |
| Delphi | 无 | ~RawByteString |
| Ruby | 无 | String |
| Python | 无 | String |

!!Unicode 字符串序列化
Unicode 字符串序列化形式为:
{{{
U:<length>:"<value>";
}}}
其中 <length> 是指原 Unicode 字符串的长度,而不是 <value> 的长度。<length> 是非负整数。<value> 为经过转义之后的字符串。

尽管 <length> 是原 Unicode 字符串的长度,但不是指它的字节数,也不完全是指它的字符数,确切的说是指它的字符单位数。因为 Unicode 字符串中采用的是 ~UTF16 编码,这种编码方式使用 16 位来表示一个字符的,但是并不是所有的字符都可以用 16 位表示,因此有些字符需要两个 16 位来表示。在 ~UTF16 编码中,16 位字符算作一个字符单位,一个实际的字符可能是一个字符单位,也有可能由两个字符单位组成。因此, Unicode 字符串中字符数并不总是等于字符单位数,而这里的 <length> 指的就是字符单位数,而不是字符数。

转义规则:对于 Unicode 编码小于 128 的字符(但不包括 \),按照单个字节写入,对于大于 128 的字符和 \ 字符,则转化为 16 进制编码的字符串,以 \ 作为开头,后面四个字节分别是这个字符单位的 16 进制编码,顺序按照由高位到低位排列,也就是第 16-13 位所对应的16进制数字字符(abcdef 这几个字母是小写)作为第一个字节,第 12-9 位作为第二个字节,第 8-5 位作为第三个字节,最后的第 4-1 位作为第四个字节。依次编码下来,得到的就是 <value>。

目前,除了 PHP 6 支持该方式序列化,只有极少数其它语言提供这种方式的序列化支持,其它语言只提供反序列化支持。

| 语言 | 序列化 | 反序列化 |
| PHP 4/ PHP 5 | 无 | 无 |
| PHP 6 | ~UnicodeString | ~UnicodeString |
| Java | 无 | String |
| C# | 无 | string |
| VB(.NET) | 无 | String |
| ~JavaScript | 无 | String |
| ~ActionScript 2 | 无 | String |
| ~ActionScript 3 | 无 | String |
| Delphi | ~WideString | ~WideString |
| Ruby | 无 | String |
| Python | Unicode | Unicode |
容器序列化形式为:
{{{
a:<n>:{<key 1><value 1><key 2><value 2>...<key n><value n>}
}}}
其中 <n> 表示容器元素的个数,<key 1>、<key 2>……<key n> 表示容器键名,<value 1>、<value 2>……<value n> 表示与键名相对应的元素的值。

键名可以是整型或字符串型,序列化后的格式跟整型和字符串型数据序列化后的格式相同。

元素值可以是任意类型,其序列化后的格式与其所对应的类型序列化后的格式相同。

它与各语言的对应关系如下表:

| 语言 | 序列化 | 反序列化 |
| PHP | array | array |
| Java | ~AssocArray/Array/List/Collection/Map | ~AssocArray |
| C# | ~AssocArray/Array/~IList/~IDictionary/~ICollection | ~AssocArray |
| VB(.NET) | ~AssocArray/Array/~IList/~IDictionary/~ICollection | ~AssocArray |
| ~JavaScript | Array/Object | Array |
| ~ActionScript 2 | Array/Object | Array |
| ~ActionScript 3 | Array/Object | Array |
| Delphi | 动态数组/~TArrayList/~THashedArrayList/~THashMap | ~THashMap |
| Ruby | Array/Hash | Hash |
| Python | List/Tuple/Dict | Dict |

以上各语言的 PHPRPC 实现中都提供了在这些容器类型之间相互转换的方法,因此可以很方便的将反序列化后的数据转换为合适的容器类型。
PHPRPC 加密传输的密钥是通过 [[Diffie-Hellman 密钥交换算法]]来在客户端和服务器端同时生成的,生成密钥需要一些参数,这些参数在 PHPRPC 中是由服务器端提供的,密钥的长度范围也是由这些参数的长度来决定的,因此这里的密钥长度实际上是指服务器端所提供的生成密钥的参数的长度。

在 PHPRPC 中,服务器端提供有下面几个长度的密钥参数:
>96 128 160 192 256 512 768 1024 1536 2048 3072 4096

密钥长度是通过客户端来指定,然后通过与服务器协商后确定。因为太长的密钥在生成时花费的时间也更长,所以服务器不一定对所有这些长度的密钥参数都支持。对于计算能力比较弱的语言来说,支持的长度可能就较短。

协商的规则很简单,就是服务器在它支持的长度中寻找最接近客户端指定的长度的值作为最后的长度值。例如,客户端指定长度为 1000,如果服务器端最大支持的是 512,那么最后的长度就是 512,如果服务器端支持的最大长度是2048,并且其中包括 1024 的话,那么最后的长度就是 1024。
对象序列化形式为:
{{{
O:<length>:"<class name>":<n>:{<name 1><value 1><name 2><value 2>...<name n><value n>}
}}}
其中 <length> 表示对象的类名 <class name> 的字符串长度。<n> 表示对象中的字段```在 PHP 手册中,字段被称为属性,而实际上,在 PHP 5 中引入的用 {{{__set}}}、{{{__get}}} 来定义的对象成员更适合叫做属性。因为用 {{{__set}}}、{{{__get}}} 来定义的对象成员与其它语言中的属性的行为是一致,而 PHP 手册中所说的属性实际上在其他语言中(例如:C#)中被称为字段,为了避免混淆,这里也称为字段,而不是属性。```个数。对于 PHP 来说,这些字段包括对象所在类及其祖先类中用 var、public、protected 和 private 声明的字段,但是不包括 static 和 const 声明的静态字段。也就是说只有实例(instance)字段。

<name 1>、<name 2>……<name n>表示每个字段的字段名,而 <value 1>、<value 2>……<value n> 则表示与字段名所对应的字段值。

字段名是字符串型,序列化后格式与字符串型数据序列化后的格式相同。

字段值可以是任意类型,其序列化后的格式与其所对应的类型序列化后的格式相同。

但字段名的序列化与它们声明的可见性是有关的,下面重点介绍一下关于字段名的序列化,这里以 PHP 来说明:

var 和 public 声明的字段都是公共字段,因此它们的字段名的序列化格式是相同的。公共字段的字段名按照声明时的字段名进行序列化,但序列化后的字段名中不包括声明时的变量前缀符号 $。

protected 声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。因此保护字段的字段名在序列化时,字段名前面会加上 {{{\0*\0}}} 的前缀。这里的 \0 表示 ASCII 码为 0 的字符,而不是 \0 组合。

private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,字段名前面会加上 {{{\0<declared class name>\0}}} 的前缀。这里 <declared class name> 表示的是声明该私有字段的类的类名,而不是被序列化的对象的类名。因为声明该私有字段的类不一定是被序列化的对象的类,而有可能是它的祖先类。

字段名被作为字符串序列化时,字符串值中包括根据其可见性所加的前缀。字符串长度也包括所加前缀的长度。其中 \0 字符也是计算长度的。

在其它语言中,支持反射私有字段和保护字段的语言(如 Java、C# 等),同样支持上面的字段名的序列化规则。但有些语言不支持反射私有字段,这种情况下都按照公共字段名序列化规则来进行序列化。

它与各语言的对应关系如下表:

| 语言 | 序列化 | 反序列化 |
| PHP | Object/~PHPRPC_Date | Object/~PHPRPC_Date |
| Java | Object/Date | Object/Date |
| C# | Object/~DateTime | Object/~DateTime |
| VB(.NET) | Object/~DateTime | Object/~DateTime |
| ~JavaScript | Object/Date | Object/Date |
| ~ActionScript 2 | Date | Object/Date |
| ~ActionScript 3 | Object/Date | Object/Date |
| Delphi | ~TPHPObject/~TDateTime | ~TPHPObject/~TDateTime |
| Ruby | Struct/Object/Time | Struct/Object/Time |
| Python | Object/datetime.datetime | Object/datetime.datetime |

上表中的 Object 是指各语言中的可序列化对象,关于各语言中具体实现请参见 PHPRPC 3.0 用户手册的相关章节。
这里所说的对象自定义序列化是指对序列化方式进行自定义,而不是指自定义对象的序列化。

PHP 5 提供了一个 Serializable 接口,如果用户在自己定义的类中实现了这个接口,那么在该类的对象序列化时,就会被按照用户实现的方式去进行序列化。

对象自定义序列化形式如下:
{{{
C:<name length>:"<class name>":<data length>:{<data>}
}}}

其中 <name length> 表示类名 <class name> 的长度,<data length> 表示自定义序列化数据 <data> 的长度,而自定义的序列化数据 <data> 是完全的用户自己定义的格式,与 PHP 序列化格式可以完全无关,这部分数据由用户自己实现的序列化和反序列化接口方法来管理。

Serializable 接口中定义了 2 个方法,serialize 和 unserialize,这两个方法不会被直接调用,而是在调用 PHP 序列化过程中被自动调用。其中 serialize 方法没有参数,它的返回值就是 <data> 的内容。而 unserialize 有一个参数,这个参数的值就是 <data> 的内容,它没有返回值。实际上接口中的 serialize 方法就是让用户来自己序列化对象中的内容,序列化后的内容格式直接充填到 <data> 中,等到反序列化时,取出这部分内容,然后传给用户实现的 unserialize 接口方法,让用户自己去反序列化这部分内容。

下面举一个 PHP 的例子,来说明 Serializable 接口的使用:
<code php>
class MyClass implements Serializable {
    public $member;

    function MyClass() {
        $this->member = 'member value';
    }

    public function serialize() {
        return wddx_serialize_value($this->member);
    }

    public function unserialize($data) {
        $this->member = wddx_deserialize($data);
    }
}
$a = new MyClass();
echo serialize($a);
echo "\n";
print_r(unserialize(serialize($a)));
</code>
命令行下的输出结果为:
{{{
C:7:"MyClass":90:{<wddxPacket version='1.0'><header/><data><string>member value</string></data></wddxPacket>}
MyClass Object
(
    [member] => member value
)
}}}

目前在其它大部分语言中也提供了同样的机制可以实现同样的自定义序列化功能,并可以在不同语言之间通过这种格式进行交互。目前 PHPRPC 实现中,只有 ~ActionScript 2.0 不支持这种对象自定义序列化,但是反序列化这种数据时,它会返回一个包含 name 和 data 字段的对象。
!!对象引用和指针引用的形式

先介绍一下这两种引用序列化的形式:

对象引用序列化形式为:
{{{
r:<number>;
}}}
指针引用序列化形式为:
{{{
R:<number>;
}}}

!!什么是对象引用和指针引用呢?

这里以 PHP 为例来用程序说明这一点:
<code php>
<?php
class SampleClass {
    var $value;
}
$a = new SampleClass();
$a->value = $a;

$b = new SampleClass();
$b->value = &$b;

echo serialize($a);
echo "\n";
echo serialize($b);
echo "\n";
?>
</code>
该程序的命令行执行结果为:
{{{
O:11:"SampleClass":1:{s:5:"value";r:1;}
O:11:"SampleClass":1:{s:5:"value";R:1;}
}}}
这里变量 $a 的 value 字段的值被序列化成了 r:1,而 $b 的 value 字段的值被序列化成了 R:1。

也就是说,$a->value 是 $a 的对象引用,而 $b->value 是 $b 的指针引用。

!!对象引用和指针引用有什么区别呢?

那继续看下面这个例子:
<code php>
class SampleClass {
    var $value;
}
$a = new SampleClass();
$a->value = $a;

$b = new SampleClass();
$b->value = &$b;

$a->value = 1;
$b->value = 1;

var_dump($a);
var_dump($b);
</code>
该程序的命令行执行结果为:
{{{
object(SampleClass)#1 (1) {
  ["value"]=>
  int(1)
}
int(1)
}}}
改变 $a->value 的值仅仅是改变了 $a->value 的值,而改变 $b->value 的值却改变了 $b 本身,这就是对象引用和指针引用的区别。

但是在 PHP 中,数组并不是对象,因此数组不会作为对象引用来序列化,例如:
<code php>
$a = array();
$a[1] = 1;
$a["value"] = $a;

echo $a["value"]["value"][1];
echo "\n";
$a = unserialize(serialize($a));
echo $a["value"]["value"][1];
</code>
该程序的命令行执行结果为:
{{{
1
}}}
大家会发现,将原数组序列化再反序列化后,数组结构变了。原本 $a["value"]["value"][1] 中的值 1,但在反序列化之后丢失了。

原因是什么呢?让我们输出序列化之后的结果来看一看:
<code php>
$a = array();
$a[1] = 1;
$a["value"] = $a;

echo serialize($a);
</code>
该程序的命令行执行结果为:
{{{
a:2:{i:1;i:1;s:5:"value";a:2:{i:1;i:1;s:5:"value";N;}}
}}}
原来,序列化之后,$a["value"]["value"] 变成了 NULL,而不是一个对象引用。

也就是说,PHP 只对对象在序列化时才会生成对象引用。对所有的标量类型和数组(也包括 NULL)序列化时都不会生成对象引用。但是如果明确使用了 & 运算符,在序列化时,会被序列化为指针引用。

因此,我们在其它语言中,对带有引用的容器类型采用指针引用方式序列化,对带有引用的对象类型采用对象引用方式序列化,这样就可以保证可以在所有语言中正确传递引用结构了。

!!引用序列化中的数字是什么?

大家一定很奇怪引用序列化形式中,{{{r}}} 和 {{{R}}} 后面这个 <number> 是什么吧?下面我们就来详细介绍一下。

<number> 简单的说,就是所引用的对象在序列化串中第一次出现的位置。但是这个位置不是指字符的位置,而是指对象(这里的对象是泛指所有类型的量,而不仅限于对象类型)的位置。

下面来举例说明一下:
<code php>
class ClassA {
    var $int;
    var $str;
    var $bool;
    var $obj;
    var $pr;
}

$a = new ClassA();
$a->int = 1;
$a->str = "Hello";
$a->bool = false;
$a->obj = $a;
$a->pr = &$a->str;

echo serialize($a);
</code>
该程序的命令行执行结果为:
{{{
O:6:"ClassA":5:{s:3:"int";i:1;s:3:"str";s:5:"Hello";s:4:"bool";b:0;s:3:"obj";r:1;s:2:"pr";R:3;}
}}}
在这个例子中,首先序列化的对象是 {{{ClassA}}} 的一个对象,那么给它编号为 1,接下来要序列化的是这个对象的几个成员,第一个被序列化的成员是 {{{int}}} 字段,那它的编号就为 2,接下来被序列化的成员是 {{{str}}},那它的编号就是 3,依此类推,到了 {{{obj}}} 成员时,它发现该成员已经被序列化了,并且编号为 1,因此它被序列化时,就被序列化成了 {{{r:1;}}} ,在接下来被序列化的是 {{{pr}}} 成员,它发现该成员实际上是指向 {{{str}}} 成员的一个引用,而 {{{str}}} 成员的编号为 3,因此,{{{pr}}} 就被序列化为 {{{R:3;}}} 了。

PHP 序列化是如何来编号被序列化的数据的呢?在 PHP 序列化时,首先建立一个空表,然后每个被序列化的数据在被序列化之前,先判断该数据是否在表中出现过,如果出现过则返回第一次出现的位置,否则添加该数据的引用到这个表的最后。如果返回了位置,则根据是否是指针引用类型来进行相应的引用序列化操作,而引用序列化中那个数字就是这个位置,最后如果不是指针引用,还需要将数据也添加到这个表的最后。

上面的过程只是来说明这个数字是如何生成的,实际在程序实现时,可以根据每种语言的特征进行具体的优化。

!!对象引用的反序列化

虽然 PHP 只对对象类型的数据做对象引用的序列化,但是反序列化的字符串如果不是 PHP 生成的,而是人为构造或者用其它语言生成的,即使对象引用指向的不是一个对象,它也能正确地按照对象引用所指向的数据进行反序列化。例如:
<code php>
class StrClass {
    var $a;
    var $b;
}

$a = unserialize('O:8:"StrClass":2:{s:1:"a";s:5:"Hello";s:1:"b";r:2;}');

var_dump($a);
</code>
该程序的命令行执行结果为:
{{{
object(StrClass)#1 (2) {
  ["a"]=>
  string(5) "Hello"
  ["b"]=>
  string(5) "Hello"
}
}}}

大家会发现,上面的例子反序列化后,$a->b 的值与 $a->a 的值是一样的,尽管 $a->a 不是一个对象,而是一个字符串。因此如果用其它语言来实现 PHP 序列化的话,没有必要把字符串作为标量类型来处理,即使按照对象引用来序列化拥有相同字符串内容的数据,用 PHP 同样可以正确的反序列化。这样可以更节省序列化后的内容所占用的空间。
在本章里,我们会首先介绍如何下载与安装 PHPRPC,之后我们会以 PHP、Java 和 ~JavaScript 三种语言来给出几个简单的示例,让你对 PHPRPC 有一个快速的认识。如果你所使用的语言并不是这三种语言,也没有关系,在你需要的语言章节中同样也会看到类似的简单示例。

!下载
<<tiddler [[PHPRPC 的下载]]>>
!安装
<<tiddler [[PHPRPC 的安装]]>>
!示例
<<tiddler [[HelloWorld]]>>
PHP 序列化支持的数值类型有 32 位有符号整型数和双精度浮点数,另外,为了方便讲解,布尔类型在这里我们也将它算为数值类型。

!! 布尔类型

布尔类型序列化形式为:
{{{
b:<digit>;
}}}
其中 <digit> 为 0 或 1,当布尔值为 false 时,<digit> 为 0,否则为 1。

它与各语言之间的对应表如下:

| 语言 | 序列化 | 反序列化 |
| PHP | true/false | true/false |
| Java | true/false | true/false |
| C# | true/false | true/false |
| VB(.NET) | True/False | True/False |
| ~JavaScript | true/false | true/false |
| ~ActionScript 2 | true/false | true/false |
| ~ActionScript 3 | true/false | true/false |
| Delphi | True/False | True/False |
| Ruby | true/false | true/false |
| Python | True/False | True/False |

!!整数类型

整数类型被序列化为:
{{{
i:<number>;
}}}
其中 <number> 为一个整型数的十进制字符串表示,范围与 32 位有符号整形数相同(-2147483648 到 2147483647)。数字前可以有正负号,超出这个范围的整数,应以浮点数类型方式序列化。

| 语言 | 序列化 | 反序列化 |
| PHP | integer | integer |
| Java | Byte/Short/Integer | Integer |
| C# | byte/sbyte/short/ushort/int | int |
| VB(.NET) | Byte/~SByte/Short/~UInt16/Integer | Integer |
| ~JavaScript | -2147483648 到 2147483647 范围的整数 | Number |
| ~ActionScript 2 | -2147483648 到 2147483647 范围的整数 | Number |
| ~ActionScript 3 | -2147483648 到 2147483647 范围的整数 | int |
| Delphi | Byte/Word/~SmallInt/~ShortInt/Integer 以及 -2147483648 到 2147483647 范围的 ~LongWord/Int64 | Integer |
| Ruby | -2147483648 到 2147483647 范围内的 Integer | Integer |
| Python | Int 以及 -2147483648 到 2147483647 范围内 Long | Int |

!!浮点数类型

浮点数类型被序列化为:
{{{
d:<number>;
}}}
其中 <number> 为一个浮点数,其范围与双精度浮点数的范围一样。可以表示成整数形式、浮点数形式和科学技术法形式。另外 <number> 有三个特殊值,它们是无穷大、负无穷大和非数,它们分别对应的值为 INF、-INF 和 NAN。对于不支持非数的语言,NAN 反序列化时返回 0。如果 <number> 范围超过双精度浮点数的范围,仍然可以用数字形式表示,对于不能表示这个范围内数字的语言来说,可以视情况将其反序列化为无穷大、负无穷大或 0。

| 语言 | 序列化 | 反序列化 |
| PHP | double | double |
| Java | Long/Float/Double | Long/Double |
| C# | uint/long/ulong/decimal/float/double | int/uint/long/ulong/decimal/double |
| VB(.NET) | ~UInt32/Long/~UInt64/Decimal/Single/Double | Integer/~UInt32/Long/~UInt64/Decimal/Double |
| ~JavaScript | 实数和 -2147483648 到 2147483647 范围之外的整数 | Number |
| ~ActionScript 2 | 实数和 -2147483648 到 2147483647 范围之外的整数 | Number |
| ~ActionScript 3 | 实数和 -2147483648 到 2147483647 范围之外的整数 | Number |
| Delphi | Single/Double/Currency 和 -2147483648 到 2147483647 范围之外的 ~LongWord/Int64 | Integer/Int64/Currency/Double |
| Ruby | Float 和 -2147483648 到 2147483647 范围之外的 Integer | Integer/Float |
| Python | Float 和 -2147483648 到 2147483647 范围之外的 Long | Long/Float |
<<tiddler [[关于本指南]]>>

如果您已经对 PHPRPC 的用途和特点有所了解,那么可以直接进入[[快速入门]],如果你需要针对某种语言的具体资料,那么请直接进入相关<<tag 语言主题>>。

!为什么要有 PHPRPC?
<<tiddler [[PHPRPC 的起源]]>>

那么 PHPRPC 有哪些[[特点|PHPRPC 的特点]]呢?