ASP入门及提高
ASP 中健壮的页结构的异常处理
错误处理是让程序员牢骚满腹的东西之一。让我们来面对它,我们不写错误的代码就是了。。。或者类似的想法。不幸的是,代码中的运行时错误可能有许多的原因,从硬件、软件的改变到使用了别的开发团队的代码等等。有效的处理这些错误并使得它对于网站正常操作过程的中断最小化是每个有良知的程序员的责任。
在本文讨论的范围内,有三个不同的地方可以发生错误:脚本,中间件,以及IT内部架构。IT内部架构的错误,比方周期性的性能降低并导致IIS进行(Inetinfo.exe)崩溃几乎是无法避免的。这种类型的错误通常只能打电话要求技术支持并且会让系统管理员忙上很久。开发者不能为阻止这类错误做些什么,但是我们通常能够应付和改正脚本和中间件中的错误。 在安装了IIS以后,缺省的服务器端脚本语言被设置成VBScript。许多Web 开发团队在他们的开发环境中保持了这些缺省设置,这是不幸的,因为VBScript对于处理运行时错误的支持非常的差。在VBScript中,开发者可以使用的唯一一个错误处理结构是
On Error Resume Next (打开错误处理功能) 和
On Error GoTo 0 (关闭错误处理功能).
为了在你的ASP 页面里有效的使用这个错误处理结构,你可能需要用这些结构包括可能抛出异常的代码,就象下面这样:
<%
Dim myVar
On Error Resume Next
'下面一行代码会在MSXML 4.0没有被安装或者已经损坏的情况下产生错误
Set myVar = Server.CreateObject("MSXML2.DOMDocument.4.0")
If Err.Number <> 0 Then
' 在这里处理错误
' 结束错误处理,避免以后发生的错误无法被发现
On Error GoTo 0
Else
' myVar 现在指向 MSXML 4.0 DOMDocument的一个实例
' 结束错误处理,避免以后发生的错误无法被发现
On Error GoTo 0
End If
就象你看到的一样,如果你要在每一行现有的可能发生错误的代码上使用上面的规则,你的程序马上就会充满 "On Error" 和 "If Err.Number <> 0 Then . . ." 这样的结构。
而另一方面JScript对于健壮的错误处理机制“结构化异常处理(SEH)”有内建的支持。使用SEH能够让你的软件开发团队顺利的转移到.NET 环境上来,因为SEH是JScript.NET, VB.NET, 和 C#缺省的错误处理机制。(注意:.NET不支持VBScript。)下面的例子代码执行与VBScript代码相同的操作,但是使用JScript语言并用SEH来处理异常
<%@ LANGUAGE="JScript" %>
<%
var myVar;
try {
myVar = Server.CreateObject("MSXML2.DOMDocument.4.0");
// 如果上面发生了错误,那么catch
// 代码块就会立刻被执行
// 并在myVar上进行必要的操作。
}
catch (e) {
// 在这里处理异常,异常本身可以用
// 'e' 变量进行引用。
}
finally {
// 在这里进行所有收尾工作
// 这段代码不管错误有没有发生
// (也就是“catch”块有没有运行)
// 都会执行。
}
%>
通过在服务器端使用JScript,你就得到了SEH带来的好处,以及对复杂ASP对象,比方Server,Request和Response对象的完全使用。要把这种脚本语言设置成你的ASP页面的缺省语言,你只需要简单的在你的ASP页面上添加@LANGUAGE指令,就象上面的例子那样。
在SQL Server中保存和输出图片
有时候我们需要保存一些binary data进数据库。SQL Server提供一个叫做image的特殊数据类型供我们保存binary data。Binary data可以是图片、文档等。在这篇文章中我们将看到如何在SQL Server中保存和输出图片。
建表
为了试验这个例子你需要一个含有数据的table(你可以在现在的库中创建它,也可以创建一个新的数据库),下面是它的结构:
Column Name
Datatype
Purpose
ID
Integer
identity column Primary key
IMGTITLE
Varchar(50)
Stores some user friendly title to identity the image
IMGTYPE
Varchar(50)
Stores image content type. This will be same as recognized content types of ASP.NET
IMGDATA
Image
Stores actual image or binary data.
保存images进SQL Server数据库
为了保存图片到table你首先得从客户端上传它们到你的web服务器。你可以创建一个web form,用TextBox得到图片的标题,用HTML File Server Control得到图片文件。确信你设定了Form的encType属性为multipart/form-data。
Stream imgdatastream = File1.PostedFile.InputStream;
int imgdatalen = File1.PostedFile.ContentLength;
string imgtype = File1.PostedFile.ContentType;
string imgtitle = TextBox1.Text;
byte[] imgdata = new byte[imgdatalen];
int n = imgdatastream.Read(imgdata,0,imgdatalen);
string connstr=
((NameValueCollection)Context.GetConfig
("appSettings"))["connstr"];
SqlConnection connection = new SqlConnection(connstr);
SqlCommand command = new SqlCommand
("INSERT INTO ImageStore(imgtitle,imgtype,imgdata)
VALUES ( @imgtitle, @imgtype,@imgdata )", connection );
SqlParameter paramTitle = new SqlParameter
("@imgtitle", SqlDbType.VarChar,50 );
paramTitle.Value = imgtitle;
command.Parameters.Add( paramTitle);
SqlParameter paramData = new SqlParameter
( "@imgdata", SqlDbType.Image );
paramData.Value = imgdata;
command.Parameters.Add( paramData );
SqlParameter paramType = new SqlParameter
( "@imgtype", SqlDbType.VarChar,50 );
paramType.Value = imgtype;
command.Parameters.Add( paramType );
connection.Open();
int numRowsAffected = command.ExecuteNonQuery();
connection.Close();
从数据库中输出图片
现在让我们从数据库中取出我们刚刚保存的图片,在这儿,我们将直接将图片输出至浏览器。你也可以将它保存为一个文件或做任何你想做的。
private void Page_Load(object sender, System.EventArgs e)
{
string imgid =Request.QueryString["imgid"];
string connstr=((NameValueCollection)
Context.GetConfig("appSettings"))["connstr"];
string sql="SELECT imgdata, imgtype FROM ImageStore WHERE id = "
+ imgid;
SqlConnection connection = new SqlConnection(connstr);
SqlCommand command = new SqlCommand(sql, connection);
connection.Open();
SqlDataReader dr = command.ExecuteReader();
if(dr.Read())
{
Response.ContentType = dr["imgtype"].ToString();
Response.BinaryWrite( (byte[]) dr["imgdata"] );
}
connection.Close();
}
在上面的代码中我们使用了一个已经打开的数据库,通过datareader选择images。接着用Response.BinaryWrite代替Response.Write来显示image文件。
使用ASP.NET加密口令
当我们在网站上建立数据库时,保护用户的信息安全是非常必要的。多数用户不愿意让别人知道自己的信息,同时网管也不想因为安全问题而丢失网站的信誉。无论对于谁,安全问题都是非常重要的。
为了解决这个问题,我给大家提供一个简单实用,但是老套的方法,就是口令加密。在此我们使用ASP.NET技术对口令加密。简单的讲,就是将用户提供的口令加密之后,然后让它和存放于系统中的数据比较,如果相同,则通过验证。
在ASP中,并未提供加密的对象,我们只能使用外部的对象来进行加密。现在好了,在ASP.NET中提供了加密的解决方法。在名字空间System.Web.Security中包含了类FormsAuthentication,其中有一个方法HashPasswordForStoringInConfigFile。这个方法可以将用户提供的字符变成乱码,然后存储起来,甚至可以 存储在cookies中。
HashPasswordForStoringInConfigFile方法使用起来很简单,它支持"SHA1"和"MD5"加密算法。
下面的代码简单的演示了关于其用法:
<%@ Page language="c#" %>
<%@ Import Namespace="System.Web.Security" %>
<html>
<head>
<script language="C#" runat="server">
public void encryptString(Object sender, EventArgs e)
{
SHA1.Text = FormsAuthentication.HashPasswordForStoringInConfigFile(txtPassword.Text,"SHA1");
MD5.Text =FormsAuthentication.HashPasswordForStoringInConfigFile(txtPassword.Text, "MD5") ;
}
</script>
</head>
<body>
<form runat="server" ID="Form1">
<p>
<b>Original Clear Text Password: </b>
<br>
<asp:Textbox id="txtPassword" runat="server" />
<asp:Button runat="server" text="Encrypt String" onClick="encryptString" ID="Button1" />
</p>
<p>
<b>Encrypted Password In SHA1: </b>
<asp:label id="SHA1" runat="server" />
</p>
<p>
<b>Encrypted Password In MD5: </b>
<asp:label id="MD5" runat="server" />
</p>
</form>
</body>
</html>
正如你所看到的这样简单易用。我们可以把这段加密程序封装在一个函数里便于重复的使用。代码如下:
public string EncryptPassword(string PasswordString,string PasswordFormat )
{
if (PasswordFormat="SHA1"){
EncryptPassword=FormsAuthortication.HashPasswordForStoringInConfigFile(PasswordString ,"SHA1");
}
elseif (PasswordFormat="MD5")
{ EncryptPassword=FormsAuthortication.HashPasswordForStoringInConfigFile(PasswordString ,"MD5");
}
else
{
EncryptPassword="";
}
我们可以在数据库中添加一个字段,使用insert将加密的口令作为一个string存入数据库中。当用户登陆的时候,就可以将用户输入的口令加密结果和数据库中的正确结果比较,通过这种办法来验证口令的正确性了。在此,我就不往下写了,关于数据库的知识还得读者自己去学习。
web.config一个中文解释
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<!-- 动态调试编译
设置 compilation debug="true" 以将调试符号(.pdb 信息)
插入到编译页中。因为这将创建执行起来
较慢的大文件,所以应该只在调试时将该值设置为 true,而所有其他时候都设置为
false。有关更多信息,请参考有关
调试 ASP.NET 文件的文档。
-->
<compilation defaultLanguage="vb" debug="true" />
<!-- 自定义错误信息
设置 customErrors mode="On" 或 "RemoteOnly" 以启用自定义错误信息,或设置为 "Off" 以禁用自定义错误信息。
为每个要处理的错误添加 <error> 标记。
-->
<customErrors mode="RemoteOnly" />
<!-- 身份验证
此节设置应用程序的身份验证策略。可能的模式是 \“Windows\”、
\“Forms\”、\“Passport\”和 \“None\”
-->
<authentication mode="Windows" />
<!-- 授权
此节设置应用程序的授权策略。可以允许或拒绝用户或角色访问
应用程序资源。通配符:"*" 表示任何人,"?" 表示匿名
(未授权的)用户。
-->
<authorization>
<allow users="*" /> <!-- 允许所有用户 -->
<!-- <allow users="[逗号分隔的用户列表]"
roles="[逗号分隔的角色列表]"/>
<deny users="[逗号分隔的用户列表]"
roles="[逗号分隔的角色列表]"/>
-->
</authorization>
<!-- 应用程序级别跟踪记录
应用程序级别跟踪在应用程序内为每一页启用跟踪日志输出。
设置 trace enabled="true" 以启用应用程序跟踪记录。如果 pageOutput="true",则
跟踪信息将显示在每一页的底部。否则,可以通过从 Web 应用程序
根浏览 "trace.axd" 页来查看
应用程序跟踪日志。
-->
<trace enabled="false" requestLimit="10" pageOutput="false" traceMode="SortByTime" localOnly="true" />
<!-- 会话状态设置
默认情况下,ASP.NET 使用 cookie 标识哪些请求属于特定的会话。
如果 cookie 不可用,则可以通过将会话标识符添加到 URL 来跟踪会话。
若要禁用 cookie,请设置 sessionState cookieless="true"。
-->
<sessionState
mode="InProc"
stateConnectionString="tcpip=127.0.0.1:42424"
sqlConnectionString="data source=127.0.0.1;user id=sa;password="
cookieless="false"
timeout="20"
/>
<!-- 全球化
此节设置应用程序的全球化设置。
-->
<globalization requestEncoding="utf-8" responseEncoding="utf-8" />
</system.web>
</configuration>
尝尝ASP.NET中的小甜饼
Cookie对于使用过ASP的读者来讲并不陌生,但是我们要讲的是在ASP.NET中的Cookie。我们知道,Cookie存在于用户计算机浏览器中,我们可以使用Cookie来存放一些很简单的数据。但是,有一点别说我没提醒你:记住这不是个好办法,因为用户可以在任何时间删除Cookie信息,也可以关闭Cookie功能。好了,开场白就这些。
使用ASP.NET我们可以很容易的对Cookie集合进行操作。它和ASP中的Cookie一样,都是附属于Request和Response对象的。Listing1和2分别给出了如何读和写Cookie的方法。图1和2则是相应的显示。
Listing 1这个文件的功能是写入cookie
<%@ language="C#" %>
<HTML>
<script language="C#" runat="server">
void WriteClicked(Object Sender, EventArgs e)
{
//创建一个新Cookie,其cookie名来自于NameField.Text
HttpCookie cookie = new HttpCookie(NameField.Text);
//设定Cookie的值
cookie.Value = ValueField.Text;
//设定cookie生命为1 minute,TimeSpan()是一个专门设定时间间隔的类,我们定义了其实例tsMinute
DateTime dtNow = DateTime.Now;
TimeSpan tsMinute = new TimeSpan(0, 0, 1, 0);
cookie.Expires = dtNow + tsMinute;
//添加Cookie
Response.Cookies.Add(cookie);
Response.Write("Cookie written. <br><hr>");
}
</script>
<body>
<h3>
Use the button below to write cookies to your browser
</h3>
The cookies will expire in one minute.
<form runat="server" ID="Form1">
Cookie Name
<asp:textbox id="NameField" runat="server" />
<br>
Cookie Value
<asp:textbox id="ValueField" runat="server" />
<br>
<asp:button text="WriteCookie" onclick="WriteClicked" runat="server" ID="Button1" />
<br>
</form>
<a href="readcookies.aspx">Read the cookies</a>
</body>
</HTML>
图1
Listing 2 这个文件是为了读取刚才写入的cookie值
<%@ language="C#" %>
<script runat="server">
void ReadClicked(Object Sender, EventArgs e)
{
//取得想要的Cookie名
String strCookieName = NameField.Text;
//取得此Cookie名对应的对象,注意目前的得到的cookie是个对象
HttpCookie cookie = Request.Cookies[strCookieName];
//检验Cookie是否已经存在
if (null == cookie) {
Response.Write("Cookie not found. <br><hr>");
}
else {
//显示Cookie的值
String strCookieValue = cookie.Value.ToString();
Response.Write("The " + strCookieName + " cookie contains: <b>"
+ strCookieValue + "</b><br><hr>");
}
}
</script>
<html>
<body>
Use the button below to read a cookie
<br>
<form runat="server" ID="Form1">
Cookie Name
<asp:textbox id="NameField" runat="server" />
<asp:button text="ReadCookie" onclick="ReadClicked" runat="server" ID="Button1" />
</form>
<a href="writecookies.aspx">Write Cookies</a>
</body>
</html>
图2
为了更好的了解cookie的读写,代码分析如下。
在Listing 1中,为了将cookie写入用户的浏览器,我们调用了HttpCookie对象,并且建立了一个HttpCookie对象的实例cookie,写入cookie时,我们使用了Response.Cookies对象的方法Add(),最后我们设定了此cookie的生命期限为一分钟。
在Listing 2中,我们尝试了读取Cookie的方法。很简单,就是访问了Request.Cookies集合。
Cookie只能存放string类型的数据,如果想要存放更为复杂的数据类型,那么必须先将其转换为string类型。提示一种办法可以向Cookie写入复杂的数据类型,就是将想存放的复杂数据类型转化为XML串,然后写入Cookie。另外,我们还可以在一个cookie中存储多个值,在此就不多说了,留给有兴趣的读者去研究吧。
用asp.net实现的把本文推荐给好友功能
///<summary>
///<author>飞鹰@ASPCool.com</author>
///<description>本文用asp.net实现把此文推荐给好友的功能。</desciption>
///<copyright>ASP酷技术资讯网(www.ASPCool.com)</copyright>
///</summary>
这里飞鹰用一个简单的例子向大家介绍如何使用asp.net的邮件发送功能。首先,我们先做一个发送界面。tuijian.asp, 其中title是代表文章的标题,id代表文章的编号。
<html>
<body>
您好,欢迎使用ASP酷技术资讯网文章推荐功能,您推荐的文章为:《<%=request("title")%>》.<BR>
<form action="mail.aspx">
您的信箱:<INPUT TYPE="text" NAME="sender"><BR>
收件人信箱:<INPUT TYPE="text" NAME="accepter"><BR>
<INPUT TYPE="hidden" name="title" value="<%=request("title")%>">
<INPUT TYPE="hidden" name="id" value="<%=request("id")%>">
<INPUT TYPE="submit" value="发送"><INPUT TYPE="reset" value="重写">
</form>
</body>
</html>
下面我们就用asp.net来实现推荐文章功能,mail.aspx
<% @Page Language="C#" Debug="true"%>
<% @Import Namespace="System.Web.Mail" %>
<%
MailMessage msgMail = new MailMessage();
msgMail.To = Request["accepter"]; //邮件接受者
msgMail.Cc = "webmaster@aspcool.com";
msgMail.From = Request["sender"];
msgMail.Subject = "您的朋友从ASPCOOL.COM给您推荐了一篇文章。";
msgMail.BodyFormat = MailFormat.Html;
string strBody = "<html><body><b>" + Request["accepter"] +"</b>,您好。<br>" +
"您的朋友: <font color=\"red\">"+ Request["sender"]+ "</font>从<a href=http://www.aspcool.com>ASP酷技术资讯网</a>给您推荐了一篇文章--<a href=http://www.aspcool.com/lanmu/dot.asp?ID=" +Request["id"]+ Request["title"]+"</a>.请<a href=http://www.aspcool.com/lanmu/dot.asp?ID=" +Request["id"]+ ">点此查看</a>。</body></html>"; //邮件内容
msgMail.Body = strBody;
SmtpMail.Send(msgMail);
Response.Write("您好,您的推荐信已经发出去了,谢谢您对<a href=http://www.aspcool.com>ASP酷技术资讯网</a>的支持!请<a href=http://www.aspcool.com>返回</a>");
%>
看,是不是很简单,如果你有支持asp.net的空间的话就可以轻松推荐您的网站了。
开发BtoC电子商务系统(ASP.NET)
在对ASP.NET Web表单的编程模型有了基本的认识后,通过应用于现实的开发案例来提高对ASP.NET Web表单内在运作机制的了解,以及由此带来的对系统架构的掌控是很有必要的。我们没有为编程而编程的高贵姿态,我们深深懂得能够开发出高效,健壮,强大的应用程序始终是编程的终极。我们下面通过一个完整的BToC电子商务系统的开发流程来展示ASP.NET Web表单是怎样具体搭建面向下一代网络平台的。
这是一个典型的基于B/S(浏览器/服务器) 三层架构的食品,饮料电子商务零售系统——“玉米地零食店”。前端为产品浏览器,为消费者提供浏览/选购商品,下订单购物等各个环节的功能;中间层为销售商的税率,优惠等商务逻辑;后端为与整个零售系统相关的产品,顾客,订单等数据库。我们采用ASP.NET+IIS 5来构建前端和中间层,SQL Server 2000来管理后端数据库,整个系统运行于Windows XP。相关硬件配置只要满足上述软件的基本配置,系统性能便可保证。下面为该网上零售系统的前端界面图示:
在编制Web 表单商业前端和中间层之前,我们有必要对后端数据库做一个简单的介绍。后端数据库 CornfieldGrocer 由4个表组成:产品类别表Categories ,产品细节表 Details ,产品表 Products ,客户信息表Customers。考虑到演示系统的的简洁性,我们没有添加相关的存储过程,视图,规则等,这些在实际的系统的开发中对提高系统的性能是很有必要,尤其是在大数据量的情况下。下面为4个表的字段的图示介绍:
各个表的字段的表义已经相当清楚,我们不在这里赘述。我们下面向大家展示一下整个电子商务零售系统——“玉米地零食店”的物理文件组成及其结构,下图为示意图:
所有的文件位于ASP.NET站点目录CornfieldGrocer下,其中还有Web表单页面用到的图片子目录Images下的文件就不再在这里列出了。
下面我们开始编写前端和中间层代码,为了更清楚地展示Web Form ASP.NET的底层代码构造,我们采用记事本来完成整个代码的编写过程。需要说明的是在真正的工程项目开发实践中,如能借助Visual Studio.Net等可视集成开发工具,开发效率会大大提高。但在ASP.NET代码的底层机制没有谙熟的情况下,笔者强烈建议初期的开发学习不妨放在Windows系统自带的“记事本”这一简单却能够把代码暴露得相当清晰的工具里。
由于篇幅有限,我们不可能将所有的代码都在这里展示给大家。如前所述,web.config为每个站点级的基于XML的配置文件,负责一些ASP.NET的安全认证,编码选择,诊断测试等ASP.NET的配置工作,为浏览器请求ASP.NET Web表单时通过 IIS处理后的第一站。下面为其内容:
<configuration>
<system.web>
<globalization requestEncoding="UTF-8" responseEncoding="UTF-8" />
</system.web>
</configuration>
容易看到这里的配置内容相当简单,仅指定请求/发送的编码为“UTF-8”。我们对此不再赘述。
global.asax文件及其由后端代码文件global.asax.cs编译成的Bin\CornfieldGrocer.dll共同组成该网上零售系统的ASP.NET应用程序定义。我们先来看文件global.asax:
<%@ Application Inherits="CornfieldGrocer.Global" %>
该文件只有一行指示符,它表示ASP.NET应用程序的定义继承自Global类,而Global类正是在global.asax.cs文件中定义:
using System;
using System.Collections;
using System.ComponentModel;
using System.Web;
using System.Web.SessionState;
namespace CornfieldGrocer
{
public class Global : System.Web.HttpApplication
{
protected void Session_Start(Object sender, EventArgs e)
{
if (Session["ShoppingCart"] == null)
{
Session["ShoppingCart"] = new CornfieldGrocer.OrderList();
}
}
}
}
在Global类里,我们定义了区段(Session)意义下的购物卡(ShoppingCart)——这里采用了C#中的索引器。购物卡的类型为CornfieldGrocer命名空间中的OrderList类,在CornfieldGrocer.cs文件中有定义。我们当然也可以在global.asax文件中用脚本语言的形式将上面两个文件的内容合并起来,但那不是ASP.NET推荐的做法,因为脚本语言的第一次执行还要进行动态编译,这回损失一部分性能,而将CS文件提前编译成dll文件则会降低这种代价——当然这里的编译的意思还是指将CS的源代码文件编译成微软中间语言的过程。其次,页面与后端代码分离的原则易于项目管理,是Visual Studio.NET推荐的工程性的做法。
文件Default.aspx为整个网上零售系统的前端页面HTML代码,Default.aspx.cs为其后端控制Web表单行为的CS代码。由于篇幅关系我们这里不再赘述其HTML代码,实际上从前面给出的前端界面图示,我们可以基本了解Default.aspx的HTML代码结构。Style.css文件为Default.aspx文件的页面样式定义文件,定义一些页面元素的颜色,格式,间距等修饰性的东西,我们也不再多言。下面只向大家展示Default.aspx的页面指示符:
<%@ AutoEventWireup="false" Inherits="CornfieldGrocer.MainForm" %>
我们用“Inherits="CornfieldGrocer.MainForm"”来表示我们的页面继承自MainForm类,这样我们就实现了对ASP.NET Web 表单行为的控制代码与页面显示的HTML的分离。其中“AutoEventWireup="false"”表示页面事件非自动使能——页面事件非自动使能的意思是所有页面事件必须经过用户明确的操作才能触发,由于该属性缺省为“true”表示自动使能,但我们的商业逻辑要求非自动使能,故这里的语句很有必要,否则会引起系统处理的混乱。下面我们来看MainForm类:
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace CornfieldGrocer
{
public class MainForm: System.Web.UI.Page
{
protected System.Web.UI.WebControls.Label CurrentCategory;
protected System.Web.UI.WebControls.Label Name;
protected System.Web.UI.WebControls.Label SubTotal;
protected System.Web.UI.WebControls.ImageButton Imagebutton1;
protected System.Web.UI.WebControls.Label Description;
protected System.Web.UI.WebControls.Label Company;
protected System.Web.UI.WebControls.Repeater DetailsListing;
protected System.Web.UI.WebControls.DataList ProductListing;
protected System.Web.UI.WebControls.DataList ShoppingCartList;
protected System.Web.UI.HtmlControls.HtmlSelect CategoryList;
protected System.Web.UI.WebControls.Button btnSelect;
protected System.Web.UI.WebControls.Label Tax;
protected System.Web.UI.WebControls.Label Total;
protected System.Web.UI.WebControls.ImageButton Imagebutton4;
protected System.Web.UI.WebControls.ImageButton Imagebutton5;
protected System.Web.UI.WebControls.ImageButton Imagebutton6;
protected System.Web.UI.HtmlControls.HtmlInputText Qty;
protected System.Web.UI.HtmlControls.HtmlGenericControl CheckoutPanel;
protected System.Web.UI.HtmlControls.HtmlImage SelectedProdPicture;
public MainForm()
{
Page.Init += new System.EventHandler(Page_Init);
}
private void Page_Load(object sender, System.EventArgs e)
{
if (!IsPostBack)
{
ProductListing.SelectedIndex = 0;
UpdateProducts();
UpdateShoppingCart();
}
}
private void Page_Init(object sender, EventArgs e)
{
InitializeComponent();
}
private void InitializeComponent()
{
this.btnSelect.Click +=
new System.EventHandler(this.CategoryList_Select);
this.ProductListing.SelectedIndexChanged+=
new System.EventHandler(this.ProductListing_Select);
this.Imagebutton1.Click+=
new ImageClickEventHandler(this.AddBtn_Click);
this.Imagebutton4.Click+=
new ImageClickEventHandler(this.Recalculate_Click);
this.Imagebutton6.Click+=
new ImageClickEventHandler(this.ClearCart_Click);
this.Load +=
new System.EventHandler(this.Page_Load);
}
private void CategoryList_Select(Object sender, EventArgs e)
{
CurrentCategory.Text =
CategoryList.Items[CategoryList.SelectedIndex].Text;
UpdateProducts();
}
private void ProductListing_Select(Object sender, EventArgs e)
{
UpdateProducts();
}
private void AddBtn_Click(Object sender, ImageClickEventArgs e)
{
int productID = Int32.Parse
(ProductListing.DataKeys[ProductListing.SelectedIndex].ToString());
InventoryDB market = new InventoryDB();
DataRow product = market.GetProduct(productID);
CornfieldGrocer.OrderList shoppingCart =
((CornfieldGrocer.OrderList) Session["ShoppingCart"]);
shoppingCart.Add(new CornfieldGrocer.OrderItem(productID,
(String) product["ProductName"],
Double.Parse(product["UnitPrice"].ToString()), 1));
UpdateShoppingCart();
}
private void Recalculate_Click(Object sender, ImageClickEventArgs e)
{
CornfieldGrocer.OrderList shoppingCart =
((CornfieldGrocer.OrderList) Session["ShoppingCart"]);
for (int i=0; i<ShoppingCartList.Items.Count; i++) >
{
HtmlInputText qty =
(HtmlInputText) ShoppingCartList.Items[i].FindControl("Qty");
try
{
shoppingCart[(String) ShoppingCartList.DataKeys][i]].Quantity
= Int32.Parse(qty.Value);
}
catch (Exception)
{
}
}
UpdateShoppingCart();
}
private void ClearCart_Click(Object sender, ImageClickEventArgs e)
{
CornfieldGrocer.OrderList shoppingCart =
((CornfieldGrocer.OrderList) Session["ShoppingCart"]);
shoppingCart.ClearCart();
UpdateShoppingCart();
}
void UpdateProducts()
{
InventoryDB market = new InventoryDB();
int categoryID = Int32.Parse
(CategoryList.Items[CategoryList.SelectedIndex].Value);
ProductListing.DataSource =
market.GetProducts(categoryID).DefaultView;
ProductListing.DataBind();
int productID = Int32.Parse
(ProductListing.DataKeys[ProductListing.SelectedIndex].ToString());
DataRow product = market.GetProduct(productID);
Name.Text = product["ProductName"].ToString();
SelectedProdPicture.Src = product["ImagePath"].ToString();
Description.Text = product["ProductDescription"].ToString();
Company.Text = product["Manufacturer"].ToString();
DetailsListing.DataSource =
market.GetProductCalories(productID).DefaultView;
DetailsListing.DataBind();
}
void UpdateShoppingCart()
{
CornfieldGrocer.OrderList shoppingCart =
((CornfieldGrocer.OrderList) Session["ShoppingCart"]);
SubTotal.Text = String.Format("{0:C}", shoppingCart.SubTotal);
Tax.Text = String.Format("{0:C}", shoppingCart.Tax);
Total.Text = String.Format("{0:C}", shoppingCart.Total);
ShoppingCartList.DataSource=shoppingCart.Values;
ShoppingCartList.DataBind();
}
}
}
MainForm类中共有11个方法,19个保护域。其中的19个保护域和前面给出的前端界面图示的页面元素相对应,这里不再赘述。11个方法中MainForm()为构建器,其添加了页面初始化事件Page_Init(),这是ASP.NET Web表单最先处理的事件,一般进行一些基础的初始化操作。我们可以看到在Page_Init()中进行了初始化组件InitializeComponent()的操作。Page_Load()事件出现在用户发出请求后,页面装载的时候,在这里一般可做一些商业逻辑初始化方面的操作,比如数据库的连接,购物卡的初始化等。我们这里进行了产品展示UpdateProducts()和购物卡的初始化UpdateShoppingCart()的操作。
其他四个方法分别为产品类别的选择ProductListing_Select(),购买产品的添加AddBtn_Click(),购物卡的重新计算Recalculate_Click(),购物卡的清除ClearCart_Click()都是通过对ASP.NET控件的操作来触发相应的事件完成商业逻辑。上面的代码已经展示的相当清楚,我们不再赘述。
最后我们要向大家说明的是中间层商务逻辑的组件。它由三个类构成:库存数据类InventoryDB,订单项目类OrderItem和订单列表类OrderList。它们共同在文件CornfieldGrocer.cs文件中定义。自解释的编程方式已经它们的结构展示的相当清除,我们下面只给出该文件的CS源代码:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections;
namespace CornfieldGrocer
{
public class InventoryDB
{
public DataTable GetProducts(int categoryID)
{
SqlConnection sqlConnect= new SqlConnection
("server=(local);database=CornfieldGrocer;Trusted_Connection=yes");
SqlDataAdapter sqlAdapter1 = new SqlDataAdapter
("Select * from Products where categoryid="+categoryID,sqlConnect);
DataSet products = new DataSet();
sqlAdapter1.Fill(products, "products");
return products.Tables[0];
}
public DataRow GetProduct(int productID)
{
SqlConnection sqlConnect= new SqlConnection
("server=(local);database=CornfieldGrocer;Trusted_Connection=yes");
SqlDataAdapter sqlAdapter1 = new SqlDataAdapter
("Select * from Products where productID=" + productID, sqlConnect);
DataSet product = new DataSet();
sqlAdapter1.Fill(product, "product");
return product.Tables[0].Rows[0];
}
public DataTable GetProductCalories(int productID)
{
SqlConnection sqlConnect = new SqlConnection
("server=(local);database=CornfieldGrocer;Trusted_Connection=yes");
SqlDataAdapter sqlAdapter1 = new SqlDataAdapter
("Select * from Details where productID="+productID,sqlConnect);
DataSet details = new DataSet();
sqlAdapter1.Fill(details, "details");
return details.Tables[0];
}
}
public class OrderItem
{
public int productID;
public int quantity;
public String name;
public double price;
public OrderItem(int productID, String name, double price, int quantity)
{
this.productID = productID;
this.quantity = quantity;
this.name = name;
this.price = price;
}
public int ProductID
{
get { return ProductID; }
}
public int Quantity
{
get { return quantity; }
set { quantity=value; }
}
public String Name
{
get { return name; }
}
public double Price
{
get { return price; }
}
public double Total
{
get { return quantity * price; }
}
}
public class OrderList
{
private Hashtable orders = new Hashtable();
private double taxRate = 0.08;
public double SubTotal
{
get
{
if (orders.Count == 0)
return 0.0;
double subTotal = 0;
IEnumerator items = orders.Values.GetEnumerator();
while(items.MoveNext())
{
subTotal += ((OrderItem) items.Current).Price *
((OrderItem) items.Current).Quantity;
}
return subTotal;
}
}
public double TaxRate
{
get { return taxRate; }
set { taxRate = value; }
}
public double Tax
{
get { return SubTotal * taxRate; }
}
public double Total
{
get { return SubTotal * (1 + taxRate); }
}
public ICollection Values {
get {
return orders.Values;
}
}
public OrderItem this[String name] {
get {
return (OrderItem) orders[name];
}
}
public void Add(OrderItem value)
{
if (orders[value.Name] == null) {
orders.Add(value.Name, value);
}
else
{
OrderItem oI = (OrderItem)orders[value.Name];
oI.Quantity = oI.Quantity + 1;
}
}
public void ClearCart() {
orders.Clear();
}
}
}
需要说明的是我们将三个文件CornfieldGrocer.cs,Default.aspx.cs,Global.asax.cs用编译命令“csc /t:library /out:CornfieldGrocer.dll cornfieldgrocer.cs default.aspx.cs global.asax.cs”将它们全部封装在CornfieldGrocer命名空间里,虽然这并不是必须的。上面的编译器输出CornfieldGrocer.dll文件,我们配置该网上零售站点时只需将该文件拷贝到站点根目录中的Bin目录下即可。
到此为止,我们已经完整的向大家展示了利用ASP.NET Web表单建立一个小型的网上交易系统的编码,配置等工作。当然作为演示案例,它还没有真正系统的完善的性能,安全,界面等各个方面的优化考虑和设计。但它向我们展示的ASP.NET Web表单模型却非常典型且底层,大家不防在此基础上通过不断的修改和扩充来开发适合自己的交易系统。比如对于Default.aspx文件中AutoEventWireup="false"如果设置为“true”或去掉这个语句,在运行页面时会出现什么情况?通过这些练习便会不断的加深我们对ASP.NET底层的认识,最后达到游刃有余的把握。实际上技术的学习,尤其是编程,除了一定的兴趣和悟性外,大量代码实例的锻炼也是很有必要的,这本身就是笔者成长的一个过程,也是本文中笔者竭力要给大家展示的。
保护 XML Web 服务免受黑客攻击
在与开发人员就 XML Web 服务的将来谈话的过程中我们得知,他们最大的担心之一就是害怕软件中存在的弱点可能使服务受到不怀好意的用户的攻击。这可以说既是一个坏消息,又是一个好消息。说它是坏消息,是因为攻击可能导致服务的可用性受限制、私有数据泄露,更糟糕的情况是,使计算机的控制权落入这些不怀好意的用户的手中。说它是好消息,是因为您可以获得一些真正的保护,以减少这些攻击所带来的风险。我们将介绍已出现的攻击类型,以及您如何保护自己在部署、设计和开发领域的心血。此主题的第一篇专栏文章将集中讲述部署时应考虑的问题,下一篇专栏文章将讲述在开发 XML Web 服务时需注意的设计和开发问题。
攻击类型
要找出风险所在并了解如何避免,第一步应了解服务可能遭受的攻击类型。在了解了可能遇到的问题种类后,就可以采取适当的措施来减小这些问题所带来的风险。
攻击通常可分为三大类:
欺骗
利用错误
拒绝服务
欺骗
在要求身份验证的系统上,最常见的黑客攻击之一是算出某个用户的身份验证证书,以该用户登录,然后访问该用户的信息。这已经很糟糕了,但如果被泄露的证书属于系统管理员或其他某个具有更高权限的用户,则风险会更大。因为,在这种情况下,攻击可能不仅限于泄露单个用户的数据,而且有泄露所有用户数据的可能。
黑客可能会使用多种方法来确定用户的密码。例如:尝试对该用户有意义的字,如该用户的姓名、其宠物的名字或生日。更有恒心的黑客甚至会尝试字典中的每个字(字典攻击)。获取证书信息的其他方法包括:捕捉网络数据包并读取发送的数据中的信息;通过 DNS 欺骗,插入一台不怀好意的计算机,作为客户端和服务器之间的中介;假装系统管理员,以排除故障为由,要求用户给出其证书;或者,记录与服务器的登录握手,然后重复这一过程,尝试通过身份验证。
可以通过采取诸如强制实现加强密码等措施以及使用安全身份验证机制,来缓解由欺骗所带来的大多数风险。
利用错误
决定系统弱点的关键因素之一是运行在该系统上的代码的质量。系统错误不仅仅局限于使某个特定的线程出现异常。黑客可能利用这些弱点在系统上执行他们自己的代码,访问具有较高权限的资源,或者,只是利用可能潜在地引起系统速度减慢或变得不可用的资源漏洞(由错误引起的)。这种攻击中最著名的一个例子就是红色代码蠕虫病毒,这种病毒利用 Index Server ISAPI 扩展中的错误,在受感染的系统上执行它选择的代码,然后继续寻找其他有弱点的计算机。
另外一种常见攻击就是利用输入数据的有效性假设方面的错误。例如,XML Web Service 希望用户名作为参数输入的情况。如果您假设用户名仅包含 ASCII 字符串,并因此将它直接放入您的 SQL 查询,可能会使您的服务出现严重的弱点。例如,假设您的代码中有一个 SQL 查询,如下所示:
sqlQuery = "SELECT * FROM Users WHERE (Username='" & UsernameInput & "')
如果 UsernameInput 参数包含的内容恰好如下所示
Bob') or not (Username='0
那么您的服务可能会返回所有记录,而不只是特定用户的记录。
拒绝服务
拒绝服务攻击的目的不在于闯入一个站点,或更改其数据,而在于使站点无法服务于合法的请求。红色代码蠕虫病毒不仅感染计算机,并继而寻找并感染其他计算机,而且,还使得被感染的计算机向官方的白宫 Web 站点发送大量的数据包。因为数千台计算机被感染,所以发往白宫 Web 站点的请求的数目极高。因为红色代码蠕虫病毒会导致从大量计算机发出请求,所以被视作“分布式拒绝服务攻击”。由于涉及到如此众多的计算机,因此这种攻击极难限制。
拒绝服务请求可能有多种形式,因为可以通过多种级别发送伪请求,以攻击您的系统。例如,您的站点可能允许用户 PING 您的 IP 地址,从而使 ICMP 消息被发送到您的服务器,然后又被返回。这是一种排除连接故障的有效方法。但是,如果数百台计算机同时向您的服务器发送数千个数据包,您会发现您的计算机忙于处理 PING 请求,而无法获得 CPU 时间来处理其他正常的请求。
级别稍高的是 SYN 攻击,这种攻击需要编写一个低级网络程序,所发送的数据包看起来有如 TCP 连接握手中的第一个数据包(SYN 包)。这种攻击比 PING 请求攻击危害更大,因为对于 PING 请求,您可以在必要时将其忽略,但对于 SYN 攻击,只要有应用程序在侦听 TCP 端口(如 Web 服务器),则无论您何时收到看似有效的连接请求,都需要花费资源。
最高级别的拒绝服务攻击可以呈现一种向 XML Web Service 发送多个基本有效的 SOAP 请求的形式,这种请求将导致数据库开始查找操作。数据库查找可能需要花费一段很长的时间。因此,如果每秒钟向服务器发送数千个这样的请求,会使得接收请求的 Web 服务器和后端数据库服务器变得非常忙。而且,这也会使您的服务无法及时处理其他请求。
如果您的计算机上有包含错误的代码,那么拒绝服务攻击会更加容易。例如,如果投入使用的 Web Service 有这样一个错误:当出现某个特定类型的错误时,会显示一个消息框,黑客可以利用这一缺陷向您的计算机发送数目相对较少的请求,使该消息框显示出来。这会锁定所有的线程处理请求,因此有效地阻止了其他人访问您的服务。
部署问题
到目前为止,我们已介绍了几种不同的攻击类型,那么我们能对这些可恶的攻击采取什么措施呢?有一个好消息可以告诉您,您可以采取很多种方法来保护自己的服务,而且,这些保护大都十分简单。让我们首先来看一看只需控制 Web 服务器和后端服务器的配置方法就可以实现的保护类型。
应采取许多重要的保护措施确保您的 Web 服务器不会受到攻击的破坏,包括一些众所周知的措施,如确保具有最新的安全性更新。下面列出了自我保护措施中最重要的步骤。其中的许多步骤并不特别针对托管 Web 服务,而是适用于所有的 Web 服务器托管内容。
安装安全性更新
首先,确保您具有最新的更新,以避免受到红色代码蠕虫病毒的攻击。可以在 Installing the patch that stops the Code Red worm(英文)中找到有关安装更新的说明和下载修补程序的链接。
对红色代码蠕虫病毒的修复以及其他修复最终会包含在 Microsoft? Windows? 2000 的下一个服务包中,并已在 Microsoft? Windows? XP 中得到解决。
当然,更大的问题是如何避免其他潜在的弱点,并保护自己免受将来可能出现的问题的侵害。有关 Microsoft 产品安全问题的信息,可以订阅“Microsoft 安全性通知列表”。对于出现的任何新问题,都将以电子邮件的形式通知订阅者。有关如何订阅的说明,请查看 Product Security Notification(英文)网页。
限制 Web 服务器的访问者
如果您对攻击的问题很关注,尤其是如果您的 XML Web 服务上包含私有信息,那么您应限定仅合法的用户可以访问您的站点。这可以用多种方法来实现,但下面讲述的几种方法可以防止黑客访问您的 XML Web 服务。
通过使用 HTTP 身份验证来对用户进行验证,然后限定他们可以访问哪些资源。身份验证的配置方法:用鼠标右键单击 Internet 服务管理器中的 Web 站点、虚拟目录或单个文件;从弹出菜单中选择“属性”;进入“目录安全性”选项卡,单击“匿名访问和身份验证控制”下面的“编辑”按钮。
限定可以访问您的 Web 服务器的 IP 地址。如果有一些可以使用您站点的合法用户,那么可以只允许这些用户的特定 IP 地址访问您的 Web 站点。您还可以限定某些 IP 地址范围具有访问权限,或拒绝某个 IP 地址或某个 IP 地址范围的访问权限。甚至可以根据域名进行限定,但在与您计算机连接的 IP 地址上,可能需要花费很长的时间来进行域名查找。修改 IP 地址限制的方法:转至步骤 1 中提到的“目录安全性”选项卡,单击“IP 地址及域名限制”下面的“编辑”按钮。图 1 显示了“IP 地址及域名限制”对话框,其中将访问权限限制为三个特定的 IP 地址。
图 1:设置 Web 站点的 IP 地址限制
要求与客户端证书具有安全套接字层 (SSL) 连接。这可能是对访问您站点的用户进行身份验证的最安全的方法。SSL 限制也是在“目录安全性”选项卡的“安全通信”下进行设置。
将路由器配置为仅允许符合要求的访问
路由器就是您的防火墙。它可以阻断发送到您计算机的大量不合法的请求。流行的路由器大多都可以将访问限制在特定的 TCP 端口上,因此您可以只允许从端口 80(默认的 HTTP 端口)传入请求。这可以防止防火墙外的任何人试图连接到您计算机上的其他任何服务。打开其他服务的端口时请务必小心。您可以很方便地从终端服务客户端打开一个端口,连接到您的 Web 服务器,以便进行远程管理。但随后,任何人都可以通过终端服务器连接尝试连接到您的计算机。即便黑客不知道有效的用户名和密码,也仍然可以通过同时建立只显示登录屏幕的多个会话,来用完您计算机上的大量资源。
在筛选掉可能用完您计算机资源的非法数据包时,也需要用到路由器这一重要工具。对于明显存在问题的数据包,只需将它丢弃即可(大多数路由器都会自动执行这一功能)。但是,目前已有许多路由器具有检测诸如 TCP SYN 包的能力,这些数据包慌称它们是从某个 IP 地址发送过来的,但实际并非如此。通过启用这种保护措施,可以避免前面在拒绝服务攻击中提到的那些 SYN 攻击。
而且,请记住,防火墙限制只会影响到防火墙处的流量。这似乎是显而易见的事情,但假定您从 Internet 服务提供商 (ISP) 处购买了一根 T1 线,并在您所在的 T1 线的那一端放置了一个具有安全配置的路由器。如果 ISP 无法在他们的路由器上启用非法 SYN 请求检测功能,那么他们的路由器就有可能受到 SYN 攻击,从而潜在地拒绝对您的 T1 线另一端的服务,最后结果是有效地切断了对您站点的访问。
考虑更复杂的环境,如某个特定连接的两端都放置有多个路由器,由于每个路由器都有可能遭受攻击,因此可能会影响到为您站点的合法用户提供服务。要列出数据包到达您的服务器途中所要经过的路由器,请使用 TRACERT.EXE 实用工具。
配置 TCP/IP 筛选以限制接受连接的端口
如果您没有路由器作为防火墙,或者,如果您由于任何原因无法管理自己的路由器,都可以通过限制您计算机将接收的传入连接种类,有效地使您自己的计算机成为防火墙。在 Windows 2000 中,单击“开始”按钮,选择“设置”,选择“网络和拨号连接”,用鼠标右键单击连接到 Internet 上的网卡,然后选择“属性”。选择“Internet 协议 (TCP/IP)”,单击“属性”按钮,单击“高级”按钮,进入“选项”选项卡。选择“TCP/IP 筛选”,然后单击“属性”按钮。将出现如图 2 所示的一个对话框。您可以在该对话框中限制将接受连接的端口。在如图 2 所示的示例中,限制了只允许在端口 80 和 443 上分别进行 HTTP 和 HTTPS 连接。
图 2:配置 TCP/IP 筛选
删除不必要的服务和软件
计算机上运行的软件越多,就越有可能受到攻击,尤其是在您作为某种具有较高权限的用户运行服务的时候更是如此。如果您的计算机专门运行 Web 服务,且 Web 服务独立于其他服务,那么应在您的计算机上禁用其他某些服务,包括 FTP 服务、SMTP 服务以及诸如终端服务客户端等的 Windows 服务。
也应限制可通过 Internet Information Server 运行或访问的软件数量。确保仅配置了您需要的虚拟站点和目录。首先,应该删除管理 Web 站点。其次,还应该删除 IISSamples 虚拟目录。同样,如果您的计算机专门运行 Web 服务,应删除其他任何虚拟目录。
即便对于已经安装某些软件的虚拟目录,也必须弄清楚哪种软件是访问您的 Web 站点时可以使用的。在 Internet 服务管理器中,用鼠标右键单击某个站点或虚拟目录,从所出现的菜单中选择“属性”,选择“虚拟目录”选项卡,然后单击“配置”按钮,将出现“应用程序映射”选项卡,其中列出了与不同的 ISAPI 扩展或 CGI 应用程序相关联的所有扩展。如果您没有使用这些扩展,请将它们从列表中删除。.IDQ 文件的索引服务器扩展自身存在错误,红色代码蠕虫病毒就利用了这个错误。如果您是在虚拟站点级进行此项更改,那么您不需要为所创建的每个虚拟目录都进行此项工作。
使用 Microsoft Internet Information Server 安全性核对表
Microsoft 为 Internet Information Server 4.0 创建了一个安全性核对表,其中除了我在本文中提到的所有安全事项以外,还提到了其他更多的安全事项。使用此核对表来确保您至少已经考虑了所有的安全性选项。虽然您运行的可能不是 Internet Information Server 4.0(5.0 版是随 Windows 2000 一起发布的版本),但本文中的大多数步骤仍然适用,而且,对于将来 Internet Information Server 版本,也仍然适用。可以从 Microsoft Internet Information Server 4.0 Security Checklist(英文)中找到此核对表。
总结
根据您计算机和网络配置的不同,会有多种保护措施,您应采取相应的措施保护您的 Web 服务器免受黑客攻击。在下一篇专栏文章中,我们将研究开发人员和设计人员在创建其 XML Web Service 时需注意的问题,并继续探讨保护您的 XML Web Service 免受黑客攻击的方法。
如果你曾经使用过RegOpenKeyEx、RegCreateKeyEx、RegCloseKey等Win32 API函数读写过注册编辑表,你肯定非常熟悉这些复杂的Registry函数。相反,在.NET框架中,Registry和RegistryKey类提供了对Windows注册编辑表的控制,通过这些类你可非常容易地对注册编辑表进行读写。
这些类被定义在Microsoft.Win32命名空间和mscorlib.dll装配中,使用这些类之前,你必须使用using声明这些命名空间。
#using
using namespace Microsoft::win32;
Registry类只有七个字段成员,使得你能够存取注册编辑表中七个特定的键,这同你在注册编辑表中打开一个键非常类似,这些所有的成员均返回一个指向注册键的指针。
ClassesRoot 对应于HKEY_CLASSES_ROOT
CurrentConfig 对应于HKEY_CURRENT_CONFIG
CurrentUser 对应于HKEY_CURRENT_USER
DynData 对应于HKEY_DYN_DATA
LocalMachine 对应于HKEY_LOCAL_MACHINE
PerformanceData 对应于HKEY_PERFORMANCE_DATA
Users 对应于HKEY_USERS
比如你想读写HKEY_LOCAL_MACHINE的数据,你可以先获得一个指向该键的指针。
RegistryKey* pRegKey = Registry::LocalMachine;
接着调用RegistryKey的OpenSubKey成员函数,然后再调用GetValue即可获得一个特定的字符串。
pRegKey->OpenSubKey(L"SOFTWARE\\Kruse Inc\\Version");
Object *pValue = pRegKey->GetValue(L"kWise");
如果你想设置一个键的值,则需要调用它的SetValue函数。
pRegKey->SetValue(L"kWise", "some Value Here");
删除一个值可以使用:
pRegKey->DeleteValue(L"kWise");
其它常用的成员函数还有:
DeleteSubKey 删除一个子键
CreateSubKey 如果键已经存在就打开该键,否则就创建新键
DeleteSubKeyTree 删除子键及其节点
下面是程序代码例子(Microsoft Visual c++.net beta2调试通过):
#using
using namespace System;
using namespace Microsoft::Win32;
// 这是应用程序的入口点
int main(void)
{
RegistryKey * pRegKey = Registry::LocalMachine;
pRegKey = pRegKey->OpenSubKey(L"HARDWARE\\DESCRIPTION
\\System\\CentralProcessor\\0");
Object *pValue = pRegKey->GetValue(L"VendorIdentifier");
Console::WriteLine(L"本机的CPU为: {0}.", pValue);
return 0;
}
比尔·盖茨在微软开发者成功之路大会上的主题演讲
今天我到这儿来感到非常兴奋,因为能有这个机会与大家分享软件业在未来十年的远
景。我特别想强调的是:全球软件开发正在经历迅速的变化,同时,我也想特别指出,
这些骤变对中国的发展带来了多么巨大的机会。
微软对于计算的看法不同于其他传统的公司。我们认为计算应该是低成本的。我们认为
应该有一个大的软件工业能够开发很多的应用,并且这些应用的价格应该是很合理的,
这一模式当然在过去已经被证明取得了令人难以置信的成功。在全球,计算机已经成为
提高生产力的最佳手段,同时,它也是人们之间进行通讯的最好工具。在中国,因为个
人电脑的成本比较低廉,计算机市场正在以每年1000万的速度递增,而其中大部分电脑
是由中国本地的厂商制造出来的。中国信息产业发展的下一个阶段的重点是要极大地扩
展中国在软件方面的经验和技能,包括开发中国企业自己的应用程序,以便使中国企业
获得成功,并从中脱颖而出一批能参与世界市场竞争的本地公司。在这方面,微软有一
个强有力的承诺,那就是通过跟你们分享Windows平台上的经验,帮助你们取得成功,
这对中国也是一个很大的机会。
软件业在个人电脑出现以后,已经经历了巨大的发展。软件业发展之初,全球只有几百
家软件公司,他们的产品比较少,价格比较高。然而,在过去二十年当中,涌现出了几
十万家软件公司,他们开发出了令人难以置信、多种多样的产品,可以适用于几乎所有
的行业。因此实际上,软件驱动了人们对技术的使用,软件在提高生产力方面功不可
没。
过去几年来,有人开始问,软件是不是已经穷尽了它的潜力,是不是还能够有更多的潜
力可以挖掘?我的答案非常简单:我们其实才刚刚开始计划,刚刚开始来探求软件的巨
大潜力。过去二十年软件方面的工作与今后十年软件所能够做的工作相比,只是很小的
一部分。为了尽可能以更快的速度向前进展,我们需要充分利用中国丰富的人才资源,
同时确保他们获得所需的教育、模板、工具,以便能够人尽其用。
微软本身为了保持其领先地位,一直在采用最新的开发方法。我们总是愿意抛弃旧的方
式,创造新的方法。这方面很好的一个例子就是在80年代,我们把公司前途的赌注放在
了图形界面上。当时,很多人认为老的界面非常完美,但是我们知道图形界面要好得
多,所以我们所有的开发工作都采用了那个方法,所有的开发都围绕着Windows进行。
在随后的五年当中,我们的方法被证明取得了很大的成功。现在人们也许开始反思:为
什么当时有那么多争议,为什么当时有那么多的人对图形界面的重要性提出了质疑。
而现在,我们又开始了另一个过渡,这个过渡和我刚刚提到的那个过渡非常相似,这种
转型的关键在于:软件将使不同的计算机以不同的方式相互交流,人们使用互联网的方
式将与我们过去五六年使用互联网的方式大不相同。今天,互联网的一切实际上是都是
围绕html展开的,我们用浏览器来找一个网页,来看一看那个网页上的资源,这当然是
很了不起的事情,但是它的局限性非常大 -- 用户一次只能看一个网页,他们自己不能
够做任何有创造性的工作,而只是读一些信息。如果你想把不同的数据组合起来,那么
其中的手工性是非常强的,也就是剪贴、粘贴,然后把网页上的数据粘贴汇集到微软
excel上。如果你想把这些数据汇总起来与大家共享,就没有特别现成的方法。
当然,html仍然是一个重要的标准,但是,现在出现了一个新的标准,这就是微软推动
的一个新的标准 -- XML。我今天要讲很多关于XML 的内容,因为在技术层面上,XML是
下一代产品的关键组成因素。微软的.Net战略是依存于XML的,就象我们以前的产品依
赖于图形界面一样。微软将把XML变成整个业界的标准,而微软.Net战略的实施会成为
最好的XML的实施案例,就象过去Windows是图形用户界面最好的实施案例一样,.Net战
略在某些方面是一个全新的理念。在.Net之前,软件是围绕一个系统写的,我们当时是
考虑一个系统而不是考虑用户来写软件的。如果用户换一台PC的话,他们要做很多的工
作才能把他们的文档、他们喜欢的东西、他们的信息转到另一台PC上;如果他们想用另
外一种终端工作,比如一种先进的电话或者手持便携设备,他们要运行一些协同软件以
便让这两种不同的装置一起工作;如果用户想通信的话,他们可能用不同的电子邮件系
统 -- 不同的装置会有不同的电子邮件系统,要由用户来把这些不同的东西结合到一
起。
但是.Net的出发点是:我们不能够把系统当作关键因素,诚然,会有不同的系统,但是
它们应该能够自然地在一起工作。所以,我们把用户看作是这个理念的中心,在服务器
层面,我们不把某个应用单纯地看作是在一种服务器上的一种应用,我们认为这个应用
可以用很多的服务器,并且能够自动地利用多个服务器带来的扩展的、更强的功能。以
人为本的理念保证了由此产生的生产力和可靠性会超越大型机时代或者是UNIX时代的最
好的应用,它所带来的巨大的可扩展性使得我们有很大的余地,这样,我们只要不断把
新系统加进来就可以了,我们就有了更大的能力。在一些地方,这已经成为现实,比如
说为Windows平台设置的用于交易的TPCC基准,它的功效更为强大,同时性能价格比更
加优越。因此,.Net是一个巨大的变化,它是编程方面的巨大变化,也是用户界面的一
个巨大变化,它使用户界面变得更加自然,它也使服务器有了巨大的变化,因为服务器
使编程更为容易。
所以,其实我们下了一些赌注,我们对这些赌注非常有信心。首先XML会成为新的标
准,XML能够使各种程序在互联网上协同运行、互相沟通,任何终端装置的智能都能被
充分利用。有了XML标准,两个从未没过面的人写的程序也能够协同工作。例如,在电
子商务中,有人要买你的货,以前处理订单的复杂过程 -- 跟踪整个货运过程、作货物
质量和价格变化跟踪等复杂的状况,都可以用先进的XML标准,提高效率。
第二个赌注就是人们会利用新的工具,比如说Video Studio .Net 这样的的新工具来写
一些应用,这些应用能够以新的方式来运行,这些新的应用增强、升级更加容易,因为
XML使得软件的结构组建来得更加简单,因为XML使过去要写的很多编码不再必要。很多
为网站写的应用会更好,其实人们真的希望能够更快地建设网站,让网站有更高的可靠
性和灵活性,并且能够支持XML,他们使用这些新的工具获益匪浅。
最后一个赌注就是我们可以让用户界面更加自然。就是说你坐在那儿浏览信息的时候,
你不仅能够使用键盘,你还能够使用一支笔来手写,也就是说有手写识别的功能。你还
可能用声音来操作,就是说电脑有语音识别系统,你所需要的信息将展示在屏幕上,并
且分辨率极高,使得屏幕的可读性非常强,即使是一个比较长的电子邮件也不需要打
印,这种用户体验对于扩展PC的作用是非常重要的,对于完全采用数字的方法也是非常
重要的。这些都是.Net战略所推动的。
我想给大家讲讲XML的背景。XML实际上来源于XTML的一个结构性的文件。早在1996年,
微软的一些工作人员在和一些其它公司举行的标准会议上说,让我们把这一些总体化。
我们不要单谈文件,我们应该谈任何数据,这些数据可以是比较丰富的。当然在正常的
数据世界中,所有的一切都是使用Tabular形式的,当然这种方法对于适合这种格式的
数据来说是非常有价值的,但是它的灵活性不够。过去计算机界一直有一个梦想,就是
说应该有一种更灵活的数据模型,人们在对象数据库、网络数据库方面做了很多工作,
现在有史以来第一次我们有信心,我们相信这种XML的方法可以在数据库的核心部分加
以使用。XML是一个深刻的变化,虽然你不可以用现有的应用,把XML放到它的周围来使
用,但是XML的所有好处只有当人们从零开始来使用它的时候才能充分体现出来。如果
在某些系统上建立XML的一个层,就会给其它的系统建立崭新的系统。
对于微软本身而言,我们运用我们的操作系统、数据库、SQL Server、Office应用,在
上面做一些基础设计上的推动,以促进对XML的需求。对于数据库来说,这就意味着XML
在中心,而不光光是表格。对于微软Office来说,它意味着象Excel这样的产品能够理
解XML的设计,所以当人们在做预测和规划的话,他们就不是单纯地在表格中做剪贴和
粘贴,相反,统计表格理解不同大小、不同形式的方案。因此XML涉及到微软的很多工
作,这就是为什么这对我们来说是一个大赌注。
我们大概是在三年多以前开始迈上条路的,我们建立了Video Studio .Net,之后几个
月我们把它推向了市场,我们对它的设计是围绕XML来进行的,我们接触了标准制定委
员会,也邀请其它公司参加进来。在过去的几年当中,XML的势头有了巨大的增长,实
际上我可以说,毫无疑问,这会成为所有计算机系统的唯一的新方法。我想说,XML确
实能够跨系统工作,微软极力把它建立成为标准的最终目的就是为了让业界受益,而微
软将通过提供最好的性价比的平台来参与竞争。
当然XML的互操作性就意味着即使有人在过去没有用过我们的平台,在新的应用环境
下,他们也可以运用我们的平台和其它的设备顺利协同工作,这样能够让人们以演变式
的方式来往前走。在现有的层的标准当中,XML是最高一层的,这是第七层协议上的应
用,它是在所有的其它标准之上的,因此下面一层的TCP/IP等等都是在下面。如果你确
实想发送一个演讲的数据的话,XML能够支持,但是对于互联网的编程来说,我们有两
个交换程序数据,我们在这方面专门使用XML。我们正将所有有关XML的标准展现给大
家。确实有许多与此相关的缩写,但有一点是很明确的,我们有一个基本的标准能够让
这些新的应用出现、使用。对我们来说,一个巨大的里程碑就是Video Studio .Net的
正式出货,实际上数据时代所建立的应用已经是很让人惊奇的。为了确认我们走的路是
对的,我们进行了一些比赛、调研,来看人们是怎么使用XML的,事实表明,人们对XML
的反馈还是非常积极的。
XML是一种格式,它让数据容易理解,它让数据具有灵活性。比如说,你想销售某个产
品。并不是所有卖某类产品的人都用同样的表格格式来说明产品,他们可能有不同的选
择。XML能够让产品说明的共同部分匹配起来,而所有产品的独特特征也能够包括进
来,这就说明为什么它是一个很大的突破。以前,系统集成商写的很多的编码只是把一
个系统的数据跟另一个系统的数据联结起来,现在这个数据有了这种自我说明、容易理
解的方案,我们就不需要很多刚才说到的编码了,系统集成商以前写编码的许多精力都
可以用来进行数据发现、数据挖掘、提高数据可靠性,降低建立这些系统的成本。
微软一直都支持追求最好性价比的想法。现在人们已经意识到他们可以享受这些好处,
并且可以建立低廉、渗透性非常高、灵活的服务器,而在过去,只有非常昂贵的服务器
才有这些特点。
我们有很多的合作伙伴已经开始用XML服务的应用。我把其中的一些合作伙伴列到屏幕
上,大家可以看到这种势头是不断增强的,所有生产应用软件的公司都认为需要采用新
的XML的方法。微软在推动XML方面是首屈一指的公司,其它有一些大公司,如IBM,也
在参与、应用这个标准。我们确实有自己的实施方案在市场上存在,我们也为其他基于
XML的系统在互操作性方面提供测试。
过去,许多大学里的人很希望看到这方面的进展,看到互联网不仅仅是一个观看文件和
图像的工具。今天,人们惊奇地看到,一切正以极快的速度发生。
实际上,.Net的实施包括在客户机、个人电脑、在小屏幕设备上,如PDA和电话上运行
的代码,.Net包括了服务器的概念,这些服务器能够执行丰富的存贮任务。同时,.Net
也包括了服务的概念,这些服务是在互联网上实时提供的,我们把它叫做服务。比如
说,你想确定某人的身份,怎么做到呢,必须有这么一个永远运行的程序,这个确认的
服务就叫护照。同样,如果你需要与别人共享文件,或者你想备份信息,我们不想专门
设一个专门的服务器,所以微软围绕.Net建立了一些服务,我们自己提供这些服务,其
它公司也可以提供这些服务,而且这些服务在互联网上随时运行。所以在互联网上,不
管是股价、还是商业的预测,很多人都在发布网络服务,这是一种商业模式,有时候这
些网上服务是免费的。所以,实际上,服务器和服务都是在同样的架构上建立的,它们
可以相互转换。
微软参与中国的市场很长时间了,我们不断进行投资,这不仅仅是投资的数额问题,这
是一个基本愿望的问题。我们希望中国的软件设计人员能够真正成为同业领先者,而不
会与其国际同行有太大差距。我们工作的意愿还是非常令人瞩目的。处于以上的良好愿
望,我们在北京的研究人员同全国的许多大学都在进行合作。但是,我们开始讨论如何
能够以更快的速度将软件设计的方法传播给更多中国领先的软件公司。我们设计了一个
项目,叫Architect 2000,就是要在中国培训两千个中国的软件设计师,告诉他们设计
软件的方法、怎么主持项目、怎么把项目分成不同的部分,以确保能够进行局部和系统
的测试,最后当它们凑在一起时能够成为世界级的应用。教室培训只是这个项目的一部
分。我们还有一些在线的社区,能够提供持断的支持,来确保这些最新的设计方法为让
大家所熟悉,最终使这2000个设计师取得成功。我们希望他们能够把这种理念和方法再
传播给别人,对中国软件设计结构和软件设计经验产生更宽广的影响。我们觉得这是一
个非常好的合作项目,这确实是一个双赢的局面。
我刚才提到,我们在全球范围内尝试看开发人员是否对.Net这种新方法感兴趣。在亚
洲,我们组织了一个竞赛,从各个国家选出最好的有关.Net的工作。昨天,我在汉城给
选出的亚洲最好的.Net应用颁奖,我非常高兴地了解到,中国获得了这个竞赛的大奖。
实际上,我们有一些参赛的应用非常不错,前三名都是非常好的例证,证明我们为什么
对.Net如此感兴趣。今天,前三名获奖的小组组长有两个会来给大家做演示。现在,我
们请来自北京理工大学的同学给我们做演示。
演示:.Net在企业工商登记中的应用(略)。
下面我们有请南开大学的小组给我们演示。
演示:.Net在网上智能订书中的应用(略)。
大家都看到了这些激动人心的事情。首先,这些开发人员的热忱和能力不断超出我们的
想象。这些应用使我们看到为什么XML是很重要的。XML给我们提供了一种灵活性,使我
们可以把应用分成不同的服务,而且可以通过不同的设备使用这些应用。它并不要求大
家非得搞一个单一的界面,实际上从每一个界面进入都会获得丰富的功能,所以我希望
这些开发小组会传播我们的XML和它能带来好处。
我再介绍一下未来人们会如何使用PC,给用户更好的体验。我是首席软件设计师,这个
职位很重要的一件事就是看技术的发展,理解我们的用户想看到什么新东西,看软件怎
样可能把两个方面结合在一起。我们以前的工作都是如此,不管是个人电脑还是微软
Office,不管是新的东西,比如说Pocket PC、极顶盒游戏等等。
微软本身是不生产硬件的,我们只是跟踪硬件发展的情况。硬件都是我们的合作伙伴做
的,象英特尔公司,他们生产更快的微处理器,象存贮公司,它们在不断提高存贮的能
力,象光纤公司,他们要迅速提高信息在光纤上的传递速度。这些硬件的发展给软件提
供了机会,每过几年,都可以让软件有一个新的提升。实际上,我觉得硬件的发展可以
提供软件所需要的东西,比如说新的液晶显示屏,它的分辨率已经让你阅读起来感觉非
常舒服了,这样就不用看纸质的书了。硬件商也给无线网络提供了很好的发展基础,特
别是现在,人们可以在任何地方建立起11兆的传输能力,每分钟的通信成本也很低。在
全球,每个商业单位、每个酒店、每个机场 -- 所有人们花很多时间的地方都会提供这
种传输能力,任何便携机都会有内置的上网功能,从而获得高速链接。
我们认为,硬件商目前最难做的事情就是把宽带接入到家庭中去。宽带技术在每个国
家,包括中国,发展都很快,但是宽带在家庭的应用还是比较慢的。在过去的几年中,
我们已经看到了宽带在韩国、日本,还有其它一些国家的实质性进展,但是要让绝大多
数人都用宽带上网,我觉得还要五到十年,这其中有价格的问题。微软作为推动宽带发
展的公司之一要促进这方面的需求。
未来,硬件创造的奇迹会将我们带向何方?未来十年,我感觉应该是数字时代,也就是
说大家会真正理解到:数字的方式是做事最好的方式。我会讲介绍一下未来个人电脑的
使用方式和现在完全不同。
首先是能够提高生产力的软件。大家会说,人类在这方面已经取得了巨大的进展,而事
实上,我们的进展还不够大。现在,我们可以使用提高生产力的软件做商业计划、跟踪
客户活动,但我们可以做得比现在好得多。使用未来提高生产力的软件,如果你想共享
信息,你可以确切地控制把这个信息跟谁共享。大家不用学习Office的不同模式,而是
使用一套统一的命令程序,所以他们不会感觉说他们在使用不同的应用。
语音识别和手写识别也应该有很大的改进,从而提高生产力软件的功能。我们可以有一
台便携机,电池寿命很长、无线通信效果也不错,还有一个高清晰度的屏幕。我们有一
个梦想,希望能够使屏幕阅读舒服一点,可以做到无论读电子邮件还是百科全书,都不
用再靠纸介质的书了。要做到这点,我们必须要改善数据的形态,不然的话,绝大多数
用户收到较长的文件都会把它打印出来。只要计算机是固定在一个位置上的,要让人们
长时间看屏幕是很疲倦的事。如果我们改善数据的形态,可以将它随时输出到便携设备
中,象看一本书或杂志一样随时阅读,人们就不会分神,老是去注意阅读过程中机器的
因素。有了这种便携设备,我们就可以把各种文件带到会议上,在文件上批注,开完会
以后可以再来看笔记、与别人共享。
现在我来谈谈通信。电子邮件、即时聊天和信息传输、可视会议等,实际上都是在考虑
把个人电脑提升作为一种通信装置。这方面的增长是爆炸性的增长。在和别人进行沟通
时,人们不光希望只有声音的交流,他们也希望在屏幕上为对方展示文件,可以调出一
个预算表、一个名单、一份图纸,或者调出正在讨论的有关信息。没有什么理由说我们
在给别人打电话的时候不能把电话和屏幕连接起来。我们完全可以有一个单一的界面,
普通用户可以用它看到日程安排,商业用户可以用它看价格,可以在上面指向正在讨论
的账单来看一看哪一方面有分歧。因此,这个屏幕在所有的通信方面的作用都是非常、
非常重要的。
我们做的一切就是,我们不想迫使人们必须有不同的电话号码和邮件地址,我们希望不
同的装置能够相互通信。今天,实际上用户经常被他们的电子邮件和电话打断,而这些
不一定是至关重要的事。因此,我们想把所有的地址都合并成一个地址,让用户自己控
制他想注意哪个电子邮件、哪条信息,这样他们的时间和宝贵资源就可以用在刀刃上。
这样电子邮件的整体体验将会非常不同,电子邮件与即时聊天和信息传递之间的界限会
消失,人可以作为主导。
再以会议为例,人们可以有360度的摄像机来录制会议,然后把它放到网上,没有亲自
参加会议的人可以看到会议的情况。这在今后会很常见,人们可以远程参加会议,极大
地提高会议的效率。
当然,许多互联网的许多梦想,如电子商务、电子政府等商业解决方案还没有完全实
现。有人说,这些问题很快会解决。但是,要使电子商务取得成功,就必须使用XML的
解决方案。因此,政府的应用也会使用这种方法、企业想在全球进行销售也会采用XML
的方法,厂商想找到更多供货商或更好的价格、跟踪客户的满意度等,所有这些使市场
经济更好运作的努力都会使用XML应用。
对于消费者来说,如何建立音乐文档,不是用CD或唱片,而是采用数据的形式,将会变
得很普通,操作也更简便,你能用随身的任何装置来得到这些东西,你可以用你喜欢的
方式编辑它;如果你的朋友感兴趣的话,你可以给他发一个样本,如果他实在喜欢,你
们可以通过在线交易买卖。图片、音乐、电影都会变成数字的形式,人们那时就会感到
奇怪,以前为什么要用唱片这种非常繁琐的东西。届时,你如果把图片发送给朋友,你
还可以描述一下当时拍照的情况,而不仅发送图形本身。
在起居室里,我们可以改善电视体验。人们不一定必须在特定时间看特定节目,我们可
以帮助随时选择他们看想看的节目。我们也可以用三维图像让人们玩交互式游戏,这些
游戏的效果比目前的要好上百倍,这样不仅年轻的男孩喜欢玩游戏,各个年龄段的人都
可以玩游戏,游戏也可以履行一些教育方面的功能。在这方面,电视也是比较符合XML
范畴的,我们也可以用XML来提高电视的应用范畴。我们目前的X-box就是未来产品前奏
曲。
我们认为这些装置能够互相补充。全屏幕的装置能够成为所有装置的中心装置。我们认
为键盘还会继续存在下去,但是除了键盘以外,还会有其他的装置来帮助人们从系统当
中获得新信息。我们认为中央化的系统中的方法和对等的方法应该是相互补充的,因
此,它所有的一切都围绕着不同的用户体验。微软也会建立一些展示性的应用,
Microsoft Office可能是这些应用当中最重要的应用,它能够展示什么是XML、工作流
是怎样的,它能够使用什么样的工作流。
我们必须保证所有的这一切使用起来都非常简便。通过语音和手写识别技术,然后通过
提高它们的可靠性,人们就不会再害怕使用这些设备了。如果你在使用电脑时遇到问题
的话,你就可以很快给微软提供反馈,然后我们来看你的问题,对它进行分析,如果是
软件存在共同的问题的话,在这个问题影响到其他人之前,我们可以通过互联网通知所
有用户,这样就不会有几百万人同时碰到同样的问题。
微软正在进行大量的投资,事实上,我们长时间以来一直在进行着大规模的投资,我们
大概在十年以前就开始这样做了,这就是为什么我们现在已经看到比如说语音识别技术
将成为一种主流技术。我们的长期承诺就是为IT的持续发展作出贡献。
最后我想说,这里确实是有一个很大的、全球性机会。现在非常缺乏软件方面的技能,
每个国家都应该发展这些技能,来满足一个国家内部应用方面的需求,并且应该有能力
向其他国家提供产品和服务。
中国在其中可以发挥巨大的作用,因为中国有丰富的人力资源,关键是人们在教育投入
多少精力和多大兴趣。今后十年,如果中国的软件技能有所增加、软件公司和软件工作
人员能够大幅度增加,中国的发展会比任何其他国家都快。你们优势是,你们的开端是
从头开始的,没有旧时代的负担,你们一开始就可以采用最新的办法。微软可以做很多
事情来给你们提供帮助,保证大家都能够获得有关的信息和机会。我们认为我们在这方
面有责任,也应该承担责任,其中一部分的责任也反映到了我们在中国所作的承诺上。
现在我们在中国已经有800多人了,工作人员的数量还会有很大的增加。这样,我们的
人员既从事软件开发工作,也和中国的合作伙伴合作。
我希望大家通过我今天的讲话了解到我们有什么样的热情。我一开始是工程师,并不是
搞软件的,我之所以开办一家软件公司是因为我喜欢软件,我也知道大家过去有一段时
间要坐下来写很多的码来保证软件没有bug。我们已经改进了各种工具,帮助成千上万
的开发人员做很好的工作,他们的工作也会影响到上千万、上亿的用户,因此把个人电
脑连接到互联网上的想法实际上是革命性的,它能够极大地提高生产力。我们实际上在
这方面才刚刚开始,我们期待着和在座的所有人进行合作,来抓住这一新的机会!