使用Winsock控件,实现网络点对点通信

80酷酷网    80kuku.com

  控件|网络网络的阶梯第二话:
使用Winsock控件,实现网络点对点通信
  blog出现在CSDN,也就blog将不blog也。你会问为什么吧?无论你心里有没有这个问号,但在我心里这是个句号!你又会问为什么吗?好,不管你问不问。我决定,现在作答。但那种长篇大论的前因后果,请恕我无法一一罗列。我只能直接而又间接地说明,blog出现在CSDN,也就blog将不blog也。
  使用VB,写过网络程序,没试过Winsock这个控件实在是遗憾(API高手除外)。我想没有朋友有这种遗憾的...呵呵!因为,通过Winsock控件,你可以把网络通信简化简化再简化。那是什么程度?可能就是10行代码以内就行了那种(IDE生成的随外)!因为那只是网络通信,而通信,仅仅就发送一条信息,对方收到了,显示出来。可以算了吧?来看看:

  首先,窗口加载过程,我们写上:
            '设置了第一个Winsock控件进入等待
            Winsock1.LocalPort = 5052
            Winsock1.Listen
            '再来把第二个Winsock控件连向第一个
            Winsock2.Connect "127.0.0.1", 5052

  好了,这时Winsock1控件的ConnectionRequest事件触发。我们写上:
            If Winsock1.State <> sckClosed Then Winsock1.Close
            Winsock1.Accept requestID '接受连接

  就这样就连上啦!简单得很。再来:
            Winsock1.SendData Text1.Text '把Text1中的文本传给对方

  当然了,你传了数据给Winsock2,那它的DataArrival事件也触发了。
            Dim strDat As String
            Winsock2.GetData strDat '取得数据
            Text2.Text = strDat '在Text2中显示出来

  至此,一个使用Winsock控件,实现网络通信程序就完成了!哈哈哈,是不是觉得上当受骗了?这种点子,MSDN里早有出卖了!盖兹可真不是盖的,竟然早早想到用MSDN断我财路!唉.难怪他比我富!

  我完成任务了?没有,我要作的是《使用Winsock控件,实现网络点对点通信》。通信实现了,也是一个点对另一个点。可是,在普遍认识里,点对点并不是这么解释的。怎么解?就是双向通信吧!呵呵,但盖兹始终比我富。这点子在MSDN里又已经出卖了!

  不耐烦了吧?但要写程序,就不能怕烦。如果你决定走这条路,那还有更烦更烦的等着你。喂,烦人,你能看得下去吗?哈哈~!既然好点子全给盖兹拿去卖了,那还有什么好说的?就如,比尔卖你一个空的数据表,可是不填上数据。它还是一个空的数据表,而不是数据表!但比尔是不能帮你填上适合你的数据的,所以,你要照着你现有的数据表,向空的数据表填上数据,再把源数据表删了,那它就成了一个独一无二的数据表了!

  我看你真的不耐烦了...你饿吗?把鼠标蒸熟吃了顶一会吧!
  好,现在就让我说一说,点对点通信应用里的一个重要程序...但这次,不会再像上面那样,列出一行一行的代码,然后告诉你添加到那里,然后怎样...现在我要就“授人以渔”的政策,坚持“编程重思想”的方针;向“由浅入深”的路线,从“理解到认识”的哲学角度出发。解释一下,通过Winsock控件,在网络上传送文件的VB程序是怎么写的!

  正如前面的例子一样,用Winsock控件通信,得分开客户端与服务器,即:Client、Server(C\S)。但大家都知道,这仅是使用TCP协议必需要上演的一幕。而在UDP里,并没有明确的把C\S分开!但这样概念要变模糊了。这在以后的话题里,我会再作解释。今天我们的主角是TCP。
  上例中,那个循序渐进的步骤,大家体会到吗?没关系,现在来回顾一下。

服务器监听5052端口。
客端连接到服务器的5052端口,就是服务器程序监听的端口了!
由于有客户连接,服务器程序那的Winsock控件就触发ConnectionRequest事件(说明见MSDN)。
服务器接受了连接,客端程序的Winsock控件会触发Connect事件,以表示连接上(例省略)。
连接上后,由服务器程序向客户端程序发送数据。
数据到达客端,Winsock控件触发起客端程序的DataArrival事件提示,取得数据,显示!
  整个流程就这样,显然很简单了,不对吗?呵呵!然后,我们得到这么一个流程表后。就要开始填空了。打开我们想发送的文件读取数据给服务器发送。对方收到数据后,再写回到文件!过程就这么个样了!具体实现起来,还得要思考思考。好,现在我们就从一个用户的角度出发去想吧!

  两个Winsock怎么连上我就不说了,反正盖兹都已经比我富!那我们要传文件,先得让用户选择一个文件吧。然后就开始传啰!好传,慢着!客端怎么知道服务器要传的是什么类型的文件呢?好!有了这个想法,我们在用户点击服务器程序传送文件按扭的时候,获取刚才用户选择的文件的名字吧。要传过去了?还是不行,如果我们这个程序是聊天+传文件的话,那不是很混乱了?嗯,给这个信息一个名字吧!就叫:“Send”怎样?好,是个很有霸气的名字~大家用过QQ之类的聊天软件传文件都知道,QQ会先询问接收方是否接收XXXX.XXX的文件!那我们就模仿一下吧,把传送命令和文件名设置成:"Send 文件名",这样的一个格式。然后用Winsock的SendData方法传给对方。

  那就去了客户端编写代码啰:
  好,当收到这条命令,我们从前4个字得出,对方要传文件过来了!也没什么好写的,就弹个Msgbox出来说,对方要传XXXX.XXX给你,你愿意接收吗?好,当用户把保存路径确定下来了,然后在这样的路径里,创建一个这样的文件,以备接收到数据时写入。我们现在就得返回一条信息给服务器那,就叫:"ILiefly "吧!表示确认接收。

  又转回来服务器那写代码了:
  收到接收确认信息,我们开始进行读文件操作了!把文件打开,声明一个Byte数组。把打开的文件数据全读入到这个数组里。哈哈~不就成了吗?那传过去试试...

  客户端开始接收到数据了:
  嘿,糟糕,怎么会有数据类型错误呢?原来每当有数据到达客户端,我们在DataArrival事件写的代码都执行了。还以为是命令或聊天内容呢!不行,要给它设置一个标记,用作记录什么时候传过来的数据是文件的的数据吧。用一个Static的语句(见MSDN)声明一个静态布尔的变量,当False的时候是我们命令或聊天内容,而True的时候就是文件数据吧!再改一改上一次在客户端那写的源码,在答应接收之后,把这个布尔变量设为True!啊?问题又出来了!这时我想到,那什么时候才能把布尔变量设回False呢?倒,还是有问题!目光集中到服务器那里去...

  再回到服务器的代码编辑框:
  有什么办法让客户端知道什么时候传完呢?发送命令?不行,那里会把命令写入到文件的。连接关闭?更加不行,我还要聊天...嗯,要是让客户端知道传过去的文件长度,不就可以确定什么时候是接收完了吗?哈哈!行动,改一改传送文件的命令为:"Send 文件名 文件长度"。哦?太多空格啦,那样对方解释起来相当麻烦嘛,用InStr?不行,要是文件名里有空格怎办?不就又出错了...还好,我们有Split嘛(见MSDN)。好,那得把命令用个特别的字符分开了!就vbNullChar吧(见MSDN)!把命令改成:["SEND" & vbNullChar & 文件名 & vbNullChar & 文件长度]。传给对方。(注:FileLen函数)

  客户端要修改代码了:
  声明一个动态字串数组,把Split函数的返回值附给它,哈哈!下标0是命令,下标1是文件名,下标2是文件长度!爽了~把长度用模块域变量保存着,以备计算什么时候收完吧!(注:Val函数)

  继续在客户端那写我们没完的代码:
  布尔变量为True,有数据到达。那就取得它的数据吧!可要注意接收到的数据类型哦,对面传过来的不是字符串了,我们要指定类型为"vbArray + vbByte"(见MSND关于GetData的说明),意思就是字节数组啦。当然,我们要定义一个字节数组才能接收嘛 Dim ByteArray() As Byte。好,那刚才保存下来的长度就有用啦,总长度-当次收到的字节数=剩余字节数。当剩余字节数为0的时候,不就接收完了吗?哈哈!不急,先来组织一下思路:数据到达,取得,保存到字节数组。用UBound函数(见MSDN)得到字节数组的最大下标,但由于数组下标0也存着数据,但文件不是这么算啊,所以要算就得+1算!总长度 = 总长度 - UBound(ByteArray) + 1 嘿,行了!

  赶快运行试试,找个50多MB的传。呵呵,突然,铛!“内存不足”晕!搞什么鬼啊,再看一下进程列表,哇!我的程序占用了60MB左右的内存!晕,什么回事啊?是不是那里出错了?再重组一下思路:选择文件,发送命令,当收到返回确认信息,打开文件,读...读???不会吧,我把文件整个读入内存了,难怪难怪(其实不会发生这个错误的,因为Windows有虚拟内存嘛!但的确,要把整个文件读入内存再发送出去,有点让系统难为啊)!得优化优化了。

  回到服务器代码里进行优化:
  不能一次读入这么多,那...分开来吧!一次传送:8192个字节如何?好!就这样吧。但不是每个文件的字节数都为8192的倍数,怎办呢?嗯,作个计算。之前,我们不是得到了要传的文件的长度?既然对方都可以计算什么时候收完,那我也可以计算什么时候传完嘛!首先,确定一下,要传的文件长度是不是比8192大。如果是,那我们就把文件长度-8192。然后定义个下标是8191的字节数组(因为数组下标0也可以存数据嘛,所以要-1),用Get方法,读取打开的文件数据来填充这个字节数组。然后传过去...嗯?对方好像会收了啊,未达到文件总长度时,客户端不会把布尔数组设回来的。但,这边怎么知道什么时候可以传下一笔数据呢?嗯~SendComplete 事件——在完成一个发送操作时出现。好,就是这个了!我们在这边也用文件长度为0时作为结束传送的条件。如果不为0,那就再看看还没传的文件剩下多少,因为上面的计算已经-去了上次传送的字节了,那现在的文件长度变量就是剩下字节的数量啦,再比较一下,是否比8192大。哈哈~不是就定义一个这么大小的字节数组,读文件,送出!完成~[MsgBox "传输完毕!", vbInformation, "⊙_⌒γ - Server"]...
  但突然发现,原来SendComplete事件,在我们把传文件命令发出去之后也会触发。那没法子了,只好再定义一个布尔变量来确定什么时候是传文件吧...

  至此,一个用Winsock控件传送文件的VB程序写完了!呵呵,没睡着吧?不过我快睡着了!好困,上一次睡觉的时间只有3小时!现在-_-#了...但得说明一下,上面提到的8192,并不是我随便安上去的。这个在以后再作详细说明吧!

  嗯,要做的事还没完!好像我没说为什么:“blog出现在CSDN,也就blog将不blog也”哈哈~其实这篇东东已经说得很清楚了!

分享到
  • 微信分享
  • 新浪微博
  • QQ好友
  • QQ空间
点击: