Crazy ITer

 
Motto
It is strange that all great men should have some little grain of madness mingled with whatever genius they possess。
---------Moliere

When looking up, one has no occasion for shame before heaven, and below, one has no occasion to blush before men。
---------Mencius

The mechanic, who wishes to do his work well, must first sharpen his tools
---------Confucius

Python
SQL
UML
Zope&Plone
Ajax & Script
Jabber
Linux
Google搜索技巧大放送
2007年1月27日星期六
如此多的Google搜索技巧,许多确实没有掌握。勤学苦练,早日进入自由世界。:)

  • Google搜索技巧(1):巧用Google�D像搜索 (2006-07-12 15:40)
  • Google搜索技巧(2):Google桌面搜索(2006-07-12 15:44)
  • Google搜索技巧(4):用Google找自己(2006-07-12 15:59)
  • Google搜索技巧之(5):我�垭�影(2006-07-12 16:01)
  • Google搜索技巧之(6):Filetype(2006-07-12 16:06)
  • Google搜索技巧(7):搜索你的世界之最(2006-07-12 16:15)
  • Google搜索技巧(8):英文歌曲攻略(2006-07-19 17:49)
  • Google搜索技巧(9):Google快���湍阌^察�����κ�(2006-07-19 17:59)
  • Google搜索技巧(10):�s小搜索����(2006-07-19 18:01)
  • Google搜索技巧(11):Google�湍闵洗��W(2006-07-19 18:10)
  • Google搜索技巧(12):八月游指南(2006-07-27 16:09)
  • posted by William @ 02:18   0 comments
    Jabber协议中的注册和销号
    2007年1月22日星期一
    注册一个用户:
    <iq to=' casetrade.com' type='set'>
    <query xmlns='jabber:iq:register'>
    <username>xiaohui</username>
    <password>name</password>
    </query>
    </iq>
    销掉一个用户:
    <iq to='casetrade.com' type='set'>
    <query xmlns='jabber:iq:register'>
    <remove/>
    </query>
    </iq>

    往user.casetrade.com中注册一个用户:
    <iq id="jcl_13" to="user.casetrade.com " type="set">
    <query xmlns="jabber:iq:register">
    <jid>crazyiter.blogspot.com@casetrade.com</jid>
    <nick>william</nick>
    <fullname>email@hot.com</fullname>
    <company>qiye</company>
    <email>email@hot.com</email>
    <domain>daybao.com</domain>
    </query>
    </iq>

    从user.casetrade.com销掉该用户:
    <iq to='user.casetrade.com' type='set'>
        <query xmlns='jabber:iq:register'>
              <remove/>
        </query>
    </iq>
    posted by William @ 10:17   0 comments
    Jabber Client Programmer's Cheat Sheet


    The Jabber Client Developer's Cheat Sheet

    Jens Alfke

    rev. 4 :: 25 May 2001
    (Copyright & license at end of document)

    Contents

    1. Introduction
    2. Jabber Sessions
    3. Logging In & Out
    4. Declaring & Receiving Presence
    5. Managing Your Roster
    6. Sending & Receiving Messages
    7. Chatting
    8. File Transfer
    9. Registering NewAccounts


    1. Introduction

    This is an informal tutorial guide for the perplexed developer trying to implement a Jabber client from scratch. Two weeks ago that developer was me; now, after many experiments and confused e-mails to jdev, I'm dropping pearls of my great wisdom to help you out. You're welcome!

    Your need for this document will be less if you use one of the many available Jabber client libraries like libjabber or JabberCOM, which do a lot of this work for you. However, the libraries typically don't do all of the work of managing a real Jabber session (libjabber in particular is pretty low-level) so you will still need to do part of the work described here.

    This document is a commentary on, not a substitute for, the Jabber Protocol Overview [JPO] or Jabber Programmer's Guide [JPG]. Since those documents are (in their present incarnations) arranged more as references than introductions, the tutorial overview here should help you in navigating them. I've included pointers to relevant sections in the official documentation, to which you can refer for more details. (See also the References section at the end of this document.)

    In particular, I'm going to assume you've already read enough of the official documentation to know how <iq> tags work, since you'll need to send and receive one to be able to log in. See [JPO 1.5].

    There are still (as of revision 2) several blank spots. There's a placeholder section on chat that I hope to fill in soon (once I implement chat in my own client!) There's no mention at all of transports and bridging to other instant messaging protocols; frankly, I'm unlikely to address this myself since I'm not especially interested in that area.


    2. Jabber Sessions

    A Jabber session runs as a single continuous TCP connection on port 5222, or on 5223 if SSL encryption is used. (Even if your client supports SSL, the user should be able to disable it, since not all Jabber servers support it.) It remains open for as long as the client is logged in.

    XML elements as commands.

    The data stream sent in each direction over the connection forms a continuous XML document whose outermost element is <stream>, although the document of course isn't finished until each side logs out by sending the final closing </stream> tag. Within the outer <stream> elements, sub-elements are sent in both directions as commands, containing their own attributes and nested elements to indicate parameters.

    The two sides of the connection are asynchronous. Unlike older protocols like POP, after you send a command you don't have to wait for a reply from the server before sending another. Moreover, the server may send you notifications at any time (for instance, if one of your buddies logs in or out.) You should always be prepared to handle any incoming command.

    Then how do you identify the reply to a particular command? The "id" attribute is used to associate commands with replies. A command you send should include such an attribute with a reasonably unique value; the response from the server will contain a matching attribute with the same value. Internally you can use a hashtable to remember the IDs of outstanding requests and associate them with any necessary data. (A particularly flexible technique is to store a pointer to a callback function or method that you'll invoke when the reply arrives. This really helps simplify your flow of control.)

    Parsing XML.

    The most complex part of a Jabber implementation is parsing XML. Fortunately, there is no need for you to code it! XML parsing is a very standard task and there are many excellent general-purpose libraries you can use as-is. Personally, for C/C++ based clients I recommend expat, which I've used in my own client. Expat was written by one of the designers of XML, it's widely used, fast and compact, open-source, and comes under a very liberal (MIT) license that allows it to be used even in commercial closed-source software. (C++ developers preferring an object-based API might consider Apache.org's Xerces.) Other excellent libraries exist for C/C++ and for other popular languages like Java, Perl and Python. Note that you need a parser that can read from a data stream and parse it incrementally as the data arrives; a parser that has to be fed an entire XML document in one chunk won't work for Jabber.

    Writing XML.

    Some libraries, like expat, only handle parsing XML, not generating it. Fortunately of course the latter is a lot easier, although you'll still find that it makes your code a lot cleaner and more robust if you abstract this out into a simple API like writeOpenTag(), writeText(), writeCloseTag() instead of littering your source with zillions of string literals filled with angle brackets! This is particularly valuable since XML syntax, unlike HTML, is extremely strict, and the server's parser may reject your input and cause you to be disconnected if you forget a single quote character.

    A couple of XML reminders: Tag and attribute names are case sensitive. All attribute values need to be enclosed in quotes. Attribute values, and text content outside of tags, need to escape the magic XML metacharacters, which are:

    < > & ' "

    These need to be replaced with XML entity references which begin with "&" and end with ";". You can use the Unicode value in hex, in the format "&xnnnn;", or the special entities:

    &lt; &gt; &amp; &apos; &quot;

    Also remember that non-ascii characters need to use the right encoding. Your stream's <?xml> header should specify your platform's native encoding, so you don't have to do any work to translate non-ascii characters.

    For lots more info on XML syntax, see the XML books in the References section.


    3. Logging In & Out

    This section assumes the user already has an account on the server. Jabber can also be used to register new accounts; see section 9.

    Opening the session.

    Once you've opened the socket on port 5222 (or 5223 for SSL) you need to send a standard XML header and open the <stream> element that encloses the entire session:

    <?xml version="1.0" encoding="UTF-8" ?>
    <stream:stream
    to="jabber.org "
    xmlns="jabber:client"
    xmlns:stream="http://etherx.jabber.org/streams">

    That's enough to wake up the server. Get the input stream of the socket hooked up to your XML parser. The server will send something like this:

    <?xml version="1.0" encoding="UTF-8" ?>
    <stream:stream
    from="jabber.org "
    id="39ABA7D2"
    xmlns="jabber:client"
    xmlns:stream="http://etherx.jabber.org/streams ">

    Your XML parser will call you with great excitement to tell you that it found the opening tag of a <stream> element. Now it's your turn to log in...

    Logging in.

    The server is now waiting for you to authenticate yourself; it won't let you do anything else until you do. You use an <iq> query [JPO 1.5] to send your credentials, and the server will send a reply to this query to tell you whether you were successful. [JPO 1.5.3.3]

    The query uses the jabber:iq:auth namespace and includes <username> and <resource> elements to identify the user's Jabber ID and the resource name of this computer. [JPO 1.6.3] Many clients seem to use the client name (e.g. "WinJab") as the resource; but this isn't very informative for buddies and isn't condusive to unique resource names. I recommend prompting users on their first login to choose a descriptive name for this computer or location ("Home", "PowerBook", "Naomi's Office") and storing it as a local preference.

    The query also has to include the authentication payload, of course. You can either send the raw password in a <password> element, which is obviously discouraged, or more securely as an encoded digest in a <digest> element. The digest is computed by concatenating the session ID (sent by the server in the "id" attribute of its <stream> tag) with the password, running this string through the SHA-1 algorithm to generate a 20-byte hash, then converting the hash into a 40-character hex representation. [Most Unix libraries include secure hash functions, and you can find a GPL'd C implementation in the libjabber sources.]

    Having sent the login query, you need to wait for the reply, which will have the same ID as the query you sent. If you get an <iq type="reply"> then all is well and you are now logged in. If the reply type is "error", the login failed and the reply will contain an explanatory error code and message. In case of failure you should close the session immediately (see next section.)

    Now what?

    Once logged in, your next tasks are to establish the user's presence and sync up the roster. You might also want to:

    • Send a jabber:iq:private query to retrieve custom preferences from the server, if you store any there. [JPG pp.55-56]
    • Send a jabber:x:autoupdate query to see whether a newer version of your client is available [JPG pp.35-36]

    Then you can relax and wait for user commands or server notifications.

    Closing the session, or being disconnected.

    To log out, simply close your outer <stream> element by writing "</stream:stream>", then close the socket. If some fatal error on your side (like an XML parse error) forces you to disconnect, but the socket is still OK, it's polite to first write an <error> element containing an error code and/or message.

    In some cases the socket may be closed unexpectedly. Obviously this can happen if the Jabber server process crashes. The server may also pre-emptively log you out if a fatal error occurs; the most common fatal error, at least during development, being that it received incorrectly structured XML from you. In this case the server will send a top-level <error> element containing a message (but usually not an error code, for some reason) followed by the </stream>. You should close the socket and tell the user they've been disconnected.

    It's important to note that normally a TCP socket cannot distinguish between an intact but idle connection vs. a broken connection where the other computer has crashed or network connectivity has been broken. It'll just never receive any more data. This is a problem for a real-time protocol like Jabber: you may think you're still online and your buddies are still available, but until you try to send some data you won't know that you're disconnected. Worse, some firewalls and routers get aggressive about scavenging idle connections and will disconnect you from the Jabber server after several minutes of inactivity!

    There are two ways to deal with this. The simple one is to simply send a bit of no-op XML data once a minute or so; a single newline will suffice. If your computer never gets an acknowlegement from the server, the OS will detect that the connection is broken and indicate an error. The slightly more complex solution is to use your OS's networking API to set the "keep-alive" option on the socket [Rogers, p.185-186] but change the timeout period from the default 2 hours down to a few minutes. This may or may not be possible depending on your operating system; in the Unix world this feature was added in Posix.1g. [Rogers, p.201]


    4. Declaring & Receiving Presence

    A big part of being an IM client is informing the server (and thus the user's buddies) of the user's presence or online status. You need to do this right after login and whenever that status changes: the user might choose a different status (busy, away, unavailable...) from a menu or type in a new status message; or user inactivity might cause the status to change to away or extended-away (xa).

    To declare a change in status you simply send a single <presence> element [JPO 1.4] whose type is either available or unavailable. You don't need to add "from" or "to" attributes; the server will add these when it relays the element to everyone whose buddy list you're on. [JPO 1.4.1.1, 1.4.1.5 ] The unavailable status is a handy way to make the user "invisible": it will appear to buddies as though the user is not logged in at all! The server will take care of relaying the status to all logged-in users who are subscribed to your user's status.

    An <x> element using the jabber:x:signed namespace may be added to sign presence information. [JPO 1.6.25] I cannot find any details on how the signature is generated.

    Receiving the presence status of your buddies is precisely the opposite: you receive the <presence> elements they send. When you log in you will receive one such element for each buddy, to get you up to date on their current status; this element may contain an <x> tag using the jabber:x:delay namespace to let you know the time at which the buddy's status last changed. [JPO 1.6.18, JPG p.89] Any future status changes while you're online will be sent to you immediately.


    5. Managing Your Roster

    Roster management is one of the most confusing areas of the protocol, or at least of its current documentation. Here's an overview.

    How the roster works.

    The roster is the list of Jabber entities with whom the user has a presence relationship. This includes people to whose status the user is subscribed ("buddies"), people who are subscribed to the user's status ("watchers"), and people to whom the user wants to subscribe but who haven't yet approved the request. The roster is thus the model of the GUI "buddy list".

    The Jabber server stores the user's roster and notifies all logged-in clients whenever it changes: when the user adds or removes a buddy, when another user adds or removes the user as a buddy, and when a potential buddy approves or rejects a subscription request. This is known as a roster push. The change notifications are sent as <iq> elements using the jabber:iq:roster namespace. The client can also manually request a complete copy of the roster: it's necessary to do this just after logging in to detect changes that may have been made since the last logout at this computer.

    [JPO 1.6.12]

    Subscribing & unsubscribing buddies.

    Confusingly enough, adding and removing buddies is not done via roster manipulation but via <presence> elements. Here the type is subscribe or unsubscribe and the request is sent to the buddy's address. Confirmation or rejection is received as a reply <presence> element (with the same ID) with a type of subscribed or unsubscribed.

    Conversely, you'll also deal with other entities wanting to subscribe to your presence. In this case it's exactly the opposite: you receive the subscribe or unsubscribe and (after consulting with the user) reply with subscribed or unsubscribed.

    [JPO 1.4.1.3 -- 1.4.1.7 ]

    In all these cases, since your roster is changed, the server will also send another roster push to tell you about the roster element to change.

    Manually updating the roster.

    It's possible for you to update the server-side roster by sending <iq type="set"> elements. You would do this not to add or remove buddies (see the previous section for how to do that) but to add or change metadata associated with a buddy, such as their nickname or group[s]. [JPO 1.6.12]

    Actually, it is possible to add people to the roster this way, but it won't subscribe you to their presence. They'll just be there passively, as in an e-mail address book. You may or may not want to support this in your UI; it doesn't seem especially useful to me, but it might be handy if there are people the user wants to send messages to but doesn't need to know their online status.

    The documentation says it's also possible to remove people directly from the roster by setting the "subscription" attribute to "remove". [JPG p.65] This is the only way to remove an item added without a subscription (as in the previous paragraph); but I'm not sure what happens if you remove a subscribed buddy this way. Presumably it should unsubscribe you from their presence notifications just as if you had sent a <presence type="unsubscribe"> element.

    Implementation tip.

    Since the server will notify you of all changes in the roster, you can drive the management of the GUI buddy list entirely from these pushes. For example, when the user wants to add a buddy, you send out a <presence type="subscribe"> element but don't update the buddy list yet; you'll immediately receive a roster push from the server telling you about the change and should update the buddy list when you receive it. By using roster pushes to manage the buddy list, you ensure that the buddy list stays up to date whether it's you or another simultaneously logged-in client making the changes.

    More roster info.

    You may want to display more buddy information in your UI than simply the Jabber ID, presence state and status message. As described above, a full name/nickname can be associated with a buddy in the roster.

    A whole lot more information about a buddy can be obtained by retrieving their vCard via an <iq> query, if they've stored one on the server [JPO 1.6.26]. vCards are the Internet equivalent of business or Rolodex cards and can contain any imaginable sort of personal and contact information such as photos, birthdays, phone numbers...

    Still more generally, the Jabber server as of version 1.4 allows users to store arbitrary XML data tagged with any namespace, accessible to anyone who queries their account using the same namespace. [JPO 1.6.10] I'm not aware of any concrete usage of this, but it has great potential.


    6. Sending & Receiving Messages

    Compared to the roster, one-to-one messaging is straightforward, although there are a lot of optional bells and whistles. You send a <message> element [JPO 1.3] with a "to" attribute identifying the recipient; conversely, you receive <message> elements with a "from" attribute identifying the sender.

    Anyone can send anyone else a message; you don't have to have permission, as you do to view their presence. Since instant messages are sometimes used for spamming or harassment, you should offer your user the ability to block incoming messages from specific addresses, or conversely to allow only messages from people on their buddy list. (This is a good setting to store as a server-side preference using the jabber:iq:private namespace.)

    Message attributes.

    Any message you receive will include a <from> attribute giving the address of the sender. You can be reasonably sure of the authenticity, more so than with e-mail, because this attribute is set by the sender's Jabber server and cannot be spoofed by the sender. Of course, it could be spoofed by their server, but that's much less likely, especially if the server has a well-known/trusted address.

    A message may contain a <subject> element containing the subject of the message. Of course, there's no guarantee that the receiver's UI will display the subject. [JPO 1.3.3.4]

    A message may contain a timestamp in the form of an <x> element using the jabber:x:delay namespace. (Typically the server will attach this element if the message was stored and forwarded, i.e. if you were not online at the time it was sent.) [JPO 1.6.18, JPG p.89] If the message has no timestamp, you can assume it was sent no more than a few seconds ago, subject only to network and server lag time.

    The jabber:x:envelope namespace provides support for more addressing information, much like traditional e-mail. This includes names for the sender and receiver, lists of multiple receivers or "cc" recipients, and indications that a message was forwarded. [JPO 1.6.20]

    The message body.

    A message always contains a <body> element containing the plain-text body of the message. [JPO 1.3.3.1]

    It may also optionally contain an <html> element containing the same text in HTML format. [JPO 1.3.3.3] But be careful with HTML: it needs to conform to the XHTML Basic dialect. Firstly, it needs to be syntactically correct XML, which means among other things that elements need to be strictly nested (no "<b><i>wow!</b></i>") and that tags with no separate close tag need to end with a "/" (for example, <hr/>.) There are many such restrictions that will come as quite a shock to traditional HTML authors. (The O'Reilly book HTML & XHTML has a great overview of the differences.) Be careful: many libraries that generate HTML from styled text don't conform to these rules, and sending bad XML will cause the server to disconnect you! (If you're writing your own code to convert styled text to XHTML, you'll find that making sure all the style tags nest is quite tricky...)

    You'll also find that XHTML Basic, which was developed with typographically limited clients like cellphones in mind, is missing a number of classic HTML tags we take for granted, like <b>, <i> and <font>. Some of these have higher-level equivalents, like <strong> for <b>, but in general, to indicate styles and colors you should use "style" attributes containing CSS (Cascading Style Sheet) commands. However, since it doesn't appear that existing Jabber clients all adhere to these rules, your parsing code should be prepared to recognize old-style formatting commands like <font> as well.

    Jabber supports encrypted messages. An encrypted message body is contained in an <x> element using the jabber:x:encrypted namespace. [JPO 1.6.19] The content is the encrypted message data. The documentation is vague, but apparently the message is encrypted using PGP or GPG and encoded using Base64. It's not clear whether it's possible to use any other encryption systems. If the message text is encrypted, the <body> element should contain an explanatory message like "(The text of this message is encrypted)" for the benefit of users whose client doesn't support encryption.

    There is a jabber:x:signed namespace, which the documentation [JPO 1.6.25] says is used to sign presence information, but it doesn't say whether it can also be used to sign a message body (or describe exactly how the signature is generated.)

    Other types of content.

    Unlike MIME, Jabber messages have no standard mechanism for including separate pieces of content like graphics or sounds. This means there's no good way to include an image in an HTML message unless the image can be stored on some other server where the client can follow a URL to reach it.

    Messages can be used to send files, but again, the file's data can't be enclosed in the <message>. Instead, the message contains a URL that points to a location from which the file can be downloaded. See section 7 of this document for details.

    Roster items (buddy info) can be sent in a message using the jabber:x:roster namespace. [JPG pp.94-96]

    A reference to a chat room can be sent in a message using the jabber:x:conference namespace. [JPO 1.6.17] This is how you invite someone to a chat.

    Message types and threads.

    The sender of a message can use the "type" attribute to send along a hint as to how the message should be displayed. If no "type" attribute is given, the message is standalone and should be displayed in a separate window. Type "chat" indicates that the message should be displayed using a one-to-one chat interface, appended to the same chat display as previous messages in the thread. Type "groupchat" is similar but is used for messages sent from a chat room. (Of course, these are only recommendations; the receiver's client has final control over the message UI.) [JPO 1.3.1.1 -- 1.3.1.4]

    Finally type "error" indicates an error delivering a message you sent (most likely, it was sent to a nonexistent Jabber address.) Such a reply will contain a standard <error> element describing the problem. It appears from the documentation that the error reply, perhaps confusingly, contains the body of the original message. [JPO 1.3.1.3]

    To assist clients in displaying messages in a chat user interface, messages should contain a <thread> element whose content uniquely identifies the sequence of messages. The clients can then map the thread to a particular window. The client sending the first message should make up a hopefully-unique thread ID (the JPO suggests hashing together the sender's Jabber ID and the current time), and all further replies in the same thread should contain the same thread ID. [JPO 1.3.3.5]

    Message events (aka return receipts).

    The sender of a message can use the jabber:x:events namespace [JPO 1.6.21] to request that notifications be sent by the receiver when the message is received or displayed to the user, or when the recipient starts composing a reply. It is of course optional for the recipient to send these notifications, and I believe this feature is fairly new, so older or simpler clients might not support it.

    Notification that a reply is being composed is especially useful for chat-style UIs, especially group chat. It makes it much clearer who's speaking at any moment and can help keep everyone from talking at once.

    Message expiration.

    The sender of a message can use the jabber:x:expire namespace to request that the message expire after some time has elapsed. [JPO 1.6.22] If the message is stored offline and the expiration date has passed, the server will not deliver the message when the receiver logs in. And if the receiver has received the message but the user hasn't yet viewed it, the message could vanish when it expires.


    7. Chatting

    Jabber's groupchat or conference mechanism allows many-to-many chats hosted by a server (which need not be the same as the server any member is logged directly into.) Not only is many-to-many chat a complex thing to implement in a client regardless of protocol, but in the Jabber world this topic is made extra confusing because there are two generations of chat protocol in use. Groupchat is the original one, and conference is newer and more flexible. Conference is supported by the Jabber 1.4 server (as an extra plug-in module) but the protocol is still in flux and likely to change somewhat in the future. (I'm writing this in May 2001.)

    I've decided to cover only the newer conference protocol here. It's what I'm implementing, it's what's going to remain relevant longer, and there's a need for documentation as existing clients add support for it.

    The primary documentation of groupchat, most of which is still relevant, is in [JPG ch.6]. Conferencing is documented partially in [JPO 1.6.6] and more fully in a protocol draft by Jeremie.

    Creating a chat room.

    Before creating a room you need to have a room name and a conference server in mind. The server can be entered by the user, or can be discovered by browsing your login server using jabber:iq:browse. The room name can be entered by the user, or you can generate it programmatically (for instance, by appending a random number to the user's name.)

    To make sure the room name is not in use, send an <iq type="get"> with xmlns="jabber:iq:browse" to the room. If the room doesn't exist, you'll get an error 404 (Not Found) back and can go ahead. Otherwise, if the room exists, you'll need to pick a different name.

    There's some disagreement about how to create the room. Jeremie's draft says that you send an <iq type="set"> with xmlns="jabber:iq:browse" to create it, but this doesn't appear to work on its own with the 1.4.1 server. What actually works is to first send presence to the room, just as though it already existed and you were joining it, then send the set query.

    Joining a chat room.

    To join an existing room (whose ID may have been typed in by the user or received in a chat invitation), first send a <presence> element to it. Do not add a resource name to the room name; that's how it worked in the old groupchat, but not in conference. If you send the resource name the server will go into backward-compatibility mode and talk groupchat protocol to you.

    Next, send an <iq type="set"> with xmlns="jabber:iq:browse" whose query contains one or more <nick> elements containing your desired nickname(s) in order of preference. Once you get a successful reply to this, you know you're really in the room.

    Finally, it appears that you should send another <presence> element once you've joined the room, otherwise the existing members won't receive your initial presence. (This may just be a server bug. In any case it won't do any harm to send it twice.)

    The chat's roster.

    Every conference of course has its own roster, the set of people currently in the room. This will change over time as people join and leave, and people may sent presence changes as well (e.g. they might go idle.) Keep in mind that the JIDs of conference members are made-up proxies invented by the server, to provide anonymity. They all look like the conference's JID with a resource that's a long hex string.

    The client is notified of who's in the chat in no less than four different ways. First, <presence> elements are sent for each member, both when you initially join the room and when any member changes their presence (updating status or message, or leaving.)

    In addition, a "set" <iq> element using the jabber:iq:conference namespace is sent to you whenever the roster changes; its contents list the proxy Jabber ID and current nickname of each member. The top-level query will be a <conference> element with some attributes that describe the conference itself. It will contain <user> elements, one for each current member, with attributes "jid" (the proxy JID) and "name" (the current nickname). You can tell which member is you by comparing nicknames; that's how you can figure out what your proxy JID is.

    Forthermore, when a member joins, leaves or changes nicknames, you'll get a similar query that contains a single <user> element. (You can tell a user that's leaving because there will be an extra type=remove attribute.)

    Lastly, the server will send user-visible groupchat messages of the form "foobar has joined." or "foobar has left." You can recognize these messages because the "from" address will be just the room's address with no resource. You may prefer, as I do, to ignore these messages and instead display your own (which can be localized to match the client) in response to the above notifications.

    By the way: if you want to find out who a person in a chat room really is, send their proxy JID a "get" <iq> element using the jabber:iq:browse namespace. If browsing is allowed, you'll get a reply containing a <user> element as the query, whose "jid" attribute gives the member's real address. (If the conference room was created with the "privacy" flag, this operation will not be allowed.)

    Chat invitations.

    A chat invitation is a regular instant message that contains an <x xmlns="jabber:x:conference"> element with a "jid" attribute whose value is the address of the chat room. If the user accepts the invitation, you join the chat room as described above. Unfortunately, there doesn't seem to be a way to notify the members of the chat room that you've declined an invitation.

    Sending and receiving messages.

    To send a message to the conference, send a <message> element with a type of "groupchat" to the chat room's address. To send a private message to a member, send a regular IM (with a type other than "groupchat") to their proxy JID.

    Incoming chat messages can be recognized by the type "groupchat". Remove the resource ID from the "from" address and what's left is the chat room ID, which you use to look up the appropriate chat window in your data structures. The entire ID, of course, identifies the sender; it's the proxy ID generated by the chat room. Look that up in your in-memory roster to find the person's nickname (or even their real JID if you previously browsed for that.)

    Jabber supports the IRC convention of allowing "emote" messages. These allow a person to send a message that looks like an action (such as a stage direction) rather than a quote. Your client can recognize an emote message by the prefix "/me "; this should be replaced with the person's [nick]name before displaying it.

    For example, I might send the message "/me waves at everyone." Your client would display this as:

    Jens waves at everyone.

    Leaving the conference.

    To leave the conference you simply send it a <presence type="unavailable"> element.

    The server will then unhelpfully send you one final roster update showing that you're leaving; this might confuse your client since the chat room it's sent from is no longer one that it thinks you're in. Just ignore it.


    8. File Transfer

    Jabber doesn't directly support file transfer, only an "out-of-band" ("OOB") mechanism for sending a URL, with the assumption that the recipient will download the data at that URL to a file. The usage scheme involves the sender's client either uploading the file to a particular FTP/HTTP/WebDAV server, or opening up another port and running a trivial server on it. In either case the URL is sent to the receiver, who then makes a connection (using the protocol specified in the URL) to download the file. The latter mechanism is more efficient but will fail if the sender is behind a firewall or NAT server but the receiver isn't.

    OOB isn't just for file transfer; it can be used to transfer any URL, i.e. a link to a favorite website, although if HTML messages are supported it seems more descriptive to send such a link via an <A> tag in the message body.

    There are two similar ways to transmit the URL. The first is in a <message> element, as a nested <x> element using the jabber:x:oob namespace. [JPG p.92, JPO 1.6.23]. The second is in an <iq> query using the jabber:iq:oob namespace. [JPG p.53, JPO 1.6.9] The latter form allows acknowlegement via an IQ reply.


    9. Registering New Accounts

    The Jabber protocol allows clients to register new accounts on servers without having to go through a Web interface or beseech a sysadmin. (Of course, any particular server can disallow this if it wants to.) Registration uses a variant form of login.

    To register, create a connection to the server and open a <stream> element just as in a normal login. But then send an <iq type="get"> element using the jabber:iq:register namespace. [JPG pp.57-62]

    The server might reply with an error if it's not allowing registration. But normally it will send a reply containing a number of sub-elements. These help define the GUI of a dialog box to be presented to the user to be filled out:

    • <key>, if present, contains a magic authentication string that needs to be sent back to the server in subsequent registration commands.
    • <instructions> contains text that should be shown to the user in the registration window; it presumably contains instructions on how to fill out any non-obvious parts of the form, or perhaps general information about the server. Hopefully it's in a language the user can read, because you have no way to request any particular language.
    • <username>, <nick>, <password>, <name>, <first>, <last>, <email>, <address>, <city>, <state>, <zip>, <phone>, <url>, <date>, <misc>, <text> all represent fields in a form that needs to be filled out by the user. They'll initially have no content. Not all of them will necessarily be present (although <username> and <password> need to be.) Most of these are self-explanatory, although I'm not really clear on what the last four are for. You should map these keys to more verbose localized names to display in the dialog.

    Once the user has filled out all of these fields, you send back an <iq type="set"> element whose query contains all the form field elements, with their contents being the text entered by the user. It also needs to contain a copy of the <key> element, if any, received from the server. Then you wait for a reply.

    If the registration succeeds, you'll get back a reply element with an empty query, which indicates success. Hooray! You still need to close the connection. The connection can't be re-used to log in using the new account; open a new one for that.

    If the registration fails, you'll get back an error element. This is not necessarily fatal &emdash; if the error code is 409 (Conflict), this means that the desired username is not available, so you should put up the dialog box again and ask the user to choose a different username, then send the updated registration query.

    Updating registration.

    The JPG says that an <iq type="set"> element can be used to update the registration information (such as password or e-mail address) of an existing account. But I'm not sure how this works; I suspect that, to be properly authenticated, it has to be sent after you're already logged in.

    Canceling an account.

    You can cancel an account by sending an <iq type="get"> element to obtain the server's <key>, then sending an <iq type="set"> containing a <remove> sub-element. Again, I think you have to be logged in already for this to work.


    Appendix 1. References

    Jabber protocol documents.

    The Jabber Protocol Overview, by Peter Saint-Andre.

    The Jabber Programmer's Guide, by Thomas Muldowney and Eliot Landrum. (Page numbers referred to in this document are from the 1.0.3 release.)

    Generic Conferencing protocol draft, by Jeremie Miller.

    (For more Jabber documentation, or to get these documents in PDF form, see the Jabber.org documentation website.)

    Other useful references.

    UNIX Network Programming, Volume 1 by W. Richard Stevens. Everything you wanted to know about sockets, including the keepalive option.

    HTML & XHTML: The Definitive Guide by Chuck Musciano and Bill Kennedy (O'Reilly). Chapter 16 describes in great detail how XHTML syntax differs from traditional HTML, which is essential knowlege if you're trying to compose HTML message bodies.

    Learning XML by Erik T. Ray (O'Reilly). Chapter 2, available online in its entirety, has a great overview of XML markup concepts.

    I'd also like to thank David Waite, Dave Smith, Oliver Jones, Todd Bradley, and others who have helpfully answered questions on the invaluable jdev mailing list.


    Appendix 2. Copyright & License

    This document is Copyright � 2001 by Jens Alfke. All Rights Reserved.

    Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. You may obtain a copy of the GNU Free Documentation License from the Free Software Foundation by visiting their Web site (http://www.fsf.org) or by writing to: The Free Software Foundation, Inc.; 59 Temple Place - Suite 330; Boston, MA 02111-1307; USA.

    posted by William @ 09:08   0 comments
    Jabber即时通讯客户端(一)

    作者:huwell    文章来源:huwell 
    *注意:本文章大部分内容参考了jabber.org的文献资料

       请参考
    WellJabber1.0程序

      约定: 
      [JPO]代表[Jabber Protocol Overview]
      [JPG]代表[Jabber Programmers Guid]

      现在网络中最流行的程序,莫过于即时通讯软件了,从ICQ到QQ,全世界约有7000万人每天在使用它们。人们利用它来沟通、交流,它是继电子邮件之后另一个最成功的通讯工具。如此成功的软件模式引出了一系列出色产品的诞生:ICQ,Yahoo! Messenger,AOL Instant Messenger,MSN Instant Messenger及中国人用的最多的QQ,而其中有一个较之其他通讯程序更璀璨夺目的明珠,那就是Jabber工程。

      Jabber是一个基于开放模式的软件工程,现在的主要目的运用于即时通讯(Instant Messaging System),Jabber并非第一个发明者,但它拥有几个非同一般的特点:
      
       *基于XML
      *分布式构架
      *开放式协议与代码库
      *方便的、可扩展的组件模式

      这些特点使得jabber一出世,便深受瞩目,可以毫不避讳的说,几大流行通讯软件(如Yahoo!,AOL Messager,还有tencent的QQ)都是从jabber的代码库中发展而来的。而它基于XML的通讯协议,使得跨平台很容易就能实现,现在的jabber已经可以使PC,Palm(掌上电脑类)以及SMS(短信息)、WAP互相沟通无碍了。总之,jabber的发展激动人心,它极有可能成为未来的即时通讯标准。

      以下介绍的是Jabber的工作状态,依据版本为最新的1.4。

      
  • Jabber前介
      
      Jabber Session
      整个Jabber的交流是基于一个会话(session)过程的,Jabber会话开始后,就会同指定服务器的端口5222(或者是5223,如果使用SSL进行加密的话)进行TCP连接。WellJabber作为一个演示性程序,没有将SSL选项包含在内――需要注意的是,并非所有的服务器端都支持SSL。

      Jabber发送的数据流是由一个连续不断的XML文档构成的,它的根元素为<stream>,而只有当C/S两端都注销时――即发出</stream>关闭标记,这个XML文档才算彻底结束。在<stream./>里的子元素们都是做为命令出现的,各自包括自己的属性、内嵌元素,以此作为参数。

      服务器/客户端两边的连接都是异步传输模式,这不同于诸如POP这样的协议,在你发出一个命令时,不需要等待另一端的回应,可以直接发出下一个(命令)。此外,服务器可以随时向你发出指示(比如说,当你的一个好友上线或离线时),这就意味着你得随时作好处理这些指示的准备。

      那如何将一个回应同特定的命令对应起来呢?这是id属性需要做的事。你发出一个命令时,需要包含这个属性――以一个恰当的唯一值出现,而从服务器返回的回应,也包括同样的属性值。具体处理时,可以用一个散列表(Hash Table)作为id值,以此来标识那些需要回应的请求等数据。当然,设置合适的id值是服务器端的事,客户端所要做的是随时接收server发来的指示,在编写代码时,可以开辟一条单独的线程或利用一个select/event来响应接收的信息。这些在C/C++中都拥有良好的支持,但PHP则不行,因为它毕竟是脚本语言,不能进行系统函数的调用,所以WellJabber暂时只能做到请求/回应的模式,不能做到随时处理主动接收的信息,也就是当好友发一个信息给你时,你没有办法去判断、接收它。因为这个至少需要一个循环来处理接收,但在脚本中出现这个循环意味着你的程序在信息到来之前始终不能完成,这是相当可怕的。对 WellJabber来说,这的确是个不小的遗憾。

      Parsing XML
      实际处理时,最困难的部分可能就是解析XML文档了,但幸运的是XML不同于HTML,它有着严格的语法定义和格式,比如所有标记和属性都是大小写敏感的,所有的属性结束时都要求明确的关闭标记,属性值、标记外的文本内容都不得与XML保留字相同(如<、 >、&、'、" 这样一些,如果需要可以用&entity;的形式代替),还有就是非ASCII码字符集的文档要求在<?xml>中明确标识,通常中文可以处理为:

      <?xml version="1.0" encoding="GB2312" ?>
      或<?xml version="1.0" encoding="BIG5" ?>

      自己写出一个XML解析程序是完全有可能的,但幸运的是,有很多标准的XML解析程序(库)可供我们使用,比如使用PHP编写的WellJabber实际上就是利用expat作为解析模块。这里要注意的一点是,你的解析程序必须能处理任何得到的XML数据片段,因为前面已经说过,jabber中传递的XML数据并非完整的,一个彻底结束的数据流(以<stream/>结尾)要到程序注销时才能出现。

      补充:Expat是一个很有名的XML解析程序,很多出色的软件工程使用它做为XML文档的解析模块,譬如PHP及Perl等。

      Writing XML
      写XML数据相对来说比较简单了,但仍要注意必须写得符合语法要求,少一个引号都可能引起服务器拒绝处理、甚至是断开连接。


      
  • 登录/注销Jabber会话

      Opening the session
      当你打开位于5222(或5333)端口上的socket后,需要发送一个标准的XML头,以此来打开一个以<stream>开始的完整jabber会话。

    <?xml version="1.0" encoding ="UTF-8"?>
    <stream:stream
    to="jabber.org"
    xmlns="jabber:client"
    xmlns:stream= http://etherx.jabber.org/streams

      这样就可以唤醒服务器了,这时服务器会回应大致如下信息:
    <?xml version="1.0" encoding="UTF-8"?>
    <stream:stream
    from="jabber.org"
    id="39ABA7D2"
    xmlns="jabber:client"
    xmlns:stream= http://etherx.jabber.org/streams

      这时XML解析器就可以发挥作用了,它提取并保留当前的id值,因为本次Jabber Session就依靠它来标识了。

      我使用TCP Echo Client测试了一下,的确是如[JPO]中所述的一样,只是要手工输入XML代码比较麻烦。
  • 标签: , , ,

    posted by William @ 08:32   0 comments
    Jabber即时通讯客户端(二)

    作者:huwell    文章来源:huwell 
    Logging in
      
      这时服务器等待你的身份验证,你使用<iq>询问[JPO 1.5]来发送你的认证信息,而服务器据此返回一个回应,指示你的登录是否成功。[JPO  1.5.3.3]

      具体来说,询问使用jabber:iq:auth空间名称和<username>以及<resource>元素来标识用户的jabber ID和计算机的resource名[JPO  1.6.3] 。

      在认证过程中,使用明文传递密码是不提倡的,比较安全的做法是配合使用一个包含已编码digest的<digest>元素。这个digest是根据session ID和密码组合成的字符串,结合SHA-1算法生成一个20个字节的散列,然后再将其转变为40个字节的16进制形式。当发出这个登录询问后,就开始等待回应了。接下来你会收到<iq type="reply">的数据,至此,你已经成功登录了。假如回应中type="error",则表示登录失败,这时得立即关闭session了。

      Logging Out

      当要注销退出时,只需简单的发出"</stream:stream>"数据即可,这样就通知服务器,本次XML数据流结束,然后关闭socket。有时候,socket会出乎意料的关闭,比如说当Jabber服务器进程中断时。比较严重的问题是,一般TCP Socket不能分辨清楚一个空闲连接和一个断开的连接(可能发生在计算机崩溃或网络断开时),这时不会收到任何数据,这对一个向jabber一样的实时通讯协议来说,是个棘手的问题:你可以想象一下,此时你仍然在线,而你的好友列表还是有效,但除非你试图发送一个讯息,否则你不会知道你已经断开连接了。更为严重的是,一些防火墙和路由器在发现你长时间没有动作后,会做自动断开连接。

      有两种方法来解决这个问题,一种比较简单,就是每隔一分钟左右的时间就发送一个bit的XML数据,假如你的计算机没有得到服务器的回应,操作系统就将检查连接是否断开,并指出一个错误。还有一种复杂的方法就是设置你操作系统的网络API中套接字的"keep-alive"选项,只需将超时间隔调整到几分钟即可,这个方法是否可行,依赖于你的操作系统(在win32平台上,可以用setsockopt函数配合SO_KEEPALIVE或SO_KEEPALIVE_VALS来修改这个选项)。

      
  • 用户在线状态
      
      即时通讯客户端程序中要处理的一个重要模块就是,通知服务器用户的在线状态。当你登录后或改变在线状态时,都需要通知服务器。(比如QQ中经常使用的"我在吃饭,请过一会儿再和我联系","我正在工作中"等等,这些都属于用户的在线状态)

      要报告一个状态信息的改变,只需要发送一个<presence>元素[JPO  1.4]。它的类型属性不是available就是unavailable。你不需要添加"from"或"to"属性,服务器在将你的状态信息发给你的好友时,会自动添加它们。[JPO  1.4.1.1,1.4.1.5]unavailable状态可以很方便的使用户处于"隐身":在你的好友看起来,你就象根本没有在线。

      接受你好友的在线信息正好相反:你会收到他们发送的<presence>数据。当你登录后,你会收到每位好友这样的数据,以更新你好友列表的内容。这个元素可能包括一个使用jabber:x:delay命名空间的<x>标记,来通知你好友状态信息最后改变的时间。[JPO  1.6.18 , JPG p.89]只要你在线,这些状态信息随时都会发送给你。


      
  • 管理好友列表
      
      好友资料(Roster)的管理是一个比较头疼的事情,至少从现在协议的描述来看。

      How the roster works

      好友资料的处理工作包括:用户的状态,好友的状态以及那些想加好友但尚未验证通过的请求。Jabber服务器存储用户的好友资料,并负责在如下情况下通知已登录的用户其好友资料的改变:用户添加或删除一个好友,其他用户在好友列表中添加或删除你,用户通过或拒绝加入好友的验证。这些都笼统的称作好友资料更新。这些更新通知都是作为<iq>元素(使用jabber:iq:roster命名空间)数据来发送的。当然,客户端也可以主动请求好友资料的更新:这个在登录后通常都应该进行一次,以更新本地客户端的好友资料。

      Subscribing & unsubscribing buddies

      添加或删除好友是通过<presence>元素来进行的,它的type属性是subscribe或unsubscribe。接收或拒绝都是通过<presence>元素来进行的(请求和应答都有同样的ID号)总之,当你的好友资料改变时,服务器就会主动通知你情况的改变。前面已经说过,PHP编写的WellJabber有很多限制,其中一条就是除非你主动要求更新好友资料,否则很难及时反映好友在线情况。

      Manually updating the roster
      
      如果你想更新服务器端的好友资料,可以发送<iq type="set">元素,你这样做并不是添加或删除好友,而是更新与好友相关的资料,比如他们的昵称或所属组名。[JPO 1.6.12]

      More roster info

      完整的好友信息可以在通过一个<iq>询问接受vCard资料时获得,前提是如果他们存储了这样的信息[JPO  1.6.26]。(关于vCard,实在又是一个很大的论题,所以作为演示例子的WellJabber没有包含它)
  • 标签: , , ,

    posted by William @ 08:31   0 comments
    Jabber即时通讯客户端(三)

    作者:huwell    文章来源:huwell   
  • 发送/接收信息

      发送信息时,使用一个<message>元素[JPO 1.3],它使用"to"属性来标识接收者;反之,你接受包含"from"属性的<message>元素,它标识了发送者。

      实际上,任何人都可以发送信息给别人,你不需要特定的权限就可以查看到别人的在线状态。这会造成信息的骚扰与泛滥吗?要解决这个情况,就要使程序有对信息进行筛选的能力,只允许从好友处来的信息,其他一律过滤掉。

      Message attributes
      我们收到的任何信息都包括一个<form>属性,它给出了信息的发送者。同电子邮件相比,它的认证更为可靠,因为这个属性是由jabber服务器端来添加的,这就减少了发送者进行欺诈行为的可能性。

      一个信息还应该包括一个<subject>元素,它标识了本次信息的主题,但显示与否取决于接收者所使用的客户端程序。

      一个信息还可以包括一个时间戳,这是用一个<x>元素来实现的,它使用了jabber:x:delay命名空间。

      而使用jabber:x:envelope命名空间还可以提供群发的功能,这就象传统的电子邮件一样。[JPO 1.6.20]

      The message body
      一个信息总是用<body>元素来包含其具体内容的。[JPO  1.3.3.1]
    当然也可以包含可选的元素<html>,它将提供HTML格式的信息。[JPO  1.3.3.3]但是需要注意的是,这个格式是基于XHTML的(w3.org制定的一种由HTML向XML过渡的格式)。

      对于HTML的使用者来说,会发现XHTML与其有很大的不同,因为设计XHTML时就考虑了客户端类型的限制(譬如说手机),具体体现为缺少一些常用的HTML元素,如<b>,<i>及<font>,但它们在XHTML中都有等价替代元素,如<strong>代替了旧的<b>,但一般指定色彩或格式时,都使用CSS(Cascading Style Sheet)。

      Jabber支持加密的信息传送,它使用包含jabber:x:encrypted命名空间的<x>元素来处理。[JPO 1.6.19]文档中对这段描述并不是很清楚,因此WellJabber并没有对加密提供支持。

      Other types of content
      与MIME不同,jabber信息并没有一个标准的格式来容纳图片或声音,这就意味着你无法在信息中包含一幅图片的数据,除非是使用超链接的形式来指示它。

      你可以随信息一起发送文件,但是文件的数据不能包含在<message>中,而是采用超链接的方式指明可以下载的文件。

      Message types and threads
      发送的信息可以使用"type"属性来提示其显示方式,如果没有指明这个属性,信息将独立地显示在单独的窗口中。若"type=chat"则指明应使用one-to-one(类似QQ的两人世界)聊天界面来显示。此外还有"type=groupchat",详细参见[JPO  1.3.1.1――1.3.1.4]。
      
      最后有可能出现"type=error"这样的属性值,它表明在发送一个信息时出错了(比较常见的是,发送信息给一个不存在的jabber地址)。这时的回应包含在一个<error>元素中。[JPO  1.3.1.3]

      为帮助客户端显示信息在相应界面中,信息还可以包含一个<thread>元素,它包含一个指向信息流的唯一值,客户端发送的第一个信息就应该包括一个唯一的线程ID,而后继的信息都应该发送到此线程ID标识的同一个线程中。(JPO建议thread ID由发送者的jabber ID及当前时间以散列算法合成)

      Message event
      信息的发送者可以使用jabber:x:events命名空间来接受这样的通告,即信息的接收者是否已经查阅过本信息,或者他/她是否在进行回复。这是个全新的功能,在演示程序WellJabber中没有体现。

      Message expiration
      信息的发送者可以使用jabber:x:expire命名空间来确定信息的发送时效。[JPO  1.6.22]如果信息是离线存储的,当时效过去时,即使对方用户登录,该信息也不会发给他/她。

      
  • 聊  天
      
      Jabber的群组聊天或会议机制允许多人同时进行交流。

      这种多人交流的方式在客户端实现时是比较复杂的,这是大家所公认的,因为有两套聊天协议在使用。群组聊天是最早采用的,而会议机制是新的,也更灵活(注意,现在只有jabber 1.4服务器版本才支持它――做为一个外接模块)。实际上,协议本身仍在不段变化,还没有最终形成标准。

       Creating a chat room
      在产生一个聊天室前,你需要有一个聊天室名和一个会议服务。服务可以由用户来制定,或者通过发送jabber:iq:browse请求来检索。聊天室名称可以自己输入,或者编程产生(比如,产生一个随机的数字作为名称)。

      为确认聊天室名称是否已被使用,可以发送<iq type="get">(含xmlns="jabber:iq:browse"的命名空间)到聊天室,如果它不存在,你会收到error 404(没有找到)错误,反之,如果其已存在,你就得重新为聊天室取个名称。QQ中体现在自建聊天室这个版块。

      对于如何生成一个聊天室,有着不同的异议。编程者的实践经验是先发送presence到聊天室,如果已存在就加入它,没有则发送set请求来建立它(发送包含xmlns="jabber:iq:browse"的<iq type="set">命令)

      Joining a chat room
      需要加入一个聊天室时(它的ID已经由用户指定或在接到聊天邀请时确定),首先发送一个<presence>元素。注意不要添加resource名在发送中,这是老的groupchat的做法,现在的conference已经不采用了。如果你需要向下兼容性,可以发送resource name。

      接下来,发送包含xmlns="jabber:iq:browse"的<iq type="set">,这个请求包含了一个或多个<nick>元素,它指明了你希望加入的会议的别名。一旦你接到一个成功回应,也就意味着你已经加入这个聊天室。

      The chat's roster
      每个聊天室都有个人员列表,表明当前在聊天室中的人员。它会随着人员加入或离开而改变。

      通知客户端聊天室人员的方法有很多种。首先,发给每个成员<presence>元素,在你加入这个聊天室或有其他成员改变在线状态时(更新状态、信息或是离开)。

      此外,当成员列表改变时,自己会收到一个含有jabber:iq:conference命名空间的<iq>元素,它具体包括代理服务器上的jabber ID以及当前成员的昵称。描述conference本身的是包含多个属性的<conference>元素,如果包含<user>子元素,则标识了当前的成员们,这时通常带有"jid" 属性和"name"属性。

      又如,当一个成员加入、离开或是改变其昵称时,你就会收到一个类似的请求,它包含一个单独的<user>元素。
      
      最后,服务器会发送一个类似"某某加入了"或"某某离开了"样式的消息。
  • 标签: , , ,

    posted by William @ 08:30   0 comments
    Jabber即时通讯客户端(四)

    作者:huwell    文章来源:huwell 
    随便说一下,如果你希望在聊天室查找某人,可以使用包含jabber:iq:borowse命名空间的<iq>元素来发送他/她的proxy JID。

      Chat invitations

      聊天邀请使用包含<xmlns="jabber:x:conference">的请求来实现。

      Sending and receiving messages

      要发送一个信息到聊天室,可以发送"type"为"groupchat"的<message>元素到聊天室地址。发送一个私人信息,可以到他们的proxy ID。

      收到的聊天信息依靠"groupchat"类型来辨识,可以从"from"地址去处掉resource ID,此时剩下来的就是聊天室ID了。

      Jabber还支持IRC的"表情"聊天方式,这使聊天者能做出类似舞台动作的行为。(这个在QQ中是很常见,也很有趣的)客户端通过前缀"/me"来辨识这样的信息,通常在显示前应该插入使用对象的昵称。

      比如,可以发送这样的信息"/me 笑眯眯的望着大家",则客户端就显示为:
      Huwell 笑眯眯的望着大家

      Leaving the conference

      要离开聊天室时,你只需要简单的发送一个<presence type="unavailable">元素即可。

      File Transfer

      Jabber不直接支持文件的传送,而是依靠"带外数据"(out-of-band)即OOB机制通过URL来超链接文件。这种解决方案使得发送者的客户端要么上传文件到一个特定的FTP/HTTP/WebDAV服务器,要么打开另一个端口,运行一个常规服务在上面。这两种方法下URL都会发送给接受者,他们便使用这个超链接来下载文件。注意,后一种机制在发送者隐藏在防火墙或NAT Server后时会失效,接收者不受影响。这种P2P的文件传送功能非常有用。

      OOB不是仅为文件转送来设计的,它可用来传送任何URL,比如一个到心爱站点的链接,尽管一个HTML消息也可以支持它。

      有两种相似的方法来传送URL,一个就是将<x>嵌套在<message>元素中,并使用jabber:x:oob命名空间[JPG p.92,JPO 1.6.23];第二种方法就是使用jabber:x:oob命名空间的<iq>元素请求[JPG p.53,JPO  1.6.9]后一种方法允许通过iq回应来确认。


      [*}注册新用户

      Jabber协议允许客户端登记一个新的用户,而不用通过web界面来或系统管理员来申请(当然,任何服务器都允许这样做)。登记新的帐户有多种方法。

      登记时,首先连接到服务器并打开一个<stream>元素,这就好象是正常登录。只是发送的是使用jabber:iq:register命名空间的<iq type="get">元素。[JPG p57-62]
    如果服务器不允许登记新用户的话,会回应一个错误。

      下列资料可以做为编程者设计登记新用户界面的参考:
      <key>,这是一个需要随着登记命令发送回服务器的认证字符串。
      <instructions>,包含一个呈现给用户的介绍。
      <username><nick><password><name><first><last><email>
      <addtress><city><state><zip><phone><url><date><misc><text>,这些都是用户的资料。属于jabber Server1.4所需要的,如果开发出自己服务器版本,就可以自己定义这些用户资料选项了。

      当用户一切就绪后,就可以发送包含上述资料的<iq type="set">回应了,然后等待服务器的响应。
      
      如果注册新用户成功,你会收到一个包含空的请求的回应。你需要关闭连接,这个连接不能再做为登录的连接重复使用了,你得另外打开一条新的。

      如果注册失败了,你将得到错误的回应,如果错误代码是409(冲突),这意味着你注册的用户名是无效的。

      Updating registration

      如果要更新注册信息(如密码或电子邮件)可以通过发送<iq type="set">元素来完成。

      Canceling an account

      如果你需要终止一个帐号,你先得通过发送一个<iq type="get">元素来获得服务器的<key>,然后再发送包含<remove>子元素的<iq type="set">来达成。


      [*}WellJabber功能模块

      开发一个jabber的客户端不是一件简单的事,事实上标准的客户端应该包括:P2P聊天,聊天室,好友列表管理,资源管理,相关资料修改,信息加密处理,MSN网关(或者还包括Yahoo!,AOL等的信息转换),在线搜索,认证控制,离线信息转发,文件互传等。

      一个比较成功的jabber客户端是由jabber.com提供的JIM,此外还有很多优秀的jabber客户端,由于jabber的通讯协议是建立在XML基础上,而且是开放式的,所以任何语言都可以用来编写客户端,最常见的是Delphi,Java,C/C++,Perl,此外还有PHP,Python,JavaScript,甚至是Flash ActionScript都可以拿来编写这样的客户端,我觉得一个开放式的应用模式才是成功的,才会有长足的进步,这就象电子邮件,我们很难想象如果电子邮件的格式被一家所垄断,只能使用一家所编写的邮件程序去接收,那它还会这么普及、全球通用吗?在这方面国内的腾讯公司做的就很不好,显然易见,QQ的代码也是从jabber中剥离出来的,但是QQ却不肯公开它的通讯协议,一心只想做国内即时通信的老大,这会造成两个后果:一个是处在竞争压力小的情况下,它会停止不前,没有进步。

      我们看到,现在的QQ客户端同以前的没有什么太大的区别,不过界面更花哨些而已,没有利益的驱动,腾讯现在连Linux,Palm等平台都没有推出,实际上很可悲。另一个是它只能在国内发展,走不出国门,一旦即时通讯的国际标准制定,那它再这么固步自封,就会成为不合格的产品,下场也好不到哪去。所以希望腾讯公司能赶快觉醒,不要垄断这个现在看起来很壮大的行业。QQ虽然很火,但比起Flash,Netscape又如何?这些世界闻名的软件都是公开格式,甚至是代码的,腾讯还不该向这些软件学习吗?当年的Netsacape几乎是独步天下,可还是被IE后来居上了,现在微软又在XP中捆绑了MSN,腾讯再不当心,可真要尴尬了。

      WellJabber是使用PHP写的,是典型的B/S结构程序,之所以这样考虑,是想使WellJabber具有跨平台的特性,能够在win32,Linux,Unix,Mac等系统上都顺畅的运行,因为它是利用浏览器做为承载平台的。而且PHP发展到今天(最新的版本是Version 4)已经很强大了,它具有多种函数库,如同C语言一样,甚至融合的比C更体贴,就Jabber来看,PHP拥有必不可少的XML解析函数,还有网络连接函数,以及加密函数(Hash散列,Base_64等),总之使用PHP来写jabber的演示程序的确很方便,但PHP也不是十全十美的,毕竟,通过HTTP端口,很多有用的功能都实现不了,而且调用的不是系统级的函数(如connect),效率有所下降。

      本程序只预计包含五个功能模块:用户注册,用户登录,获取好友列表,发送信息,用户注销。

    标签: , , ,

    posted by William @ 08:29   0 comments
    数据库设计经验谈
    2007年1月21日星期日
    一个成功的管理系统,是由:[50% 的业务 + 50% 的软件] 所组成,而 50% 的成功软件又有 [25% 的数据库 + 25% 的程序] 所组成,数据库设计的好坏是一个关键。如果把企业的数据比做生命所必需的血液,那么数据库的设计就是应用中最重要的一部分。有关数据库设计的材料汗牛充栋,大学学位课程里也有专门的讲述。不过,就如我们反复强调的那样,再好的老师也比不过经验的教诲。插入一些数据库设计心得:

    一、 设计思想

    对许多程序员来说,设计一个数据库应用程序并不是很难的一件事。但是却有许多数据库应用软件得不到用户的承认,其原因就是前期调研中,信息化设计单位和使用单位没有得到相应的思想沟通。

    这里所说的沟通包括用户对软件功能的要求,时间效益的要求,软件平台的要求,价格的要求和软件维护的要求。这五种要求构成一个成功应用的软件的所有的调研项目。

    但是这里最重要的就是对软件功能的要求,不同的企业对软件要求的是不一样的。下面就软件功能的需求要求做一个概要介绍:

    1. 对象性:

    这并不是软件工程或者其他参考书中所描绘的软件设计要求,但是这是一个必然的发展趋势。我国软件主要由财务软件起步,财务业务流程是国家统一规定的,零售业的财务流程和建材业的财务业务流程并没有多大不同,所以设计一种软件就可以应用不同的公司甚至是跨行业的公司也就是很正常的一件事,但是随着我国市场经济的发展,用信息化技术来推动企业发展成为一种切实有效的手段,许多不同行业的企业甚至同行业不同企业对信息化应用软件都有不同的要求。

    在现代程序开发技术中,面对对象的技术是一个大的飞跃。但是许多开发的数据库应用软件并没由认识到这一点,所以开发的软件就没有市场。有一次,一个软件推销员到我公司来推销软件,是明煌软件公司的人事管理软件,公司人事部门领导很感兴趣,随口问了几个问题,其中一个是有没有临时工的管理,一个是工资统计查询能不能按照职工年龄,岗位,职称,学历分类统计查询。结果这个软件没有这两项功能,所以人事部门领导很客气的拒绝了这个应用软件推销员的关于演示软件的请求。

    作为一个开发人员来说,在一个数据库应用软件加上以上两个功能实在是很一般的工作,但是就是因为在开发时没有面对对象的考虑用户的需求导致了这次软件推销的失败。

    所以对一个应用软件来说一开始就考虑软件的对象性是一个成功的必要因素。

    2.易用性

    关于易用性的好坏不是由开发部门测定的,也不是由软件评测机构认定的,而是由用户认定的。这是在工作交流中得到确认的。

    许多软件考虑精细,例如ORACLE数据库为后台数据库的ORACLE公司的ERP软件解决方案,就没有考虑到中国的国情,不但应用界面分类复杂,而且在工作业务繁忙的时候,由于操作复杂往往还适得其反,到耽误了工作,惹得领导埋怨,职工抱怨,反而不如不用。

    在销售系统软件的调试过程中,我认识了一个销售公司的业务员,他跟我谈了使用软件后的许多感想。他说软件本来是减轻工作量的,但是销售系统有的应用界面就很不友好,在向网络数据库中录入数据时,录入数据很多,但是软件总要求一会用键盘打字,一会用鼠标点击,这几千项数据输入时,人一会用键盘一会用鼠标,活就像个钟摆,累死了,干吗不设计的都能用键盘控制呢。

    事实上就是这样,软件在编制的过程中一定要多与业务人员交流,了解工作流程很重要,但是决不能忽视易用性在整个软件性能中不可忽视的比例。

    3. 扩展性

    作为现代软件系统的一部分,可扩展性越来越成为构成软件生命的主要功能之一。无论什么公司都希望买的软件能够适应并满足公司业务发展变化的需求,还希望能够和其他购买的软件一起构成一个完整的企业软件系统。

    在软件上来说,这有点困难,因为要满足这项要求不但要预测企业发展方向,并且在软件中预留出数据交换接口,在应用文档中要公布部分数据库构成甚至时部分源码。

    但是从大的应用方向上,我们设计的软件必须达到这样使用的功能。金蝶,用友这两个大的软件公司已经实现的客户开发工具包来实现客户化二次开发的需求。

    4. 维护功能

    为了保证软件正常工作,软件维护是必要的。但是远水救不了近火,谁也不能保证软件在故障的时候软件维护人员能够及时维护,这就要求在软件设计是要增加软件维护功能。有了软件维护功能,哪怕是简单的备份功能,也能够在突发事件中将数据损失降到最低点。

    除了一般功能外,在软件设计时,我认为上述四个功能是注意要添加和完善的,这样我们作出来的数据库应用软件才能够具有更高的使用价值。

    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    所以我归纳历年来所走的弯路及体会,并在网上找了些对数据库设计颇有造诣的专业人士给大家传授一些设计数据库的技巧和经验。精选了其中的 60 个最佳技巧,并把这些技巧编写成了本文,为了方便索引其内容划分为 5 个部分:

    第 1 部分 - 设计数据库之前
    这一部分罗列了 12 个基本技巧,包括命名规范和明确业务需求等。

    第 2 部分 - 设计数据库表
    总共 24 个指南性技巧,涵盖表内字段设计以及应该避免的常见问题等。

    第 3 部分 - 选择键
    怎么选择键呢?这里有 10 个技巧专门涉及系统生成的主键的正确用法,还有何时以及如何索引字段以获得最佳性能等。

    第 4 部分 - 保证数据完整性
    讨论如何保持数据库的清晰和健壮,如何把有害数据降低到最小程度。

    第 5 部分 - 各种小技巧
    不包括在以上 4 个部分中的其他技巧,五花八门,有了它们希望你的数据库开发工作会更轻松一些。

    第 1 部分 - 设计数据库之前

    1. 考察现有环境
    在设计一个新数据库时,你不但应该仔细研究业务需求而且还要考察现有的系统。大多数数据库项目都不是从头开始建立的;通常,机构内总会存在用来满足特定需求的现有系统(可能没有实现自动计算)。显然,现有系统并不完美,否则你就不必再建立新系统了。但是对旧系统的研究可以让你发现一些可能会忽略的细微问题。一般来说,考察现有系统对你绝对有好处。

    2. 定义标准的对象命名规范
    一定要定义数据库对象的命名规范。对数据库表来说,从项目一开始就要确定表名是采用复数还是单数形式。此外还要给表的别名定义简单规则(比方说,如果表名是一个单词,别名就取单词的前 4 个字母;如果表名是两个单词,就各取两个单词的前两个字母组成 4 个字母长的别名;如果表的名字由 3 个单词组成,你不妨从头两个单词中各取一个然后从最后一个单词中再取出两个字母,结果还是组成 4 字母长的别名,其余依次类推)对工作用表来说,表名可以加上前缀 WORK_ 后面附上采用该表的应用程序的名字。表内的列[字段]要针对键采用一整套设计规则。比如,如果键是数字类型,你可以用 _N 作为后缀;如果是字符类型则可以采用 _C 后缀。对列[字段]名应该采用标准的前缀和后缀。再如,假如你的表里有好多“money”字段,你不妨给每个列[字段]增加一个 _M 后缀。还有,日期列[字段]最好以 D_ 作为名字打头。

    检查表名、报表名和查询名之间的命名规范。你可能会很快就被这些不同的数据库要素的名称搞糊涂了。假如你坚持统一地命名这些数据库的不同组成部分,至少你应该在这些对象名字的开头用 Table、Query 或者 Report 等前缀加以区别。

    如果采用了 Microsoft Access,你可以用 qry、rpt、tbl 和 mod 等符号来标识对象(比如 tbl_Employees)。我在和 SQL Server 打交道的时候还用过 tbl 来索引表,但我用 sp_company (现在用 sp_feft_)标识存储过程,因为在有的时候如果我发现了更好的处理办法往往会保存好几个拷贝。我在实现 SQL Server 2000 时用 udf_ (或者类似的标记)标识我编写的函数。

    3. 工欲利其器
    采用理想的数据库设计工具,比如:SyBase 公司的 PowerDesign,她支持 PB、VB、Delphe 等语言,通过 ODBC 可以连接市面上流行的 30 多个数据库,包括 dBase、FoxPro、VFP、SQL Server 等,今后有机会我将着重介绍 PowerDesign 的使用。

    4. 获取数据模式资源手册
    正在寻求示例模式的人可以阅读《数据模式资源手册》一书,该书由 Len Silverston、W. H. Inmon 和 Kent Graziano 编写,是一本值得拥有的最佳数据建模图书。该书包括的章节涵盖多种数据领域,比如人员、机构和工作效能等。其他的你还可以参考:[1]萨师煊 王珊著 数据库系统概论(第二版)高等教育出版社 1991、[2][美] Steven M.Bobrowski 著 Oracle 7 与客户/服务器计算技术从入门到精通 刘建元等译 电子工业出版社,1996、[3]周中元 信息系统建模方法(下) 电子与信息化 1999年第3期,1999

    5. 畅想未来,但不可忘了过去的教训
    我发现询问用户如何看待未来需求变化非常有用。这样做可以达到两个目的:首先,你可以清楚地了解应用设计在哪个地方应该更具灵活性以及如何避免性能瓶颈;其次,你知道发生事先没有确定的需求变更时用户将和你一样感到吃惊。
    一定要记住过去的经验教训!我们开发人员还应该通过分享自己的体会和经验互相帮助。即使用户认为他们再也不需要什么支持了,我们也应该对他们进行这方面的教育,我们都曾经面临过这样的时刻“当初要是这么做了该多好..”。

    6. 在物理实践之前进行逻辑设计
    在深入物理设计之前要先进行逻辑设计。随着大量的 CASE 工具不断涌现出来,你的设计也可以达到相当高的逻辑水准,你通常可以从整体上更好地了解数据库设计所需要的方方面面。

    7. 了解你的业务
    在你百分百地确定系统从客户角度满足其需求之前不要在你的 ER(实体关系)模式中加入哪怕一个数据表(怎么,你还没有模式?那请你参看技巧 9)。了解你的企业业务可以在以后的开发阶段节约大量的时间。一旦你明确了业务需求,你就可以自己做出许多决策了。

    一旦你认为你已经明确了业务内容,你最好同客户进行一次系统的交流。采用客户的术语并且向他们解释你所想到的和你所听到的。同时还应该用可能、将会和必须等词汇表达出系统的关系基数。这样你就可以让你的客户纠正你自己的理解然后做好下一步的 ER 设计。

    8. 创建数据字典和 ER 图表
    一定要花点时间创建 ER 图表和数据字典。其中至少应该包含每个字段的数据类型和在每个表内的主外键。创建 ER 图表和数据字典确实有点费时但对其他开发人员要了解整个设计却是完全必要的。越早创建越能有助于避免今后面临的可能混乱,从而可以让任何了解数据库的人都明确如何从数据库中获得数据。
    有一份诸如 ER 图表等最新文档其重要性如何强调都不过分,这对表明表之间关系很有用,而数据字典则说明了每个字段的用途以及任何可能存在的别名。对 SQL 表达式的文档化来说这是完全必要的。

    9. 创建模式
    一张图表胜过千言万语:开发人员不仅要阅读和实现它,而且还要用它来帮助自己和用户对话。模式有助于提高协作效能,这样在先期的数据库设计中几乎不可能出现大的问题。模式不必弄的很复杂;甚至可以简单到手写在一张纸上就可以了。只是要保证其上的逻辑关系今后能产生效益。

    10. 从输入输出下手
    在定义数据库表和字段需求(输入)时,首先应检查现有的或者已经设计出的报表、查询和视图(输出)以决定为了支持这些输出哪些是必要的表和字段。举个简单的例子:假如客户需要一个报表按照邮政编码排序、分段和求和,你要保证其中包括了单独的邮政编码字段而不要把邮政编码糅进地址字段里。

    11. 报表技巧
    要了解用户通常是如何报告数据的:批处理还是在线提交报表?时间间隔是每天、每周、每月、每个季度还是每年?如果需要的话还可以考虑创建总结表。系统生成的主键在报表中很难管理。用户在具有系统生成主键的表内用副键进行检索往往会返回许多重复数据。这样的检索性能比较低而且容易引起混乱。

    12. 理解客户需求
    看起来这应该是显而易见的事,但需求就是来自客户(这里要从内部和外部客户的角度考虑)。不要依赖用户写下来的需求,真正的需求在客户的脑袋里。你要让客户解释其需求,而且随着开发的继续,还要经常询问客户保证其需求仍然在开发的目的之中。一个不变的真理是:“只有我看见了我才知道我想要的是什么”必然会导致大量的返工,因为数据库没有达到客户从来没有写下来的需求标准。而更糟的是你对他们需求的解释只属于你自己,而且可能是完全错误的。一个成功的管理系统,是由:[50% 的业务 + 50% 的软件] 所组成,而 50% 的成功软件又有 [25% 的数据库 + 25% 的程序] 所组成,数据库设计的好坏是一个关键。如果把企业的数据比做生命所必需的血液,那么数据库的设计就是应用中最重要的一部分。有关数据库设计的材料汗牛充栋,大学学位课程里也有专门的讲述。不过,就如我们反复强调的那样,再好的老师也比不过经验的教诲。所以我归纳历年来所走的弯路及体会,并在网上找了些对数据库设计颇有造诣的专业人士给大家传授一些设计数据库的技巧和经验。精选了其中的 60 个最佳技巧,并把这些技巧编写成了本文,为了方便索引其内容划分为 5 个部分:

    第 1 部分 - 设计数据库之前
    这一部分罗列了 12 个基本技巧,包括命名规范和明确业务需求等。
    第 2 部分 - 设计数据库表
    总共 24 个指南性技巧,涵盖表内字段设计以及应该避免的常见问题等。
    第 3 部分 - 选择键
    怎么选择键呢?这里有 10 个技巧专门涉及系统生成的主键的正确用法,还有何 时以及如何索引字段以获得最佳性能等。
    第 4 部分 - 保证数据完整性
    讨论如何保持数据库的清晰和健壮,如何把有害数据降低到最小程度。
    第 5 部分 - 各种小技巧
    不包括在以上 4 个部分中的其他技巧,五花八门,有了它们希望你的数据库开发工作会更轻松一些。
    第 1 部分 - 设计数据库之前
    考察现有环境
    在设计一个新数据库时,你不但应该仔细研究业务需求而且还要考察现有的系统。大多数数据库项目都不是从头开始建立的;通常,机构内总会存在用来满足特定需求的现有系统(可能没有实现自动计算)。显然,现有系统并不完美,否则你就不必再建立新系统了。但是对旧系统的研究可以让你发现一些可能会忽略的细微问题。一般来说,考察现有系统对你绝对有好处。
    定义标准的对象命名规范
    一定要定义数据库对象的命名规范。对数据库表来说,从项目一开始就要确定表名是采用复数还是单数形式。此外还要给表的别名定义简单规则(比方说,如果表名是一个单词,别名就取单词的前 4 个字母;如果表名是两个单词,就各取两个单词的前两个字母组成 4 个字母长的别名;如果表的名字由 3 个单词组成,你不妨从头两个单词中各取一个然后从最后一个单词中再取出两个字母,结果还是组成 4 字母长的别名,其余依次类推)对工作用表来说,表名可以加上前缀 WORK_ 后面附上采用该表的应用程序的名字。表内的列[字段]要针对键采用一整套设计规则。比如,如果键是数字类型,你可以用 _N 作为后缀;如果是字符类型则可以采用 _C 后缀。对列[字段]名应该采用标准的前缀和后缀。再如,假如你的表里有好多“money”字段,你不妨给每个列[字段]增加一个 _M 后缀。还有,日期列[字段]最好以 D_ 作为名字打头。

    检查表名、报表名和查询名之间的命名规范。你可能会很快就被这些不同的数据库要素的名称搞糊涂了。假如你坚持统一地命名这些数据库的不同组成部分,至少你应该在这些对象名字的开头用 Table、Query 或者 Report 等前缀加以区别。

    如果采用了 Microsoft Access,你可以用 qry、rpt、tbl 和 mod 等符号来标识对象(比如 tbl_Employees)。我在和 SQL Server 打交道的时候还用过 tbl 来索引表,但我用 sp_company (现在用 sp_feft_)标识存储过程,因为在有的时候如果我发现了更好的处理办法往往会保存好几个拷贝。我在实现 SQL Server 2000 时用 udf_ (或者类似的标记)标识我编写的函数。
    工欲善其事, 必先利其器
    采用理想的数据库设计工具,比如:SyBase 公司的 PowerDesign,她支持 PB、VB、Delphe 等语言,通过 ODBC 可以连接市面上流行的 30 多个数据库,包括 dBase、FoxPro、VFP、SQL Server 等,今后有机会我将着重介绍 PowerDesign 的使用。
    获取数据模式资源手册
    正在寻求示例模式的人可以阅读《数据模式资源手册》一书,该书由 Len Silverston、W. H. Inmon 和 Kent Graziano 编写,是一本值得拥有的最佳数据建模图书。该书包括的章节涵盖多种数据领域,比如人员、机构和工作效能等。其他的你还可以参考:[1]萨师煊 王珊著 数据库系统概论(第二版)高等教育出版社 1991、[2][美] Steven M.Bobrowski 著 Oracle 7 与客户/服务器计算技术从入门到精通 刘建元等译 电子工业出版社,1996、[3]周中元 信息系统建模方法(下) 电子与信息化 1999年第3期,1999
    畅想未来,但不可忘了过去的教训
    我发现询问用户如何看待未来需求变化非常有用。这样做可以达到两个目的:首先,你可以清楚地了解应用设计在哪个地方应该更具灵活性以及如何避免性能瓶颈;其次,你知道发生事先没有确定的需求变更时用户将和你一样感到吃惊。

    一定要记住过去的经验教训!我们开发人员还应该通过分享自己的体会和经验互相帮助。即使用户认为他们再也不需要什么支持了,我们也应该对他们进行这方面的教育,我们都曾经面临过这样的时刻“当初要是这么做了该多好..”。
    在物理实践之前进行逻辑设计
    在深入物理设计之前要先进行逻辑设计。随着大量的 CASE 工具不断涌现出来,你的设计也可以达到相当高的逻辑水准,你通常可以从整体上更好地了解数据库设计所需要的方方面面。
    了解你的业务
    在你百分百地确定系统从客户角度满足其需求之前不要在你的 ER(实体关系)模式中加入哪怕一个数据表(怎么,你还没有模式?那请你参看技巧 9)。了解你的企业业务可以在以后的开发阶段节约大量的时间。一旦你明确了业务需求,你就可以自己做出许多决策了。

    一旦你认为你已经明确了业务内容,你最好同客户进行一次系统的交流。采用客户的术语并且向他们解释你所想到的和你所听到的。同时还应该用可能、将会和必须等词汇表达出系统的关系基数。这样你就可以让你的客户纠正你自己的理解然后做好下一步的 ER 设计。
    创建数据字典和 ER 图表
    一定要花点时间创建 ER 图表和数据字典。其中至少应该包含每个字段的数据类型和在每个表内的主外键。创建 ER 图表和数据字典确实有点费时但对其他开发人员要了解整个设计却是完全必要的。越早创建越能有助于避免今后面临的可能混乱,从而可以让任何了解数据库的人都明确如何从数据库中获得数据。

    有一份诸如 ER 图表等最新文档其重要性如何强调都不过分,这对表明表之间关系很有用,而数据字典则说明了每个字段的用途以及任何可能存在的别名。对 SQL 表达式的文档化来说这是完全必要的。
    创建模式
    一张图表胜过千言万语:开发人员不仅要阅读和实现它,而且还要用它来帮助自己和用户对话。模式有助于提高协作效能,这样在先期的数据库设计中几乎不可能出现大的问题。模式不必弄的很复杂;甚至可以简单到手写在一张纸上就可以了。只是要保证其上的逻辑关系今后能产生效益。
    从输入输出下手
    在定义数据库表和字段需求(输入)时,首先应检查现有的或者已经设计出的报表、查询和视图(输出)以决定为了支持这些输出哪些是必要的表和字段。举个简单的例子:假如客户需要一个报表按照邮政编码排序、分段和求和,你要保证其中包括了单独的邮政编码字段而不要把邮政编码糅进地址字段里。
    报表技巧
    要了解用户通常是如何报告数据的:批处理还是在线提交报表?时间间隔是每天、每周、每月、每个季度还是每年?如果需要的话还可以考虑创建总结表。系统生成的主键在报表中很难管理。用户在具有系统生成主键的表内用副键进行检索往往会返回许多重复数据。这样的检索性能比较低而且容易引起混乱。
    理解客户需求
    看起来这应该是显而易见的事,但需求就是来自客户(这里要从内部和外部客户的角度考虑)。不要依赖用户写下来的需求,真正的需求在客户的脑袋里。你要让客户解释其需求,而且随着开发的继续,还要经常询问客户保证其需求仍然在开发的目的之中。一个不变的真理是:“只有我看见了我才知道我想要的是什么”必然会导致大量的返工,因为数据库没有达到客户从来没有写下来的需求标准。而更糟的是你对他们需求的解释只属于你自己,而且可能是完全错误的。
    第 2 部分 - 设计表和字段
    检查各种变化
    我在设计数据库的时候会考虑到哪些数据字段将来可能会发生变更。比方说,姓氏就是如此(注意是西方人的姓氏,比如女性结婚后从夫姓等)。所以,在建立系统存储客户信息时,我倾向于在单独的一个数据表里存储姓氏字段,而且还附加起始日和终止日等字段,这样就可以跟踪这一数据条目的变化。
    采用有意义的字段名
    有一回我参加开发过一个项目,其中有从其他程序员那里继承的程序,那个程序员喜欢用屏幕上显示数据指示用语命名字段,这也不赖,但不幸的是,她还喜欢用一些奇怪的命名法,其命名采用了匈牙利命名和控制序号的组合形式,比如 cbo1、txt2、txt2_b 等等。
    除非你在使用只面向你的缩写字段名的系统,否则请尽可能地把字段描述的清楚些。当然,也别做过头了,比如 Customer_Shipping_Address_Street_Line_1,虽然很富有说明性,但没人愿意键入这么长的名字,具体尺度就在你的把握中。
    采用前缀命名
    如果多个表里有好多同一类型的字段(比如 FirstName),你不妨用特定表的前缀(比如 CusLastName)来帮助你标识字段。

    时效性数据应包括“最近更新日期/时间”字段。时间标记对查找数据问题的原因、按日期重新处理/重载数据和清除旧数据特别有用。
    标准化和数据驱动
    数据的标准化不仅方便了自己而且也方便了其他人。比方说,假如你的用户界面要访问外部数据源(文件、XML 文档、其他数据库等),你不妨把相应的连接和路径信息存储在用户界面支持表里。还有,如果用户界面执行工作流之类的任务(发送邮件、打印信笺、修改记录状态等),那么产生工作流的数据也可以存放在数据库里。预先安排总需要付出努力,但如果这些过程采用数据驱动而非硬编码的方式,那么策略变更和维护都会方便得多。事实上,如果过程是数据驱动的,你就可以把相当大的责任推给用户,由用户来维护自己的工作流过程。
    标准化不能过头
    对那些不熟悉标准化一词(normalization)的人而言,标准化可以保证表内的字段都是最基础的要素,而这一措施有助于消除数据库中的数据冗余。标准化有好几种形式,但 Third Normal Form(3NF)通常被认为在性能、扩展性和数据完整性方面达到了最好平衡。简单来说,3NF 规定:
    * 表内的每一个值都只能被表达一次。
    * 表内的每一行都应该被唯一的标识(有唯一键)。
    * 表内不应该存储依赖于其他键的非键信息。
    遵守 3NF 标准的数据库具有以下特点:有一组表专门存放通过键连接起来的关联数据。比方说,某个存放客户及其有关定单的 3NF 数据库就可能有两个表:Customer 和 Order。Order 表不包含定单关联客户的任何信息,但表内会存放一个键值,该键指向 Customer 表里包含该客户信息的那一行。
    更高层次的标准化也有,但更标准是否就一定更好呢?答案是不一定。事实上,对某些项目来说,甚至就连 3NF 都可能给数据库引入太高的复杂性。

    为了效率的缘故,对表不进行标准化有时也是必要的,这样的例子很多。曾经有个开发餐饮分析软件的活就是用非标准化表把查询时间从平均 40 秒降低到了两秒左右。虽然我不得不这么做,但我绝不把数据表的非标准化当作当然的设计理念。而具体的操作不过是一种派生。所以如果表出了问题重新产生非标准化的表是完全可能的。
    Microsoft Visual FoxPro 报表技巧
    如果你正在使用 Microsoft Visual FoxPro,你可以用对用户友好的字段名来代替编号的名称:比如用 Customer Name 代替 txtCNaM。这样,当你用向导程序 [Wizards,台湾人称为‘精灵’] 创建表单和报表时,其名字会让那些不是程序员的人更容易阅读。
    不活跃或者不采用的指示符
    增加一个字段表示所在记录是否在业务中不再活跃挺有用的。不管是客户、员工还是其他什么人,这样做都能有助于再运行查询的时候过滤活跃或者不活跃状态。同时还消除了新用户在采用数据时所面临的一些问题,比如,某些记录可能不再为他们所用,再删除的时候可以起到一定的防范作用。
    使用角色实体定义属于某类别的列[字段]
    在需要对属于特定类别或者具有特定角色的事物做定义时,可以用角色实体来创建特定的时间关联关系,从而可以实现自我文档化。
    这里的含义不是让 PERSON 实体带有 Title 字段,而是说,为什么不用 PERSON 实体和 PERSON_TYPE 实体来描述人员呢?比方说,当 John Smith, Engineer 提升为 John Smith, Director 乃至最后爬到 John Smith, CIO 的高位,而所有你要做的不过是改变两个表 PERSON 和 PERSON_TYPE 之间关系的键值,同时增加一个日期/时间字段来知道变化是何时发生的。这样,你的 PERSON_TYPE 表就包含了所有 PERSON 的可能类型,比如 Associate、Engineer、Director、CIO 或者 CEO 等。
    还有个替代办法就是改变 PERSON 记录来反映新头衔的变化,不过这样一来在时间上无法跟踪个人所处位置的具体时间。
    采用常用实体命名机构数据
    组织数据的最简单办法就是采用常用名字,比如:PERSON、ORGANIZATION、ADDRESS 和 PHONE 等等。当你把这些常用的一般名字组合起来或者创建特定的相应副实体时,你就得到了自己用的特殊版本。开始的时候采用一般术语的主要原因在于所有的具体用户都能对抽象事物具体化。
    有了这些抽象表示,你就可以在第 2 级标识中采用自己的特殊名称,比如,PERSON 可能是 Employee、Spouse、Patient、Client、Customer、Vendor 或者 Teacher 等。同样的,ORGANIZATION 也可能是 MyCompany、MyDepartment、Competitor、Hospital、Warehouse、Government 等。最后 ADDRESS 可以具体为 Site、Location、Home、Work、Client、Vendor、Corporate 和 FieldOffice 等。
    采用一般抽象术语来标识“事物”的类别可以让你在关联数据以满足业务要求方面获得巨大的灵活性,同时这样做还可以显著降低数据存储所需的冗余量。
    用户来自世界各地
    在设计用到网络或者具有其他国际特性的数据库时,一定要记住大多数国家都有不同的字段格式,比如邮政编码等,有些国家,比如新西兰就没有邮政编码一说。
    数据重复需要采用分立的数据表
    如果你发现自己在重复输入数据,请创建新表和新的关系。
    每个表中都应该添加的 3 个有用的字段
    * dRecordCreationDate,在 VB 下默认是 Now(),而在 SQL Server 下默认为 GETDATE()
    * sRecordCreator,在 SQL Server 下默认为 NOT NULL DEFAULT USER
    * nRecordVersion,记录的版本标记;有助于准确说明记录中出现 null 数据或者丢失数据的原因
    对地址和电话采用多个字段
    描述街道地址就短短一行记录是不够的。Address_Line1、Address_Line2 和 Address_Line3 可以提供更大的灵活性。还有,电话号码和邮件地址最好拥有自己的数据表,其间具有自身的类型和标记类别。

    过分标准化可要小心,这样做可能会导致性能上出现问题。虽然地址和电话表分离通常可以达到最佳状态,但是如果需要经常访问这类信息,或许在其父表中存放“首选”信息(比如 Customer 等)更为妥当些。非标准化和加速访问之间的妥协是有一定意义的。
    使用多个名称字段
    我觉得很吃惊,许多人在数据库里就给 name 留一个字段。我觉得只有刚入门的开发人员才会这么做,但实际上网上这种做法非常普遍。我建议应该把姓氏和名字当作两个字段来处理,然后在查询的时候再把他们组合起来。

    我最常用的是在同一表中创建一个计算列[字段],通过它可以自动地连接标准化后的字段,这样数据变动的时候它也跟着变。不过,这样做在采用建模软件时得很机灵才行。总之,采用连接字段的方式可以有效的隔离用户应用和开发人员界面。
    提防大小写混用的对象名和特殊字符
    过去最令我恼火的事情之一就是数据库里有大小写混用的对象名,比如 CustomerData。这一问题从 Access 到 Oracle 数据库都存在。我不喜欢采用这种大小写混用的对象命名方法,结果还不得不手工修改名字。想想看,这种数据库/应用程序能混到采用更强大数据库的那一天吗?采用全部大写而且包含下划符的名字具有更好的可读性(CUSTOMER_DATA),绝对不要在对象名的字符之间留空格。
    小心保留词
    要保证你的字段名没有和保留词、数据库系统或者常用访问方法冲突,比如,最近我编写的一个 ODBC 连接程序里有个表,其中就用了 DESC 作为说明字段名。后果可想而知!DESC 是 DESCENDING 缩写后的保留词。表里的一个 SELECT * 语句倒是能用,但我得到的却是一大堆毫无用处的信息。
    保持字段名和类型的一致性
    在命名字段并为其指定数据类型的时候一定要保证一致性。假如字段在某个表中叫做“agreement_number”,你就别在另一个表里把名字改成“ref1”。假如数据类型在一个表里是整数,那在另一个表里可就别变成字符型了。记住,你干完自己的活了,其他人还要用你的数据库呢。
    仔细选择数字类型
    在 SQL 中使用 smallint 和 tinyint 类型要特别小心,比如,假如你想看看月销售总额,你的总额字段类型是 smallint,那么,如果总额超过了 $32,767 你就不能进行计算操作了。
    删除标记
    在表中包含一个“删除标记”字段,这样就可以把行标记为删除。在关系数据库里不要单独删除某一行;最好采用清除数据程序而且要仔细维护索引整体性。
    避免使用触发器
    触发器的功能通常可以用其他方式实现。在调试程序时触发器可能成为干扰。假如你确实需要采用触发器,你最好集中对它文档化。
    包含版本机制
    建议你在数据库中引入版本控制机制来确定使用中的数据库的版本。无论如何你都要实现这一要求。时间一长,用户的需求总是会改变的。最终可能会要求修改数据库结构。虽然你可以通过检查新字段或者索引来确定数据库结构的版本,但我发现把版本信息直接存放到数据库中不更为方便吗?。
    给文本字段留足余量
    ID 类型的文本字段,比如客户 ID 或定单号等等都应该设置得比一般想象更大,因为时间不长你多半就会因为要添加额外的字符而难堪不已。比方说,假设你的客户 ID 为 10 位数长。那你应该把数据库表字段的长度设为 12 或者 13 个字符长。这算浪费空间吗?是有一点,但也没你想象的那么多:一个字段加长 3 个字符在有 1 百万条记录,再加上一点索引的情况下才不过让整个数据库多占据 3MB 的空间。但这额外占据的空间却无需将来重构整个数据库就可以实现数据库规模的增长了。身份证的号码从 15 位变成 18 位就是最好和最惨痛的例子。
    列[字段]命名技巧
    我们发现,假如你给每个表的列[字段]名都采用统一的前缀,那么在编写 SQL 表达式的时候会得到大大的简化。这样做也确实有缺点,比如破坏了自动表连接工具的作用,后者把公共列[字段]名同某些数据库联系起来,不过就连这些工具有时不也连接错误嘛。举个简单的例子,假设有两个表:
    Customer 和 Order。Customer 表的前缀是 cu_,所以该表内的子段名如下:cu_name_id、cu_surname、cu_initials 和cu_address 等。Order 表的前缀是 or_,所以子段名是:
    or_order_id、or_cust_name_id、or_quantity 和 or_description 等。
    这样从数据库中选出全部数据的 SQL 语句可以写成如下所示:
    Select * From Customer, Order Where cu_surname = "MYNAME" ;
    and cu_name_id = or_cust_name_id and or_quantity = 1
    在没有这些前缀的情况下则写成这个样子(用别名来区分):
    Select * From Customer, Order Where Customer.surname = "MYNAME" ;
    and Customer.name_id = Order.cust_name_id and Order.quantity = 1
    第 1 个 SQL 语句没少键入多少字符。但如果查询涉及到 5 个表乃至更多的列[字段]你就知道这个技巧多有用了。

    2 部分 - 设计表和字段

    1. 检查各种变化
    我在设计数据库的时候会考虑到哪些数据字段将来可能会发生变更。比方说,姓氏就是如此(注意是西方人的姓氏,比如女性结婚后从夫姓等)。所以,在建立系统存储客户信息时,我倾向于在单独的一个数据表里存储姓氏字段,而且还附加起始日和终止日等字段,这样就可以跟踪这一数据条目的变化。

    2. 采用有意义的字段名

    有一回我参加开发过一个项目,其中有从其他程序员那里继承的程序,那个程序员喜欢用屏幕上显示数据指示用语命名字段,这也不赖,但不幸的是,她还喜欢用一些奇怪的命名法,其命名采用了匈牙利命名和控制序号的组合形式,比如 cbo1、txt2、txt2_b 等等。

    除非你在使用只面向你的缩写字段名的系统,否则请尽可能地把字段描述的清楚些。当然,也别做过头了,比如 Customer_Shipping_Address_Street_Line_1,虽然很富有说明性,但没人愿意键入这么长的名字,具体尺度就在你的把握中。

    3. 采用前缀命名

    如果多个表里有好多同一类型的字段(比如 FirstName),你不妨用特定表的前缀(比如 CusLastName)来帮助你标识字段。

    4,时效性数据应包括“最近更新日期/时间”字段。

    时间标记对查找数据问题的原因、按日期重新处理/重载数据和清除旧数据特别有用。

    5. 标准化和数据驱动

    数据的标准化不仅方便了自己而且也方便了其他人。比方说,假如你的用户界面要访问外部数据源(文件、XML 文档、其他数据库等),你不妨把相应的连接和路径信息存储在用户界面支持表里。还有,如果用户界面执行工作流之类的任务(发送邮件、打印信笺、修改记录状态等),那么产生工作流的数据也可以存放在数据库里。预先安排总需要付出努力,但如果这些过程采用数据驱动而非硬编码的方式,那么策略变更和维护都会方便得多。事实上,如果过程是数据驱动的,你就可以把相当大的责任推给用户,由用户来维护自己的工作流过程。

    6. 标准化不能过头

    对那些不熟悉标准化一词(normalization)的人而言,标准化可以保证表内的字段都是最基础的要素,而这一措施有助于消除数据库中的数据冗余。标准化有好几种形式,但 Third Normal Form(3NF)通常被认为在性能、扩展性和数据完整性方面达到了最好平衡。简单来说,3NF 规定:

    * 表内的每一个值都只能被表达一次。
    * 表内的每一行都应该被唯一的标识(有唯一键)。
    * 表内不应该存储依赖于其他键的非键信息。

    遵守 3NF 标准的数据库具有以下特点:有一组表专门存放通过键连接起来的关联数据。比方说,某个存放客户及其有关定单的 3NF 数据库就可能有两个表:Customer 和 Order。Order 表不包含定单关联客户的任何信息,但表内会存放一个键值,该键指向 Customer 表里包含该客户信息的那一行。

    更高层次的标准化也有,但更标准是否就一定更好呢?答案是不一定。事实上,对某些项目来说,甚至就连 3NF 都可能给数据库引入太高的复杂性。

    为了效率的缘故,对表不进行标准化有时也是必要的,这样的例子很多。曾经有个开发餐饮分析软件的活就是用非标准化表把查询时间从平均 40 秒降低到了两秒左右。虽然我不得不这么做,但我绝不把数据表的非标准化当作当然的设计理念。而具体的操作不过是一种派生。所以如果表出了问题重新产生非标准化的表是完全可能的。

    7. Microsoft Visual FoxPro 报表技巧

    如果你正在使用 Microsoft Visual FoxPro,你可以用对用户友好的字段名来代替编号的名称:比如用 Customer Name 代替 txtCNaM。这样,当你用向导程序 [Wizards,台湾人称为‘精灵’] 创建表单和报表时,其名字会让那些不是程序员的人更容易阅读。

    8. 不活跃或者不采用的指示符

    增加一个字段表示所在记录是否在业务中不再活跃挺有用的。不管是客户、员工还是其他什么人,这样做都能有助于再运行查询的时候过滤活跃或者不活跃状态。同时还消除了新用户在采用数据时所面临的一些问题,比如,某些记录可能不再为他们所用,再删除的时候可以起到一定的防范作用。

    9. 使用角色实体定义属于某类别的列[字段]

    在需要对属于特定类别或者具有特定角色的事物做定义时,可以用角色实体来创建特定的时间关联关系,从而可以实现自我文档化。

    这里的含义不是让 PERSON 实体带有 Title 字段,而是说,为什么不用 PERSON 实体和 PERSON_TYPE 实体来描述人员呢?比方说,当 John Smith, Engineer 提升为 John Smith, Director 乃至最后爬到 John Smith, CIO 的高位,而所有你要做的不过是改变两个表 PERSON 和 PERSON_TYPE 之间关系的键值,同时增加一个日期/时间字段来知道变化是何时发生的。这样,你的 PERSON_TYPE 表就包含了所有 PERSON 的可能类型,比如 Associate、Engineer、Director、CIO 或者 CEO 等。

    还有个替代办法就是改变 PERSON 记录来反映新头衔的变化,不过这样一来在时间上无法跟踪个人所处位置的具体时间。

    10. 采用常用实体命名机构数据

    组织数据的最简单办法就是采用常用名字,比如:PERSON、ORGANIZATION、ADDRESS 和 PHONE 等等。当你把这些常用的一般名字组合起来或者创建特定的相应副实体时,你就得到了自己用的特殊版本。开始的时候采用一般术语的主要原因在于所有的具体用户都能对抽象事物具体化。

    有了这些抽象表示,你就可以在第 2 级标识中采用自己的特殊名称,比如,PERSON 可能是 Employee、Spouse、Patient、Client、Customer、Vendor 或者 Teacher 等。同样的,ORGANIZATION 也可能是 MyCompany、MyDepartment、Competitor、Hospital、Warehouse、Government 等。最后 ADDRESS 可以具体为 Site、Location、Home、Work、Client、Vendor、Corporate 和 FieldOffice 等。

    采用一般抽象术语来标识“事物”的类别可以让你在关联数据以满足业务要求方面获得巨大的灵活性,同时这样做还可以显著降低数据存储所需的冗余量。

    11. 用户来自世界各地

    在设计用到网络或者具有其他国际特性的数据库时,一定要记住大多数国家都有不同的字段格式,比如邮政编码等,有些国家,比如新西兰就没有邮政编码一说。

    12. 数据重复需要采用分立的数据表

    如果你发现自己在重复输入数据,请创建新表和新的关系。

    13. 每个表中都应该添加的 3 个有用的字段

    * dRecordCreationDate,在 VB 下默认是 Now(),而在 SQL Server 下默认为 GETDATE()
    * sRecordCreator,在 SQL Server 下默认为 NOT NULL DEFAULT USER
    * nRecordVersion,记录的版本标记;有助于准确说明记录中出现 null 数据或者丢失数据的原因

    14. 对地址和电话采用多个字段

    描述街道地址就短短一行记录是不够的。Address_Line1、Address_Line2 和 Address_Line3 可以提供更大的灵活性。还有,电话号码和邮件地址最好拥有自己的数据表,其间具有自身的类型和标记类别。

    过分标准化可要小心,这样做可能会导致性能上出现问题。虽然地址和电话表分离通常可以达到最佳状态,但是如果需要经常访问这类信息,或许在其父表中存放“首选”信息(比如 Customer 等)更为妥当些。非标准化和加速访问之间的妥协是有一定意义的。

    15. 使用多个名称字段

    我觉得很吃惊,许多人在数据库里就给 name 留一个字段。我觉得只有刚入门的开发人员才会这么做,但实际上网上这种做法非常普遍。我建议应该把姓氏和名字当作两个字段来处理,然后在查询的时候再把他们组合起来。

    我最常用的是在同一表中创建一个计算列[字段],通过它可以自动地连接标准化后的字段,这样数据变动的时候它也跟着变。不过,这样做在采用建模软件时得很机灵才行。总之,采用连接字段的方式可以有效的隔离用户应用和开发人员界面。

    16. 提防大小写混用的对象名和特殊字符

    过去最令我恼火的事情之一就是数据库里有大小写混用的对象名,比如 CustomerData。这一问题从 Access 到 Oracle 数据库都存在。我不喜欢采用这种大小写混用的对象命名方法,结果还不得不手工修改名字。想想看,这种数据库/应用程序能混到采用更强大数据库的那一天吗?采用全部大写而且包含下划符的名字具有更好的可读性(CUSTOMER_DATA),绝对不要在对象名的字符之间留空格。

    17. 小心保留词

    要保证你的字段名没有和保留词、数据库系统或者常用访问方法冲突,比如,最近我编写的一个 ODBC 连接程序里有个表,其中就用了 DESC 作为说明字段名。后果可想而知!DESC 是 DESCENDING 缩写后的保留词。表里的一个 SELECT * 语句倒是能用,但我得到的却是一大堆毫无用处的信息。

    18. 保持字段名和类型的一致性

    在命名字段并为其指定数据类型的时候一定要保证一致性。假如字段在某个表中叫做“agreement_number”,你就别在另一个表里把名字改成“ref1”。假如数据类型在一个表里是整数,那在另一个表里可就别变成字符型了。记住,你干完自己的活了,其他人还要用你的数据库呢。

    19. 仔细选择数字类型
    在 SQL 中使用 smallint 和 tinyint 类型要特别小心,比如,假如你想看看月销售总额,你的总额字段类型是 smallint,那么,如果总额超过了 $32,767 你就不能进行计算操作了。

    20. 删除标记

    在表中包含一个“删除标记”字段,这样就可以把行标记为删除。在关系数据库里不要单独删除某一行;最好采用清除数据程序而且要仔细维护索引整体性。

    21. 避免使用触发器

    触发器的功能通常可以用其他方式实现。在调试程序时触发器可能成为干扰。假如你确实需要采用触发器,你最好集中对它文档化。

    22. 包含版本机制

    建议你在数据库中引入版本控制机制来确定使用中的数据库的版本。无论如何你都要实现这一要求。时间一长,用户的需求总是会改变的。最终可能会要求修改数据库结构。虽然你可以通过检查新字段或者索引来确定数据库结构的版本,但我发现把版本信息直接存放到数据库中不更为方便吗?。

    23. 给文本字段留足余量

    ID 类型的文本字段,比如客户 ID 或定单号等等都应该设置得比一般想象更大,因为时间不长你多半就会因为要添加额外的字符而难堪不已。比方说,假设你的客户 ID 为 10 位数长。那你应该把数据库表字段的长度设为 12 或者 13 个字符长。这算浪费空间吗?是有一点,但也没你想象的那么多:一个字段加长 3 个字符在有 1 百万条记录,再加上一点索引的情况下才不过让整个数据库多占据 3MB 的空间。但这额外占据的空间却无需将来重构整个数据库就可以实现数据库规模的增长了。身份证的号码从 15 位变成 18 位就是最好和最惨痛的例子。

    24. 列[字段]命名技巧

    我们发现,假如你给每个表的列[字段]名都采用统一的前缀,那么在编写 SQL 表达式的时候会得到大大的简化。这样做也确实有缺点,比如破坏了自动表连接工具的作用,后者把公共列[字段]名同某些数据库联系起来,不过就连这些工具有时不也连接错误嘛。

    举个简单的例子,假设有两个表:Customer 和 Order。Customer 表的前缀是 cu_,所以该表内的子段名如下:cu_name_id、cu_surname、cu_initials 和cu_address 等。Order 表的前缀是 or_,所以子段名是:

    or_order_id、or_cust_name_id、or_quantity 和 or_description 等。

    这样从数据库中选出全部数据的 SQL 语句可以写成如下所示:

    Select * From Customer, Order Where cu_surname = "MYNAME" ;
    and cu_name_id = or_cust_name_id and or_quantity = 1

    在没有这些前缀的情况下则写成这个样子(用别名来区分):

    Select * From Customer, Order Where Customer.surname = "MYNAME" ;
    and Customer.name_id = Order.cust_name_id and Order.quantity = 1

    第 1 个 SQL 语句没少键入多少字符。但如果查询涉及到 5 个表乃至更多的列[字段]你就知道这个技巧多有用了。

    第 3 部分 选择键和索引

    1. 数据采掘要预先计划
    我所在的某一客户部门一度要处理 8 万多份联系方式,同时填写每个客户的必要数据(这绝对不是小活)。我从中还要确定出一组客户作为市场目标。当我从最开始设计表和字段的时候,我试图不在主索引里增加太多的字段以便加快数据库的运行速度。然后我意识到特定的组查询和信息采掘既不准确速度也不快。结果只好在主索引中重建而且合并了数据字段。我发现有一个指示计划相当关键——当我想创建系统类型查找时为什么要采用号码作为主索引字段呢?我可以用传真号码进行检索,但是它几乎就象系统类型一样对我来说并不重要。采用后者作为主字段,数据库更新后重新索引和检索就快多了。
    可操作数据仓库(ODS)和数据仓库(DW)这两种环境下的数据索引是有差别的。在 DW 环境下,你要考虑销售部门是如何组织销售活动的。他们并不是数据库管理员,但是他们确定表内的键信息。这里设计人员或者数据库工作人员应该分析数据库结构从而确定出性能和正确输出之间的最佳条件。
    2. 使用系统生成的主键
    这类同技巧 1,但我觉得有必要在这里重复提醒大家。假如你总是在设计数据库的时候采用系统生成的键作为主键,那么你实际控制了数据库的索引完整性。这样,数据库和非人工机制就有效地控制了对存储数据中每一行的访问。
    采用系统生成键作为主键还有一个优点:当你拥有一致的键结构时,找到逻辑缺陷很容易。
    3. 分解字段用于索引
    为了分离命名字段和包含字段以支持用户定义的报表,请考虑分解其他字段(甚至主键)为其组成要素以便用户可以对其进行索引。索引将加快 SQL 和报表生成器脚本的执行速度。比方说,我通常在必须使用 SQL LIKE 表达式的情况下创建报表,因为 case number 字段无法分解为 year、serial number、case type 和 defendant code 等要素。性能也会变坏。假如年度和类型字段可以分解为索引字段那么这些报表运行起来就会快多了。
    4. 键设计 4 原则
    * 为关联字段创建外键。
    * 所有的键都必须唯一。
    * 避免使用复合键。
    * 外键总是关联唯一的键字段。
    5. 别忘了索引
    索引是从数据库中获取数据的最高效方式之一。95% 的数据库性能问题都可以采用索引技术得到解决。作为一条规则,我通常对逻辑主键使用唯一的成组索引,对系统键(作为存储过程)采用唯一的非成组索引,对任何外键列[字段]采用非成组索引。不过,索引就象是盐,太多了菜就咸了。你得考虑数据库的空间有多大,表如何进行访问,还有这些访问是否主要用作读写。
    大多数数据库都索引自动创建的主键字段,但是可别忘了索引外键,它们也是经常使用的键,比如运行查询显示主表和所有关联表的某条记录就用得上。还有,不要索引 memo/note 字段,不要索引大型字段(有很多字符),这样作会让索引占用太多的存储空间。
    6. 不要索引常用的小型表
    不要为小型数据表设置任何键,假如它们经常有插入和删除操作就更别这样作了。对这些插入和删除操作的索引维护可能比扫描表空间消耗更多的时间。
    7. 不要把社会保障号码(SSN)或身份证号码(ID)选作键
    永远都不要使用 SSN 或 ID 作为数据库的键。除了隐私原因以外,须知政府越来越趋向于不准许把 SSN 或 ID 用作除收入相关以外的其他目的,SSN 或 ID 需要手工输入。永远不要使用手工输入的键作为主键,因为一旦你输入错误,你唯一能做的就是删除整个记录然后从头开始。
    我在破解他人的程序时候,我看到很多人把 SSN 或 ID 还曾被用做系列号,当然尽管这么做是非法的。而且人们也都知道这是非法的,但他们已经习惯了。后来,随着盗取身份犯罪案件的增加,我现在的同行正痛苦地从一大摊子数据中把 SSN 或 ID 删除。
    8. 不要用用户的键
    在确定采用什么字段作为表的键的时候,可一定要小心用户将要编辑的字段。通常的情况下不要选择用户可编辑的字段作为键。这样做会迫使你采取以下两个措施:
    * 在创建记录之后对用户编辑字段的行为施加限制。假如你这么做了,你可能会发现你的应用程序在商务需求突然发生变化,而用户需要编辑那些不可编辑的字段时缺乏足够的灵活性。当用户在输入数据之后直到保存记录才发现系统出了问题他们该怎么想?删除重建?假如记录不可重建是否让用户走开?
    * 提出一些检测和纠正键冲突的方法。通常,费点精力也就搞定了,但是从性能上来看这样做的代价就比较大了。还有,键的纠正可能会迫使你突破你的数据和商业/用户界面层之间的隔离。
    所以还是重提一句老话:你的设计要适应用户而不是让用户来适应你的设计。
    不让主键具有可更新性的原因是在关系模式下,主键实现了不同表之间的关联。比如,Customer 表有一个主键 CustomerID,而客户的定单则存放在另一个表里。Order 表的主键可能是 OrderNo 或者 OrderNo、CustomerID 和日期的组合。不管你选择哪种键设置,你都需要在 Order 表中存放 CustomerID 来保证你可以给下定单的用户找到其定单记录。
    假如你在 Customer 表里修改了 CustomerID,那么你必须找出 Order 表中的所有相关记录对其进行修改。否则,有些定单就会不属于任何客户——数据库的完整性就算完蛋了。
    如果索引完整性规则施加到表一级,那么在不编写大量代码和附加删除记录的情况下几乎不可能改变某一条记录的键和数据库内所有关联的记录。而这一过程往往错误丛生所以应该尽量避免。
    9. 可选键有时可做主键
    记住,查询数据的不是机器而是人。
    假如你有可选键,你可能进一步把它用做主键。那样的话,你就拥有了建立强大索引的能力。这样可以阻止使用数据库的人不得不连接数据库从而恰当的过滤数据。在严格控制域表的数据库上,这种负载是比较醒目的。如果可选键真正有用,那就是达到了主键的水准。
    我的看法是,假如你有可选键,比如国家表内的 state_code,你不要在现有不能变动的唯一键上创建后续的键。你要做的无非是创建毫无价值的数据。如你因为过度使用表的后续键[别名]建立这种表的关联,操作负载真得需要考虑一下了。
    10. 别忘了外键
    大多数数据库索引自动创建的主键字段。但别忘了索引外键字段,它们在你想查询主表中的记录及其关联记录时每次都会用到。还有,不要索引 memo/notes 字段而且不要索引大型文本字段(许多字符),这样做会让你的索引占据大量的数据库空间。

    第 4 部分 - 保证数据的完整性

    1. 用约束而非商务规则强制数据完整性
    如果你按照商务规则来处理需求,那么你应当检查商务层次/用户界面:如果商务规则以后发生变化,那么只需要进行更新即可。假如需求源于维护数据完整性的需要,那么在数据库层面上需要施加限制条件。如果你在数据层确实采用了约束,你要保证有办法把更新不能通过约束检查的原因采用用户理解的语言通知用户界面。除非你的字段命名很冗长,否则字段名本身还不够。
    只要有可能,请采用数据库系统实现数据的完整性。这不但包括通过标准化实现的完整性而且还包括数据的功能性。在写数据的时候还可以增加触发器来保证数据的正确性。不要依赖于商务层保证数据完整性;它不能保证表之间(外键)的完整性所以不能强加于其他完整性规则之上。
    2. 分布式数据系统
    对分布式系统而言,在你决定是否在各个站点复制所有数据还是把数据保存在一个地方之前应该估计一下未来 5 年或者 10 年的数据量。当你把数据传送到其他站点的时候,最好在数据库字段中设置一些标记。在目的站点收到你的数据之后更新你的标记。为了进行这种数据传输,请写下你自己的批处理或者调度程序以特定时间间隔运行而不要让用户在每天的工作后传输数据。本地拷贝你的维护数据,比如计算常数和利息率等,设置版本号保证数据在每个站点都完全一致。
    3. 强制指示完整性
    没有好办法能在有害数据进入数据库之后消除它,所以你应该在它进入数据库之前将其剔除。激活数据库系统的指示完整性特性。这样可以保持数据的清洁而能迫使开发人员投入更多的时间处理错误条件。
    4. 关系
    如果两个实体之间存在多对一关系,而且还有可能转化为多对多关系,那么你最好一开始就设置成多对多关系。从现有的多对一关系转变为多对多关系比一开始就是多对多关系要难得多。
    5. 采用视图
    为了在你的数据库和你的应用程序代码之间提供另一层抽象,你可以为你的应用程序建立专门的视图而不必非要应用程序直接访问数据表。这样做还等于在处理数据库变更时给你提供了更多的自由。
    6. 给数据保有和恢复制定计划
    考虑数据保有策略并包含在设计过程中,预先设计你的数据恢复过程。采用可以发布给用户/开发人员的数据字典实现方便的数据识别同时保证对数据源文档化。编写在线更新来“更新查询”供以后万一数据丢失可以重新处理更新。
    7. 用存储过程让系统做重活
    解决了许多麻烦来产生一个具有高度完整性的数据库解决方案之后,我决定封装一些关联表的功能组,提供一整套常规的存储过程来访问各组以便加快速度和简化客户程序代码的开发。数据库不只是一个存放数据的地方,它也是简化编码之地。
    8. 使用查找
    控制数据完整性的最佳方式就是限制用户的选择。只要有可能都应该提供给用户一个清晰的价值列表供其选择。这样将减少键入代码的错误和误解同时提供数据的一致性。某些公共数据特别适合查找:国家代码、状态代码等。



    第 5 部分 - 各种小技巧

    1. 文档、文档、文档
    对所有的快捷方式、命名规范、限制和函数都要编制文档。
    采用给表、列[字段]、触发器等加注释的数据库工具。是的,这有点费事,但从长远来看,这样做对开发、支持和跟踪修改非常有用。
    取决于你使用的数据库系统,可能有一些软件会给你一些供你很快上手的文档。你可能希望先开始在说,然后获得越来越多的细节。或者你可能希望周期性的预排,在输入新数据同时随着你的进展对每一部分细节化。不管你选择哪种方式,总要对你的数据库文档化,或者在数据库自身的内部或者单独建立文档。这样,当你过了一年多时间后再回过头来做第 2 个版本,你犯错的机会将大大减少。
    2. 使用常用英语(或者其他任何语言)而不要使用编码
    为什么我们经常采用编码(比如 9935A 可能是‘青岛啤酒’的供应代码,4XF788-Q 可能是帐目编码)?理由很多。但是用户通常都用英语进行思考而不是编码。工作 5 年的会计或许知道 4XF788-Q 是什么东西,但新来的可就不一定了。在创建下拉菜单、列表、报表时最好按照英语名排序。假如你需要编码,那你可以在编码旁附上用户知道的英语。
    3. 保存常用信息
    让一个表专门存放一般数据库信息非常有用。我常在这个表里存放数据库当前版本、最近检查/修复(对 FoxPro)、关联设计文档的名称、客户等信息。这样可以实现一种简单机制跟踪数据库,当客户抱怨他们的数据库没有达到希望的要求而与你联系时,这样做对非客户机/服务器环境特别有用。
    4. 测试、测试、反复测试
    建立或者修订数据库之后,必须用用户新输入的数据测试数据字段。最重要的是,让用户进行测试并且同用户一道保证你选择的数据类型满足商业要求。测试需要在把新数据库投入实际服务之前完成。
    5. 检查设计
    在开发期间检查数据库设计的常用技术是通过其所支持的应用程序原型检查数据库。换句话说,针对每一种最终表达数据的原型应用,保证你检查了数据模型并且查看如何取出数据。
    6. Microsoft Visual FoxPro 设计技巧
    对复杂的 Microsoft Visual FoxPro 数据库应用程序而言,可以把所有的主表放在一个数据库容器文件里,然后增加其他数据库表文件和装载同原有数据库有关的特殊文件。根据需要用这些文件连接到主文件中的主表。比如数据输入、数据索引、统计分析、向管理层或者政府部门提供报表以及各类只读查询等。这一措施简化了用户和组权限的分配,而且有利于应用程序函数(存储过程)的分组和划分,从而在程序必须修改的时候易于管理。

    标签: , ,

    posted by William @ 08:06   0 comments
    About Me

    Name: William
    Home: Pudong, Shanghai, China
    About Me: Here is a collection of my program notes.I'd like to put all my notes and learning together.Welcome anybody to help me to perfect it.
    See my complete profile
    Previous Post
    Archives
    Links
    © 2005 Crazy ITer Template by Isnaini Dot Com