2004年09月21日

<head>
<script>
if (!document.getElementById)
    document.getElementById = function() { return null; }

function initializeMenu(menuId, actuatorId) {
    var menu = document.getElementById(menuId);
    var actuator = document.getElementById(actuatorId);

    if (menu == null || actuator == null) return;

    //if (window.opera) return; // I’m too tired

    actuator.parentNode.style.backgroundImage = “url(/images/plus.gif)”;
    actuator.onclick = function() {
        var display = menu.style.display;
        this.parentNode.style.backgroundImage =
            (display == “block”) ? “url(/images/plus.gif)” : “url(/images/minus.gif)”;
        menu.style.display = (display == “block”) ? “none” : “block”;

        return false;
    }
}
 window.onload = function() {
            initializeMenu(“productsMenu”, “productsActuator”);
            initializeMenu(“newPhonesMenu”, “newPhonesActuator”);
            initializeMenu(“compareMenu”, “compareActuator”);
        }
</script>
<style>
body {
  font-family: verdana, helvetica, arial, sans-serif;
}

#mainMenu {
  background-color: #EEE;
  border: 1px solid #CCC;
  color: #000;
  width: 203px;
}

#menuList {
  margin: 0px;
  padding: 10px 0px 10px 15px;
}

li.menubar {
  background: url(/images/plus.gif) no-repeat 0em 0.3em;
  font-size: 12px;
  line-height: 1.5em;
  list-style: none outside;
}

.menu, .submenu {
  display: none;
  margin-left: 15px;
  padding: 0px;
}

.menu li, .submenu li {
  background: url(/images/square.gif) no-repeat 0em 0.3em;
  list-style: none outside;
}

a.actuator {
  background-color: transparent;
  color: #000;
  font-size: 12px;
  padding-left: 15px;
  text-decoration: none;
}

a.actuator:hover {
  text-decoration: underline;
}

.menu li a, .submenu li a {
  background-color: transparent;
  color: #000;
  font-size: 12px;
  padding-left: 15px;
  text-decoration: none;
}

.menu li a:hover, submenu li a:hover {
  /*border-bottom: 1px dashed #000;*/
  text-decoration: underline;
}

span.key {
  text-decoration: underline;
}
</style>
</head>
<body>
<div id=”mainMenu”>
      <ul id=”menuList”>
        <li class=”menubar”>
          <a href=”#” id=”productsActuator” class=”actuator”>Phones</a>
          <ul id=”productsMenu” class=”menu”>
            <li>
              <a href=”#” id=”newPhonesActuator” class=”actuator”>New Phones</a>
              <ul id=”newPhonesMenu” class=”submenu”>
                <li><a href=”#”>9290</a></li>
                <li><a href=”#”>8390</a></li>
                <li><a href=”#”>8290</a></li>
                <li><a href=”#”>8270</a></li>
              </ul>
            </li>
            <li>
              <a href=”#” id=”compareActuator” class=”actuator”>Compare</a>
              <ul id=”compareMenu” class=”submenu”>
                <li><a href=”#”>All Phones</a></li>
                <li><a href=”#”>Service Provider</a></li>
              </ul>
            </li>
          </ul>
        </li>
      </ul>
    </div>
  </body>

Asp.net动态生成html页面
作者:love610 | 来自:CSDN | 浏览: 6
最近研究一个新闻系统,找到了关于asp.net生成HTML的资料

  • 思路

1. 利用如Dw-Mx这样的工具生成html格式的模板,在需要添加格式的地方加入特殊标记(如$htmlformat$),动态生成文件时利用代码读取此模板,然后获得前台输入的内容,添加到此模板的标记位置中,生成新文件名后写入磁盘,写入后再向数据库中写入相关数据。

2. 使用后台代码硬编码Html文件,可以使用HtmlTextWriter类来写html文件。

  • 优点

1. 可以建立非常复杂的页面,利用包含js文件的方法,在js文件内加入document.write()方法可以在所有页面内加入如页面头,广告等内容。

2. 静态html文件利用MS Windows2000的Index Server可以建立全文搜索引擎,利用asp.net可以以DataTable的方式得到搜索结果。而Win2000的Index服务无法查找xml文件的内容。如果包括了数据库搜索与Index索引双重查找,那么此搜索功能将非常强大。

3. 节省服务器的负荷,请求一个静态的html文件比一个aspx文件服务器资源节省许多。

  • 缺点

思路二: 如果用硬编码的方式,工作量非常大,需要非常多的html代码。调试困难。而且使用硬编码生成的html样式无法修改,如果网站更换样式,那么必须得重新编码,给后期带来巨大的工作量。

因此这里采用的是第一种思路

示列代码

1.定义(template.htm)html模板页面

<html>

<head>

<title></title>

<meta http-equiv=”Content-Type” content=”text/html; charset=gb2312″>

</head>

<body >

<table $htmlformat[0] height=”100%” border=”0″ width=”100%” cellpadding=”10″ cellspacing=”0″ bgcolor=”#eeeeee” style=”border:1px solid #000000″>

<tr>

<td width=”100%” valign=”middle” align=”left”>

<span style=”color: $htmlformat[1];font-size: $htmlformat[2]“>$htmlformat[3]</span>

</td>

</tr>

</table>

</body>

</html>

2.asp.net代码:

//———————读html模板页面到stringbuilder对象里—-

string[] format=new string[4];//定义和htmlyem标记数目一致的数组

StringBuilder htmltext=new StringBuilder();

try

{

using (StreamReader sr = new StreamReader(“存放模板页面的路径和页面名”))

{

String line;

while ((line = sr.ReadLine()) != null)

{

htmltext.Append(line);

}

sr.Close();

}

}

catch

{

Response.Write(“<Script>alert(‘读取文件错误’)</Script>”);

}

//———————给标记数组赋值————

format[0]=”background=\”bg.jpg\”";//背景图片

format[1]= “#990099″;//字体颜色

format[2]=”150px”;//字体大小

format[3]= “<marquee>生成的模板html页面</marquee>”;//文字说明

//———-替换htm里的标记为你想加的内容

for(int i=0;i<4;i++)

{

htmltext.Replace(“$htmlformat["+i+"]“,format[i]);

}

//———-生成htm文件——————――

try

{

using(StreamWriter sw=new StreamWriter(“存放路径和页面名”,false,System.Text.Encoding.GetEncoding(“GB2312″)))

{

sw.WriteLine(htmltext);

sw.Flush();

sw.Close();

}

}

catch

{

Response.Write (“The file could not be wirte:”);

}

小结

用此方法可以方便的生成html文件。程序使用了是循环替换,因此对需替换大量元素的模板速度非常快。

让搜索出来的关键字变红色的函数
作者:小荷 | 来自:蓝色理想 | 浏览: 83
Function Highlight(strText, strFind, strBefore, strAfter)
    Dim nPos
    Dim nLen
    Dim nLenAll
   
    nLen = Len(strFind)
    nLenAll = nLen + Len(strBefore) + Len(strAfter) + 1

    Highlight = strText

    If nLen > 0 And Len(Highlight) > 0 Then
        nPos = InStr(1, Highlight, strFind, 1)
        Do While nPos > 0
            Highlight = Left(Highlight, nPos – 1) & _
                strBefore & Mid(Highlight, nPos, nLen) & strAfter & _
                Mid(Highlight, nPos + nLen)

            nPos = InStr(nPos + nLenAll, Highlight, strFind, 1)
        Loop
    End If
End Function

使用:

Response.Write Highlight(myText, “someword”, “<font color=red>”, “</font>”)

如在”我是中国人”中搜索”中国”,用该函数后的效果为:我是中国

window.open的例子和使用方法以及参数说明(完整版)
作者:不详 | 来自:柠檬树下 | 浏览: 323
一、window.open()支持环境: JavaScript1.0+/JScript1.0+/Nav2+/IE3+/Opera3+

二、基本语法:
window.open(pageURL,name,parameters)
其中:
pageURL 为子窗口路径
name 为子窗口句柄
parameters 为窗口参数(各参数用逗号分隔)

三、示例:
<SCRIPT>
<!–
window.open (‘page.html’,'newwindow’,'height=100,width=400,top=0,left=0,toolbar=no,menubar=no,scrollbars=no, resizable=no,location=no, status=no’)
//写成一行
–>
</SCRIPT>
脚本运行后,page.html将在新窗体newwindow中打开,宽为100,高为400,距屏顶0象素,屏左0象素,无工具条,无菜单条,无滚动条,不可调整大小,无地址栏,无状态栏。
请对照。

上例中涉及的为常用的几个参数,除此以外还有很多其他参数,请见四。

四、各项参数
其中yes/no也可使用1/0;pixel value为具体的数值,单位象素。

参数 | 取值范围 | 说明
| |
alwaysLowered | yes/no | 指定窗口隐藏在所有窗口之后
alwaysRaised | yes/no | 指定窗口悬浮在所有窗口之上
depended | yes/no | 是否和父窗口同时关闭
directories | yes/no | Nav2和3的目录栏是否可见
height | pixel value | 窗口高度
hotkeys | yes/no | 在没菜单栏的窗口中设安全退出热键
innerHeight | pixel value | 窗口中文档的像素高度
innerWidth | pixel value | 窗口中文档的像素宽度
location | yes/no | 位置栏是否可见
menubar | yes/no | 菜单栏是否可见
outerHeight | pixel value | 设定窗口(包括装饰边框)的像素高度
outerWidth | pixel value | 设定窗口(包括装饰边框)的像素宽度
resizable | yes/no | 窗口大小是否可调整
screenX | pixel value | 窗口距屏幕左边界的像素长度
screenY | pixel value | 窗口距屏幕上边界的像素长度
scrollbars | yes/no | 窗口是否可有滚动栏
titlebar | yes/no | 窗口题目栏是否可见
toolbar | yes/no | 窗口工具栏是否可见
Width | pixel value | 窗口的像素宽度
z-look | yes/no | 窗口被激活后是否浮在其它窗口之上

【1、最基本的弹出窗口代码】

其实代码非常简单:

<SCRIPT LANGUAGE=”javascript”>
<!–
window.open (‘page.html’)
–>
</SCRIPT>
因为着是一段javascripts代码,所以它们应该放在<SCRIPT LANGUAGE=”javascript”>标签和</script>之间。<!– 和 –>是对一些版本低的浏览器起作用,在这些老浏览器中不会将标签中的代码作为文本显示出来。要养成这个好习惯啊。
window.open (‘page.html’) 用于控制弹出新的窗口page.html,如果page.html不与主窗口在同一路径下,前面应写明路径,绝对路径(http://)和相对路径(../)均可。用单引号和双引号都可以,只是不要混用。
这一段代码可以加入HTML的任意位置,<head>和</head>之间可以,<body>间</body>也可以,越前越早执行,尤其是页面代码长,又想使页面早点弹出就尽量往前放。

【2、经过设置后的弹出窗口】

下面再说一说弹出窗口的设置。只要再往上面的代码中加一点东西就可以了。
我们来定制这个弹出的窗口的外观,尺寸大小,弹出的位置以适应该页面的具体情况。
<SCRIPT LANGUAGE=”javascript”>
<!–
window.open (‘page.html’, ‘newwindow’, ‘height=100, width=400, top=0,left=0, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=no, status=no’)
//写成一行
–>
</SCRIPT>
参数解释:
<SCRIPT LANGUAGE=”javascript”> js脚本开始;
window.open 弹出新窗口的命令;
‘page.html’ 弹出窗口的文件名;
‘newwindow’ 弹出窗口的名字(不是文件名),非必须,可用空”代替;
height=100 窗口高度;
width=400 窗口宽度;
top=0 窗口距离屏幕上方的象素值;
left=0 窗口距离屏幕左侧的象素值;
toolbar=no 是否显示工具栏,yes为显示;
menubar,scrollbars 表示菜单栏和滚动栏。
resizable=no 是否允许改变窗口大小,yes为允许;
location=no 是否显示地址栏,yes为允许;
status=no 是否显示状态栏内的信息(通常是文件已经打开),yes为允许;
</SCRIPT> js脚本结束

【3、用函数控制弹出窗口】

下面是一个完整的代码。
<html>
<head>
<script LANGUAGE=”JavaScript”>
<!–
function openwin() { window.open (“page.html”, “newwindow”, “height=100, width=400, toolbar=
no, menubar=no, scrollbars=no, resizable=no, location=no, status=no”)
//写成一行
}
//–>
</script>
</head>
<body onload=”openwin()”>
…任意的页面内容…
</body>
</html>
这里定义了一个函数openwin(),函数内容就是打开一个窗口。在调用它之前没有任何用途。
怎么调用呢?
方法一:<body onload=”openwin()”> 浏览器读页面时弹出窗口;
方法二:<body onunload=”openwin()”> 浏览器离开页面时弹出窗口;
方法三:用一个连接调用:
<a href=”#” onclick=”openwin()”>打开一个窗口</a>
注意:使用的“#”是虚连接。
方法四:用一个按钮调用:
<input type=”button” onclick=”openwin()” value=”打开窗口”>

【4、同时弹出2个窗口】

对源代码稍微改动一下:
<script LANGUAGE=”JavaScript”>
<!–
function openwin()
{ window.open (“page.html”, “newwindow”, “height=100, width=100, top=0,left=0,toolbar=no, menubar=no, scrollbars=no, resizable=no, location=no, status=no”)
//写成一行
window.open (“page2.html”, “newwindow2″, “height=100, width=100, top=100, left=100,toolbar=no, menubar=no, scrollbars=no, resizable=no, location=no, status=no”)
//写成一行
}
//–>
</script>
为避免弹出的2个窗口覆盖,用top和left控制一下弹出的位置不要相互覆盖即可。最后用上面说过的四种方法调用即可。

注意:2个窗口的name(newwindows和newwindow2)不要相同,或者干脆全部为空。OK?

【5、主窗口打开文件1.htm,同时弹出小窗口page.html】

如下代码加入主窗口<head>区:
<script language=”javascript”>
<!–
function openwin()
{window.open(“page.html”,”",”width=200,height=200″)
}
//–>
</script>
加入<body>区:
<a href=”1.htm” onclick=”openwin()”>open</a>即可。

【6、弹出的窗口之定时关闭控制】

下面我们再对弹出的窗口进行一些控制,效果就更好了。如果我们再将一小段代码加入弹出的页面(注意是加入到page.html的HTML中,可不是主页面中,否则…),让它10秒后自动关闭是不是更酷了?

首先,将如下代码加入page.html文件的<head>区:
<script language=”JavaScript”>
function closeit()
{setTimeout(“self.close()”,10000) //毫秒}
</script>
然后,再用<body onload=”closeit()”> 这一句话代替page.html中原有的<BODY>这一句就可以了。(这一句话千万不要忘记写啊!这一句的作用是调用关闭窗口的代码,10秒钟后就自行关闭该窗口。)

【7、在弹出窗口中加上一个关闭按钮】

<FORM>
<INPUT TYPE=’BUTTON’ VALUE=’关闭’ onClick=’window.close()’>
</FORM>
呵呵,现在更加完美了!

【8、内包含的弹出窗口-一个页面两个窗口】

上面的例子都包含两个窗口,一个是主窗口,另一个是弹出的小窗口。

通过下面的例子,你可以在一个页面内完成上面的效果。
<html>
<head>
<SCRIPT LANGUAGE=”JavaScript”>
function openwin()
{OpenWindow=window.open(“”, “newwin”, “height=250, width=250,toolbar=no,scrollbars=”+scroll+”,menubar=no”);
//写成一行
OpenWindow.document.write(“<TITLE>例子</TITLE>”)
OpenWindow.document.write(“<BODY BGCOLOR=#ffffff>”)
OpenWindow.document.write(“<h1>Hello!</h1>”)
OpenWindow.document.write(“New window opened!”)
OpenWindow.document.write(“</BODY>”)
OpenWindow.document.write(“</HTML>”)
OpenWindow.document.close()}
</SCRIPT>
</head>
<body>
<a href=”#” onclick=”openwin()”>打开一个窗口</a>
<input type=”button” onclick=”openwin()” value=”打开窗口”>
</body>
</html>
看看 OpenWindow.document.write()里面的代码不就是标准的HTML吗?只要按照格式写更多的行即可。千万注意多一个标签或少一个标签就会出现错误。记得用OpenWindow.document.close()结束啊。

【9、终极应用–弹出的窗口之Cookie控制】

回想一下,上面的弹出窗口虽然酷,但是有一点小毛病(沉浸在喜悦之中,一定没有发现吧?)比如你将上面的脚本放在一个需要频繁经过的页面里(例如首页),那么每次刷新这个页面,窗口都会弹出一次,是不是非常烦人?:-(有解决的办法吗?Yes! ;-) Follow me.

我们使用cookie来控制一下就可以了。

首先,将如下代码加入主页面HTML的<HEAD>区:
<script>
function openwin()
{window.open(“page.html”,”",”width=200,height=200″)}
function get_cookie(Name)
{var search = Name + “=”
var returnvalue = “”;
if (document.cookie.length > 0) {
offset = document.cookie.indexOf(search)
if (offset != -1) {
offset += search.length
end = document.cookie.indexOf(“;”, offset);
if (end == -1)
end = document.cookie.length;
returnvalue=unescape(document.cookie.substring(offset,end))
}
}
return returnvalue;
}
function loadpopup(){
if (get_cookie(‘popped’)==”){
openwin()
document.cookie=”popped=yes”
}
}
</script>
然后,用<body onload=”loadpopup()”>(注意不是openwin而是loadpop啊!)替换主页面中原有的<BODY>这一句即可。你可以试着刷新一下这个页面或重新进入该页面,窗口再也不会弹出了。真正的Pop-Only-Once!

写到这里弹出窗口的制作和应用技巧基本上算是完成了,俺也累坏了,一口气说了这么多,希望对正在制作网页的朋友有所帮助俺就非常欣慰了。

需要注意的是,JS脚本中的的大小写最好前后保持一致。

DHTML 怎样防止IE工具对window.open的拦截

http://www.cnblogs.com/huobazi/archive/2004/07/10/22926.aspx

我这里也有一个类似的解决方案:

其中 open.htm 为:

这个的好处就是不用改变已有的代码。只要想办法把这个代码放进原来的网页的前面把window.open替换掉就可以了。

2004年09月15日
winform下树和数据库关联操作是很常用的,下面的代码是项目中用到的,从一个表生成树:

DataView dvTree;
  private void InitTreeByDataSet( TreeNodeCollection nds, string parentID, DataSet ds )
  {
   dvTree = new DataView();
   
   TreeNode tempNode;
   
   dvTree.Table = ds.Tables[0];

   //判断ParentServiceTypeID是否为null,并生成不同的过滤条件
   if ( parentID == “Isnull(ParentServiceTypeID,’Null Column’) = ‘Null Column’” )
   {
    dvTree.RowFilter = parentID ;
   }
   else
   {
    dvTree.RowFilter = “ParentServiceTypeID =’” + parentID +”‘”;
   }
   
   foreach ( DataRowView drvTree in dvTree )
   {
    tempNode      = new TreeNode();
    tempNode.Text = drvTree["Name"].ToString();
    tempNode.Tag  = drvTree["PKFieldValue"].ToString();
   
    nds.Add(tempNode );
    //递归调用
    InitTreeByDataSet( tempNode.Nodes, tempNode.Tag.ToString(), ds );
   }
  
  }

先在项目引用里添加上对Microsoft Word 9.0 object library的引用。

using System;

namespace DocConvert

{
 class DoctoRtf
 {
  static void Main()
  {

   //创建一个word的实例
   Word.Application newApp = new Word.Application();

   // 指定源文件和目标文件
   object Source=”c:\\abc\\Source.doc”;
   object Target=”c:\\abc\\Target.rtf”;

   object Unknown =Type.Missing;

   // 打开要转换的Word文件
   newApp.Documents.Open(ref Source,ref Unknown,
    ref Unknown,ref Unknown,ref Unknown,
    ref Unknown,ref Unknown,ref Unknown,
    ref Unknown,ref Unknown,ref Unknown,
    ref Unknown );

   // 指定文档的类型
   object format = Word.WdSaveFormat.wdFormatRTF;

   //改变文档类型
   newApp.ActiveDocument.SaveAs(ref Target,ref format,
    ref Unknown,ref Unknown,ref Unknown,
    ref Unknown,ref Unknown,ref Unknown,
    ref Unknown,ref Unknown,ref Unknown);                   

   //关闭word实例
   newApp.Quit(ref Unknown,ref Unknown,ref Unknown);

  }
 }
}

http://www.codeproject.com/csharp/ConvertDOCintoOtherFormat.asp

2004年08月13日

◆匈牙利命名法

基本原则是:

变量名=属性+类型+对象描述

⑴属性部分:

全局变量: g_

常量 : c_

类成员变量: m_

⑵类型部分:

指针: p

句柄: h

布尔型: b

浮点型: f

无符号: u

⑶描述部分:

初始化: Init

临时变量: Tmp

目的对象: Dst

源对象: Src

窗口: Wnd

下边举例说明:

hwnd: h表示句柄,wnd表示窗口,合起来为“窗口句柄”。

m_bFlag: m表示成员变量,b表示布尔,合起来为:“某个类的成员变量,布尔型,是一个状态标志”。

1,变量命名;
2,常量命名、宏定义;
3,资源名字定义格式;
4,函数命名和命名空间、类的命名、接口的命名;
5,结构体命名;
6,控件的命名;
7,注释;

2004年08月12日

对等点如何彼此定位
2003-10-21    Levi       点击: 58
http://www.chinap2p.com/article/article.php?articleid=47
对等点如何彼此定位

要完成有用的工作,P2P 应用程序中的对等点必须能够彼此发现对方和与对方交互。软件开发人员 Todd Sundsted 在本文中继续研究 P2P 计算,并描述了几种完成这一任务(称为发现(discovery))的方法,以及每种方法的优势和弱点。
对等应用程序是一种大规模但又是细粒度的应用程序。每个对等点都可以进入或退出 — 每个对等点都关注于自己的任务。在他们短暂的活动期间,尝试完成布置给它们的任务。这些任务中的大多数都要涉及与其它对等点交互。

管理体系结构(对等点在这种体系结构下运作)必须为构成完整 P2P 应用程序的对等点提供许多必要的服务。在我们的 P2P 计算“旅程”中,已经讲述了通信和安全性服务(请参阅参考资料);现在是时候来研究对等点发现服务了。

对等点发现服务使 P2P 应用程序中的对等点能够彼此定位以便相互之间可以交互。实现对等点发现服务有多种方法。我们先从告诉对等点彼此之间的存在这种最简单的方法开始:显式点到点配置。

显式点到点配置
显式点到点配置与其说是一种真正的发现机制,还不如说是一种用来避免实现发现的机制。每个存在的对等点都知道“居住”在其 P2P 世界中的其它对等点。

术语点到点(point-to-point)意味着,在 P2P 应用程序中的每个对等点都知道需要不断与之交互的每个对等点,并与之相连。这种连线不必将每个对等点都连起来 — 将每个对等点和其他各个对等点彼此相连,是不太可能的 — 但是,不这样做(无论是有意与否)将会使某些对等点产生网络盲点。

我为本系列文章所构建的简单的 P2P 应用程序(请参阅参考资料)使用显式点到点配置。每个对等点必须预先配置其它所有对等点的地址。如果您已经下载并使用了那些代码,则您无疑已经经历了忍受这一需求所带来的挫折 — 配置既单调乏味又容易出错,更不用提一般的麻烦了。

一般而言,分布式应用程序中节点的显式点到点配置不能很好地扩展到具有较多节点的大型网络。那就是为什么分布式计算应用程序和技术总是(也有一些显著的例外)包含命名和定位功能。这也解释了为什么域名系统(DNS) — 一种分布式命名系统,最终取代了用于机器命名的主机文件(hosts file)机制。维护主机文件是单调乏味、容易出错的,并且一般来说,很难在大型网络环境下运转。

但是,显式点到点配置并非一无是处。点到点寻址缺乏灵活性的特性也带来了一定程度的安全性。通过对网络中的每个对等点预先设置它所知道并且将要与之交互的对等点列表,使得网络在外部攻击面前表现得很稳固。

动态发现模型
与显式点到点配置方法的静态特性截然相反,目录服务和网络模型具有动态特性。这些模型通常能更好地符合 P2P 应用程序,它们倾向于该领域中动态的一边。

在下面几节中,我们将研究三种不同的机制,对等点通过这些机制动态地定位其它对等点和了解它自身所属的环境。

目录服务模型
在目录服务模型中,一台或多台有特殊用途的服务器为对等点提供目录服务。为了使可扩展性最大化,对应用程序进行了结构化设计,以便少量的目录就可以为数量众多的对等点服务。对等点向目录服务注册关于自身的信息(其名称、地址、资源和元数据),并通过根据目录服务器中信息的查询,使用目录服务来定位其它对等点。

图 1 说明了一个使用目录来向对等点提供位置和命名服务的 P2P 体系结构。目录本身可以是对等点(尽管是很庞大的对等点),或者可以只担当目录而不作它用。

图 1,目录服务模型

目录有两种类型。这两种类型因为其目录集中式管理的程度不同而有略微的差别。

P2P 领域中目录模型的最佳示例是 Napster 和与 Napster 几乎一模一样的 OpenNap。在 Napster 模型中,采用集中式管理目录。虽然,采用集中式管理的目录遭到“本质上是‘非 P2P’”的指责,并且事实上促使了 Napster 被弃用,但它确实提供了一个重要的优势:集中式管理使它容易确保服务器硬件和配置能足以达到服务质量目标。

如果我们先暂时将 P2P 放一放,可以发现 DNS 是分散式目录的一个优秀示例。与因特网本身相似,DNS 设计为甚至在部分网络受到严重破坏的情况下仍能工作。DNS 目录采用层次化结构,根目录代表顶级域(譬如“com”),它将子域查询服务(如“etcee.com”这样的域)的任务委派到下一层次的 DNS 服务器。

在任意一种情况下,只有目录位置必须配置到每个对等点中,这对于点到点模型有重要优势。为加入到 P2P,对等点将自己注册到集中目录服务器。回忆上面的图。当对等点 A 希望与一个它不知道位置的对等点交互时,对等点 A 向目录服务器发送请求。然后,目录服务器向 A 返回那个对等点的位置。

网络模型
图 2 说明了另一种 P2P 应用程序。它由许多对等点组成,这些对等点在功能上很类似。没有专门的目录服务器。对等点必须使用它们所在的网络来定位其它对等点。

图 2,网络模型

正如名称所暗示的,网络模型 P2P 应用程序由一些(通常是动态的)对等点组成。没有一个对等点知道整个网络的结构或者组成网络的每个对等点的身份。相反,对等点只知道直接与它们通信的对等点 — 它们通过代理参与到大型网络中。

对等点必须合作完成任务。在许多环境中这种合作包括支持分布式查询、分布式消息传递,甚至包括认证和授权行为。因为涉及通信量的多少,象文件传输这样需要大流量的网络操作通常在对等点间直接发生 — 而不是通过对等点的网络。

思考上面图 2 中的网络。当对等点 A 希望知道网络中另一个对等点的位置时,它就发出一个查询请求并传递给邻居。这些邻居尝试满足这个请求。如果这些邻居不能完全满足这个请求,就将请求传递给它们的邻居,以此类推。

要加入网络,一个对等点要找到愿意接受它为邻居的另一个对等点。但是,当对等点本身还不是网络的一部分时,它如何找到网络中的另一个对等点呢?

一个可能的解决方案是向这个对等点提供一个对等点列表,让其检查。对等点设法联系列表上的对等点直到一个或多个对等点接受它为邻居。这个解决方案听起来很象点到点模型,是不是?正如最初 Gnutella 用户所证明的那样,这个解决方案只是一定程度上有效。因为 P2P 网络(尤其是 Gnutella)动态性很强,任何静态列表都不太可能长期有效。

进一步研究 Gnutella 这种环境中的解决方案是很有趣的。Gnutella 实现首先将这样开始:当其它对等点通过网络传播发送请求时,Gnutella 捕获并持久地存储这些对等点的位置。当这些客户机关闭后又重新启动时,它试图连接每个先前标识的对等点直到找到一个或多个仍在运行。

这种方法,虽然自动化程度很高,但是脆弱而且低效。后来,通过添加对从中央缓存下载活动对等点的列表的支持(请参阅参考资料以获得示例),改进了这种模式下的客户机。

这种模型的一个有趣的方面是,在支持对等点发现的过程中,组成网络的对等点担任了非常活跃的角色。正如我们即将看到的,活动对等点的参与并不是必要条件。

多播(multicast)模型
除了网络中的节点不必协助发现以外,多播模型和网络模型很相似。这种模型利用网络自身提供的特性来定位和确认对等点和资源。这种技术的实现(Sun Microsystems 的 Project Jxta 是一个极佳的示例;有关 Jxta的更多信息,请参阅参考资料)使用 IP 多播来实现查询。

不象单播(unicast) IP 数据报 — 一台主机,最多只能向一台主机发送数据报,多播 IP 数据报可以同时发往多台主机。更重要的是,发送方不必知道有多少接收方存在或者究竟有没有接收方存在。发送主机只是封装消息并将它发布到网络上。所有调整到适当频道(特殊 IP 地址和端口号的组合)的客户机将接收到该消息的一个副本。

使用 IP 多播技术的发现通过让对等点用多播定期宣布自己的存在来工作。该消息包含对等点的 TCP/IP 主机名和端口号。对此消息感兴趣的对等点检测这个消息后,抽取出主机名和端口号,并使用这个信息与新对等点建立正常的 TCP/IP 连接。

这就是多播是如何在单个子网上工作的。众多子网(组成整个网络)间的路由多播通信是完全不同的,并且是一个非常复杂的课题。这也是基于 IP 多播的发现的主要局限。没有路由器的支持,基于 IP 多播的发现被局限在同一子网上的对等点之间。不幸的是,因特网对多播并不友好。通常,因特网(或大型内部网)上的发现由跨网络边界的特殊对等点将消息复制到另一个网络中来实现。

结束语
P2P 应用程序的对等点发现是一个有趣的话题。下个月,我们将研究使用 IP 多播来查找活动对等点的对等点发现的实现。这个简单 P2P 应用程序代码的附加部分将消除一个最大的问题 — 对等点的点到点配置。

TCP/IP

维基百科,自由的百科全书。

TCP/IP协议,或称为TCP/IP协议栈,或互联网协议系列。

TCP/IP协议栈
(按TCP/IP参考模型划分)

应用层 FTP SMTP HTTP
传输层 TCP UDP
网络层 IP ICMP ARP
链路层 以太网 令牌环 FDDI

包含了一系列构成互联网基础的网络协议

TCP/IP成功的另一个因素在与对为数众多的低层协议的支持。这些低层协议对应与OSI模型 中的第一层(物理层)和第二层(数据链路层)。每层的所有协议几乎都有一半数量的支持TCP/IP,例如: 以太网(Ethernet),令牌环(Token Ring),光纤数据分布接口(FDDI),端对端协议( PPP),X.25帧中继(Frame Relay),ATMSonet, SDH等。

TCP/IP协议栈组成

整个通信网络的任务,可以划分成不同的功能块,即抽象成所谓的 ” 层” 。用于互联网的协议可以比照TCP/IP参考模型进行分类。TCP/IP协议栈起始于第三层协议IP(互联网协议) 。所有这些协议都在相应的RFC文档中讨论及标准化。重要的协议在相应的RFC文档中均标记了状态: “必须“ (required) ,“推荐“ (recommended) ,“可选“ (elective) 。其它的协议还可能有“ 试验“(experimental) 或“ 历史“(historic) 的状态。

必须协议

所有的TCP/IP应用都必须实现IPICMP。对于一个路由器(router) 而言,有这两个协议就可以运作了,虽然从应用的角度来看,这样一个路由器 意义不大。实际的路由器一般还需要运行许多“推荐“使用的协议,以及一些其它的协议。

在几乎所有连接到互联网上的计算机上都存在的IPv4 协议出生在1981年,今天的版本和最早的版本并没有多少改变。升级版IPv6 的工作始于1995年,目的在与取代IPv4ICMP 协议主要用于收集有关网络的信息查找错误等工作。

推荐协议

每一个应用层(TCP/IP参考模型 的最高层) 一般都会使用到两个传输层协议之一: 面向连接的TCP传输控制协议和无连接的包传输的UDP用户数据报文协议 。 其它的一些推荐协议有:

  • TELNET (Teletype over the Network, 网络电传) ,通过一个终端(terminal)登陆到网络(运行在TCP协议上)。
  • FTP (File Transfer Protocol, 文件传输协议) ,由名知义(运行在TCP协议上) 。
  • SMTP (Simple Mail Transfer Protocol,简单邮件传输协议) ,用来发送电子邮件(运行在TCP协议上) 。
  • DNS (Domain Name Service,域名服务) ,用于完成地址查找,邮件转发等工作(运行在TCPUDP协议上) 。
  • ECHO (Echo Protocol, 回绕协议) ,用于查错及测量应答时间(运行在TCPUDP协议上) 。
  • NTP (Network Time Protocol,网络时间协议) ,用于网络同步(运行在UDP协议上) 。
  • SNMP (Simple Network Management Protocol, 简单网络管理协议) ,用于网络信息的收集和网络管理。
  • BOOTP (Boot Protocol,启动协议) ,应用于无盘设备(运行在UDP协议上)。

可选协议

最常用的一些有

  • 支撑万维网WWW的超文本传输协议HTTP
  • 动态配置IP地址的DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),
  • 收邮件用的POP3 (Post Office Protocol, version 3, 邮局协议) ,
  • 用于加密安全登陆用的SSH (Secure Shell,用于替代安全性差的TELNET) ,
  • 用于动态解析以太网硬件地址的ARP (Address Resolution Protocol,地址解析协议) 。

范例: 不同计算机运行的不同协议

参考文献

  1. RFC 1180 – TCP/IP tutorial