头版头条 >
p>  上次的网络编程专题,南海向大家讲了网络的基础Socket接口,并详细介绍了简单的聊天程序的编写。相信大家一定知道了简单的聊天程序是怎么一回事了吧!

  在上次的最后南海说到《实现自制网络协议》这个问题,当时因为时间关系,没有很详细的讲解。这次,我们想从这个话题深入进去,然后在详细讲解如何编写一个类似OICQ的聊天工具。

  还想和大家说个问题,南海并不是一个对程序样样精通的程序员,南海在编程的世界里学习了两年多,只学习并精通了VB,其他的语言只是知道一点语法。因为南海认为编程重要的是方法而不是语言。语言只是用来实现方法的工具罢了。

  为了尽量照顾到大都数网友,南海准备在适当的时候不在使用VB作为编写示例的语言。前两天曾看到一个网友自制的编程工具-E。觉得不错,只是现在功能还不强大,大家可以去看看。请大家多给作者一点鼓励吧!!也希望大家喜欢网络编程系列专题。

  这次我们将对上次的例子做些修改,用来解决我们现有的问题,并作为两人世界的聊天工具。然后,我们将按照面向对象的思路,将所有的内容模块化封装起来,建立一个类似OICQ聊天工具。我们会在以后的专题里将邮件管理(FOXMAIL),数据服务器(类似SQLSERVER的数据传输)等常用的网络通讯方面的方法告诉大家。并还将会和ASP、PHP、JSP等服务器端脚本编制工具结合起来,希望能使大家对编写网络应用有个具体的了解。那我们开始这次的旅程吧……

网络协议究竟是怎么一回事?

现在我们首先要说的是数据流。假设我要一段留言给另一个同事,可能我会这样写:

XXX同事:

  你好!我因为XXX事需要和你商量,而你正好不在!如果你回来看到这个留言,请速与我联系。

  我的分机是XXX,如果我不在公司,请打我手机:13983080391(或家里电话:XXXXXXXX),谢谢!

  致

南海  

XXXX年XX月XX日

  大家请注意,在这里我们要表明3件事,我是谁,对方是谁,我需要找对方有什么事 富澳谌荩6杂诩扑慊此担⒚挥形颐撬哂械谋姹鹉芰ΑK裕颐峭ǔ;嵩谑莸南嘤ξ恢眉由弦桓霰昙牵绦蛟诜治龅氖焙颍涂梢酝ü昙堑哪谌葜澜酉吕吹氖菔歉墒裁吹牧恕?/p>

按照这个思路,我们将前面的留言改写,就变成:

收件人(TO): XXX同事
撰写人(FROM): 南海
时间(DATE): XXXX年XX月XX日
内容:

XXX同事:

  你好!我因为XXX事需要和你商量,而你正好不在!如果你回来看到这个留言,请速与我联系。

  我的分机是XXX,如果我不在公司,请打我手机:13983080391(或家里电话:XXXXXXXX),谢谢!

  致

  这样的话,程序就可以知道这个信将给谁看,是谁发的,内容是什么,在什么时间发的。

  说明了上面这几点,接下来我们就可以简单的定义出网络协议其实就是为了在网络上实现通讯,而定义出的一套数据格式。我们可以来做个实验,大家都可以试一试。如果WEB服务器是一台机器,我们可以打开盖子的话,我们将会发现,你在浏览这个WEB服务器上内容的时候,浏览器将騑EB服务器发送一段请求,服务器将请求解析后将内容发回给客户端。具体见图1和图2。

  请大家注意一下,在这里—浏览器所发请求是一个格式化过的数据。可以按照CRLF分割开来,里面还有很多命令。象GET、ACCEPT等。相信大家也该明白了吧,网络协议是用于组织数据的一种约定的方式。

设计简单的网络协议

  既然网络协议是这么简单的道理,那我们来尝试写一个简单的自定义的网络协议。为了使说明更加方便,我们将使用上次专题所讲的例子(见认识WINSOCK控件

  首先我们先回顾一下上次所讲的例子中有些什么。上次的聊天程序完成的功能是将一个窗体的数据发送到另一个窗体,并显示出来。那么,需要些什么呢?

    1. 对方的IP地址(发给谁)
    2. 自己的IP地址(是谁发的)
    3. 时间(发送的时间)
    4. 发给对方的数据(要说些什么)
我们分别将其定义为:

    1. [RemoteIP]
    2. [LocateIP]
    3. [SendDate]
    4. [SendData]

  那么在这四个标记中,[RemoteIP]和[LocateIP]之间是远端IP地址的数据,在[LocateIP]和[SendDate]之间是本地IP地址的数据,在[SendDate]和[SendData]之间是发送时的本地时间,在[SendData]一直到最后是聊天的内容。

程序的解析的实现如下:

’定义数据结构

Option Explicit

Type singleChat

LocateIP As String
RemoteIP As String
SendDate As String
SendData As String

End Type
Const Data = “[RemoteIP]213.213.213.97[LocateIP]213.213.213.96[SendDate]2000-9-21 15:24[SendData]你好!”

Sub main()

ReadData Data

End Sub

Private Sub ReadData(SouceData As String)

’这个过程用来分析输入字串的格式

’SouceData是从WinSock接受到的数据

Dim ChatData As singleChat

With ChatData

.LocateIP = SearchData(SouceData, “[LocateIP]”, “[SendDate]”)
.RemoteIP = SearchData(SouceData, “[RemoteIP]”, “[LocateIP]”)
.SendDate = SearchData(SouceData, “[SendDate]”, “[SendData]”)
.SendData = SearchData(SouceData, “[SendData]”, “”)
MsgBox “从” + .LocateIP + “发来”
MsgBox “发送到:” + .RemoteIP
MsgBox “发送时间:” + .SendDate
MsgBox “发送信息:” + .SendData

End With

End Sub

你可以从这里下载这段代码,测试一下。

封装WINSOCK控件

  封装是将已经完成的方法和属性、事件一起,包裹在一个密封的容器里,留出一个入口可以和外面联络。为此我们希望将我们的聊天作为一种方式封装起来。那么对于聊天来说,我们需要一些什么接口呢?南海把所需的一些归纳成如下几点:

    1. 通知服务器,南海已上线
    2. 从服务器上得到南海的在线朋友的资料
    3. 向朋友发送信息
    4. 得到别人发给南海的信息
    5. 服务器通知南海,南海有朋友上(下)线了

  大致就是这样,那么我们把这些资料定义成我们能够可以编写的内容:

属性:
persons 集合 朋友的集合
person 单项
方法:
connect 和服务器连接
send 象朋友发送消息
事件:
evtPersonData 从服务器得到资料
evtReceiveData 朋友发送消息过来

为此,我们将接口做成如下的部分:

clsChat:

Option Explicit
Private mvarPerson As clsPersons ’local copy
’To fire this event, use RaiseEvent with the following syntax:
’RaiseEvent evtListenData[(arg1, arg2, ... , argn)]
Public Event evtListenData(ByVal id As String, status As Boolean)
’To fire this event, use RaiseEvent with the following syntax:
’RaiseEvent evtReceiveData[(arg1, arg2, ... , argn)]
Public Event evtReceiveData(id As String, sData As String)
’local variable(s) to hold property value(s)
Private mvarSendSock As Winsock ’local copy
Private mvarListenSock As Winsock ’local copy

Public Property Set ListenSock(ByVal vData As Winsock)
  ’used when assigning an Object to the property, on the left side of a Set statement.
  ’Syntax: Set x.ListenSock = Form1
  Set mvarListenSock = vData
End Property

Public Property Set SendSock(ByVal vData As Winsock)
  ’used when assigning an Object to the property, on the left side of a Set statement.
  ’Syntax: Set x.SendSock = Form1
  Set mvarSendSock = vData
End Property

Public Sub send(sData As String)
End Sub

Public Sub connect()
End Sub
Public Property Set Person(ByVal vData As clsPersons)
  Set mvarPerson = vData
End Property

Public Property Get Person() As clsPersons
  Set Person = mvarPerson
End Property

Private Sub Class_Initialize()
  Set mvarPerson = New clsPersons
End Sub

Private Sub Class_Terminate()
  Set mvarPerson = Nothing
End Sub

clsPersons:

Option Explicit

’local variable to hold collection
Private mCol As Collection

Public Function Add(Key As String, Optional sKey As String) As clsPerson
  ’create a new object
  Dim objNewMember As clsPerson
  Set objNewMember = New clsPerson

  ’set the properties passed into the method
  objNewMember.Key = Key
  If Len(sKey) = 0 Then
    mCol.Add objNewMember
  Else
    mCol.Add objNewMember, sKey
  End If

  ’return the object created
  Set Add = objNewMember
  Set objNewMember = Nothing

End Function

Public Property Get Item(vntIndexKey As Variant) As clsPerson
  ’used when referencing an element in the collection
  ’vntIndexKey contains either the Index or Key to the collection,
  ’this is why it is declared as a Variant
  ’Syntax: Set foo = x.Item(xyz) or Set foo = x.Item(5)
  Set Item = mCol(vntIndexKey)
End Property

Public Property Get Count() As Long
  ’used when retrieving the number of elements in the
  ’collection. Syntax: Debug.Print x.Count
  Count = mCol.Count
End Property


Public Sub Remove(vntIndexKey As Variant)
  ’used when removing an element from the collection
  ’vntIndexKey contains either the Index or Key, which is why
  ’it is declared as a Variant
  ’Syntax: x.Remove(xyz)

  mCol.Remove vntIndexKey
End Sub


Public Property Get NewEnum() As IUnknown
  ’this property allows you to enumerate
  ’this collection with the For...Each syntax
  Set NewEnum = mCol.[_NewEnum]
End Property


Private Sub Class_Initialize()
  ’creates the collection when this class is created
  Set mCol = New Collection
End Sub


Private Sub Class_Terminate()
  ’destroys collection when this class is terminated
  Set mCol = Nothing
End Sub

clsPerson:

Option Explicit
Public Key As String
’local variable(s) to hold property value(s)
Private mvarID As String ’local copy
Private mvarthereIP As String ’local copy
Private mvarnowStatus As Boolean ’local copy
Private mvarChatDatas As String ’local copy
Private mvartopic As String ’local copy
Private mvarthsName As String ’local copy
Private Type ChatRecord
thisDate As String
thisNick As String
thisData As String
thisshow As Boolean
End Type
Public Property Let thsName(ByVal vData As String)
  ’used when assigning a value to the property, on the left side of an assignment.
  ’Syntax: X.thsName = 5
  mvarthsName = vData
End Property

Public Property Get thsName() As String
  ’used when retrieving value of a property, on the right side of an assignment.
  ’Syntax: Debug.Print X.thsName
  Set thsName = mvarthsName
End Property

Public Property Let topic(ByVal vData As String)
  ’used when assigning a value to the property, on the left side of an assignment.
  ’Syntax: X.topic = 5
  mvartopic = vData
End Property

Public Property Get topic() As String
  ’used when retrieving value of a property, on the right side of an assignment.
  ’Syntax: Debug.Print X.topic
  Set topic = mvartopic
End Property

Public Property Let ChatDatas(ByVal vData As String)
  ’used when assigning a value to the property, on the left side of an assignment.
  ’Syntax: X.ChatDatas = 5
  mvarChatDatas = vData
End Property

Public Property Get ChatDatas() As String
  ’used when retrieving value of a property, on the right side of an assignment.
  ’Syntax: Debug.Print X.ChatDatas
  ChatDatas = mvarChatDatas
End Property

Public Property Let nowStatus(ByVal vData As Boolean)
  ’used when assigning a value to the property, on the left side of an assignment.
  ’Syntax: X.nowStatus = 5
  mvarnowStatus = vData
End Property

Public Property Get nowStatus() As Boolean
  ’used when retrieving value of a property, on the right side of an assignment.
  ’Syntax: Debug.Print X.nowStatus
  Set nowStatus = mvarnowStatus
End Property

Public Property Let thereIP(ByVal vData As String)
  ’used when assigning a value to the property, on the left side of an assignment.
  ’Syntax: X.thereIP = 5
  mvarthereIP = vData
End Property

Public Property Get thereIP() As String
  ’used when retrieving value of a property, on the right side of an assignment.
  ’Syntax: Debug.Print X.thereIP
  Set thereIP = mvarthereIP
End Property

Public Property Let id(ByVal vData As String)
  ’used when assigning a value to the property, on the left side of an assignment.
  ’Syntax: X.ID = 5
  mvarID = vData
End Property

Public Property Get id() As String
  ’used when retrieving value of a property, on the right side of an assignment.
  ’Syntax: Debug.Print X.ID
  Set id = mvarID
End Property

  因为南海没有搞到一个好点的服务器,所以没有办法给大家一个好点的测试[平台,接下来的工作是相当的枯燥的,而现在该交代的都交代了,所以不如大家将南海前面所讲的好好归纳一下,看看你是否能够做一个出来呢?还有对做组件不懂的朋友,请你将你的问题告诉南海。今天这个算是给大家的作业吧:)我会在两天后将这些详细内容告诉大家,对于想知道答案的朋友,你可以期待南海的详细的讲解。而对于想挑战自己的朋友,你只有两天时间呀。所以,赶快……(注:公布时间是9月25号)

专题预告

我们这次写网络专题是想将网络编程的内容完完全全的讲清楚,为了考虑一些没有入门的和刚刚入门的朋友,我们罗嗦了两个专题,对于没有入门的朋友,可以看看我们出的教程。你也可以向南海提问,或发到BBS上,南海和一些编程高手们都很乐意为大家解决难题。以后我们将推出的专题分别是:

管理你自己的邮件

介绍收发EMAIL的原理,编写一个类似FOXMAIL的管理工具

做只“勤劳”的蚂蚁

介绍FTP上传和下载的原理,包括断点续传和多线程下载的方法

“隐藏”你的WEB数据库

研究ODBC的数据传输部分的特点,

建立自己的“分布式”应用

服务器脚本语言谁是王者?

JSP,让“心”奔腾

ASP,明天会更好

PHP,我的未来不是梦

定制网站应用平台之应用程序篇

定制网站应用平台之服务器端脚本篇

【发表评论】【关闭窗口】