回 帖 发 新 帖 刷新版面

主题:用vb写“QQ”

[img]http://blog.programfan.com/upfile/200811/20081107131451.jpg[/img]

软件下载地址: ftp://download:download@60.217.234.148/pfcsetup.exe (连接不稳定,请复制这个地址,用工具比如迅雷下载)
   本人的专业和计算机完全不沾边,只是业余的程序爱好者,学了这么多年的程序,准备再把这个软件写完就去深造我的专业知识了,觉得还是有点可惜的

    在编程序的过程中,曾经发现好多有用的东西,在这次编写这个软件的时候又想了起来。如果我觉得对大家有用的东西,我会陆续发到这个帖子里!

    这次编写的软件,是我写的第一个网络软件,因为曾经没有网络可以学习编程,一直是一大遗。
初步设想功能类似QQ,取名为PFC(programfan chat),没想到好名字!

    以前有点这方面知识,写这个软件前,把网络编程看了半天,还是有好多困难的地方,在实践中才逐渐解决的。最初我想用tcp协议写,但server承受能力有限,后来改用udp,丢包问题还没完全解决,不过已经想到办法。现在因为想实现传大量连续数据(比如文件,声音等), 不可能靠server中转,必须点对点传,内网点对点已经用vb+winsock实现(有一些目前p2p软件没实现的穿墙我也无能为力)!

测试说明:
    现在已有的功能:登录,读写信息(都是在服务器上操作的),查找功能(还不全),发彩字消息,传文件(UDP,索取模式,解决丢包问题),远程视频。
    下一步实现功能:音频(没时间做了,估计要2个月后了)。
    本来视频准备用directshow,但老是出错,没搞出来,我也是第一次写这个,不晓得哪里出问题了,还是用的是vfw,自己做了个camera.dll,整个软件资料比较大,打了包,现在发布出来了,有单机测试的server端和客户端,里面有说明,目前还有很多bug。

如果连不上的话就是server端程序没开!
为了安全,server端有个地址跟踪程序,请不要恶意攻击!
软件下载地址:ftp://download:download@60.217.234.148/pfcsetup.exe  (连接不稳定,请复制这个地址,用工具比如迅雷下载)

回复列表 (共57个回复)

11 楼

工作之余有点无聊,又把它改了下,现在变得有点庞大了....有点象qq了!
目前主体代码已经写好了
我准备把传输模式也改了下,对有外网ip的机器可以自己做个主机和内网及外网的建立联系,这也是目前网络聊天和下载软件普遍的模式,服务器的负荷会降低很多

目前服务器userinf文件还是个二进制文件,自己建立的一个简单数据库,改起来还比较麻烦

以后还想加上传文件、音像的功能
其实最想要的就是传声音,和远方朋友打游戏的时候很有用!!![em7]


我把名字改成 PFC 了,就是Program Fan Chat

希望做好了广大的编程爱好者都进来耍耍哈!

下面是个截图:

[img]http://blog.programfan.com/upfile/200811/20081107133438.jpg[/img]

12 楼

经过自己的实践,证实网上有个说法是错的
自己写的一段传送自定义类型数据的代码:
dim passStr() as byte'一定要可变数组,固定的不行
Private Sub SockSend_DataArrival(Index As Integer, ByVal bytesTotal As Long)
    'ReDim PassStr(1 To bytesTotal)'网上找到的东西加了这句,实际上没意义,无论怎么redim,最后结果都是Lbound=0,Len=bytesTotal,把Lbound改成1反而容易出错
    SockSend(Index).GetData PassStr 'socksend(index) 是winsock(数组)
    Call CopyMemory(NewID, PassStr(0), UBound(PassStr) + 1)
    NewID.myName = "测试"
    Call CopyMemory(PassStr(0), NewID, Len(NewID)) '用copymemory解决不能传自定义类型的问题
    SockSend(Index).SendData PassStr
如果一次对多个客户端发信息
需要在winsock.senddata x 后+doevents,否则只有1个能收到

刚开始写这个时,因为要考虑两台机器上的程序“同步”问题,思维感觉很乱,因为每端程序得到数据的时间无法预料
后来再次改写代码的时候,因为要考虑可能传送任何东西,包括传命令和普通信息等等,想到了在msg头里面加标志,就象网络传输报文一样,虽然“浪费”了几个字节,但是无论什么时候传来都可以识别,这样写起来非常轻松

13 楼

高手能说说,怎么把一段代码生成一个EXE么

14 楼

因为只有星期天才能写,郁闷到了
今天觉得代码写的不好,又把它全部重写了,哎!
进展太缓慢了,目前这个样子:

[img]http://blog.programfan.com/upfile/200811/20081107133546.jpg[/img]

15 楼

今天想起一个关于二进制文件结构化读写的问题,发出来与大家分享!

比如自定义一个二进制文件想存储用户的id,姓名,年龄,性别和密码等,用户有很多个
1。因为要考虑多个用户存储和删除的问题,那么每个存储单元长度最好一样长(这个和数据库结构类似,其实数据库也就那么回事,只是存储并没按顺序,但多了个索引表,看起来外观还是顺序的,如果有兴趣的话,可以自己做个数据库,用自己算法查找,还免得用那么庞大的数据库驱动程序)
那么通常容易想到定义一个变量类型如下:
type userInf
   myid as long
   myName as string*4
   myAge as byte
   mySex as String*1
   myPw as string*16
end type
dim MyInf as userInf
那么这个类型长度是26,其中注意的是string需要定长度
因为初次考虑可能不周全,免得以后文件没法扩展,需要加上
reserved(1 to 10) as byte保留一定长度供扩展用

2.就以上类型,假设设置一个myinf.myName="我是好心人",当写入文件再读出的时候,成了“我是好心"没人了,因为在这里存的是unicode,不是ascii码,所以需要把string 长度+一倍

3.假设设置myinf.mypw="1234",当输入检测密码时老是不对,因为读出的密码是"1234    "后面有空格,但不能用trim删除掉,因为密码里面有可能本来有空格,为了取得正常的密码,需要知道它本来的长度,那么现在定义变量类型如下:
type userInf
   myid as long
   myNameLen as byte
   myName as string*8
   myAge as byte
   mySex as byte'实际这里只存byte就可以了
   myPwLen as byte
   myPw as string*16
   reserve(1 to 10) as byte
end type
每次在存密码的时候,要把len(密码)一起存进去,用left(mypw,mypwlen)读出来

4.假设需要补存一个long 类型QQ号码
类型变成如下:

type userInf
   myid as long
   myid2 as byte'为说明下面的问题+的
   myNameLen as byte
   myName as string*8
   myAge as byte
   mySex as byte
   myPwLen as byte
   myPw as string*16
   myQQ as long 
   reserve(1 to 6) as byte
end type
dim infbyte() as byte
dim myInf as userinf
将数据存入变量后
执行 
redim infbyte(0 to len(myinf)-1)
copymemory infbyte(0),myinf,len(myinf)
然后将infbyte存入文件,再myinf变量读取文件后数据是错的

可以这样理解:因为windows系统的基本数据处理长度是4字节,也就是long,不是1(就是32位,这样cpu处理最快)。在API函数中是这样的。(这里我以前理解错了,一直以为vb变量中也是这样,哎)内存里面都是数据,要把它看成4个byte“一块一块”的,就象硬盘上512字节一块一块的一样.如果要保证long类型读取不出错,必须保证它在“一个块”上。如果是integer类型,那么不能占其第2,3字节。查看上面类型,在myQQ前面共有33byte长度,为满足条件,前面需要加到36byte,所以最终类型申明如下:
private type userInf
   myid as long
   myid2 as byte
   myNameLen as byte
   myName as string*8
   myAge as byte
   mySex as byte
   myPwLen as byte
   myPw as string*16
   reserve2(1 to 3) as byte'前面长度时36byte,不知算错没:)
   myQQ as long 
   reserve(1 to 6) as byte
end type

5.为了加快读写速度,数值变量最好都是long,把该变量类型的总长度设置成512或者其倍数,或者256,128之类的,如果有文件头的话,其长度也最好和其长度一样,以达到和文件读写一致。

以上只是我自己在写程序中总结出来的,如果有错,敬请指出!

16 楼

先谢谢楼主分享你的点滴

[quote]当存入myQQ后,读出来总是错误的,问题在哪里呢?(这里需要追溯到windows系统的变量存储方式,是系统里面的东西,这里不想说了,只把这个问题的解决方法给大家)
可以这样理解:因为windows系统的基本变量长度是4,也就是long,不是1。把内存里面都是数据,要把它看成4个byte“一块一块”的,就象硬盘上512字节一块一块的一样(就是读取的基本单位,不要试图从半路去读,要出错的,虽然我们用open 读文件好象哪里都可以读,但实际上文件系统操作的时候是把你要读的位置所在的一个扇区甚至一个簇都读到缓存里了,只是你没发现而已)。言归正传,如果要保证long类型读取不出错,必须保证它在“一个块”上。查看上面类型,在myQQ前面共有33byte长度,为满足条件,前面需要加到36byte,所以最终类型申明如下:[/quote]
这个好像叫“内存自动对齐”吧?在调用API的时候好像会自动对齐内存的,至于写文件的话,我不是很清楚,自己试一下:

[code=c]
Option Explicit

Private Type TEST
    a As Byte
    b As Integer
    c As Long
End Type

Private Sub Form_Load()
    
    Dim tt As TEST
    Dim i As Long
    
    Open "D:\test.txt" For Binary Access Write As #1
        For i = 1 To 3
            tt.a = i
            tt.b = i * 2
            tt.c = i * 3
            Put #1, , tt
            Debug.Print "Write", tt.a, tt.b, tt.c
        Next
    Close #1
    
    Open "D:\test.txt" For Binary Access Read As #1
        For i = 1 To 3
            Get #1, , tt
            Debug.Print "Read", tt.a, tt.b, tt.c
        Next
    Close #1
    
    End
    
End Sub
[/code]
结果:
[quote]
Write          1             2             3 
Write          2             4             6 
Write          3             6             9 
Read           1             2             3 
Read           2             4             6 
Read           3             6             9 
[/quote]

而且看了下文件大小,刚才3*7=21字节,好像没使用自动对齐呀?

17 楼

对了,第一次写就是用了copymemory API函数,难怪会出现,所以以后我就一直注意这个问题,每次申明变量类型都将变量对齐,一直以为vb变量里也是这样,可能是以前学vc引起的误导。现在想起来也是哈,如果vb也支持内存自动对齐,就不应该产生那种错误!

不过这样做还是有好处,否则很难兼容!

感谢tanchuhan,让我把这点理解得更深刻了

我已经把上面的内容改了!

18 楼

郁闷到了,现在工程搞大了
遇到个难题:
目前有外网ip的client之间或有外网ip的与内网client之间可以实现连接。
但要实现两个内网机器的直连,用winsock好象不能取得NAT映射的ip和port,不能实现p2p打洞,测试用api取,vb常崩溃,不稳定啊...............

请高手门来帮个忙啊!

19 楼

我突然又想起个问题,因为目前我用vb没能把p2p打洞实现,有个疑问:
当内网机器与另外一个ip连接后,会不会丢失对前一个连接的映射记录??
如下:
假设有client A和B,都是内网的,Server S有外网ip
初步用udp通讯,
1.当a,b分别连接到s后,s取得a和b的映射ip和port,并存下来
2。然后a和另外一个client c通讯了
3。现在b想和a通讯,向server发起请求
4,s得到命令后用原来记录的a的ip和port去通知a,那么现在a会不会丢失了s的映射记录,会不会不接受s的消息???
如果真是这样,难道client到server要用tcp连接,这样用普通机器做的server承受有点难度啊

20 楼

[quote]RemoteHostIP属性返回远程计算机的IP地址。在客户端,当使用了控件的Connect方法后,远程计算机的IP地址就赋给了RemoteHostIP属性,而在服务器端,当ConnectRequest事件后,远程计算机(客户端)的IP地址就赋给了这个属性。如果使用的是UDP协议那么当DataArrival事件后,发送UDP报文的计算机的IP才赋给了这个属性。[/quote]
网上找到这句话,原来如此!晚了,改天试试[em7][em19]

我来回复

您尚未登录,请登录后再回复。点此登录或注册