关于Socket编程,我之前一直处于小数据传输的阶段,就像什么多人聊天之类的,这几天项目碰到了要做在线更新,就想着肯定要静静的写一个非文字信息传输的Socket程序


难点在于:

1、Socket编程中,发送数据和接受数据都需要声明一个缓冲区来存放数据,这个缓冲区的大小设置有讲究,大了可以提高数据传输效率,但是会增加丢包、粘包等现象,小了数据传输效率低,速度减慢并且程序计算量增大

2、一个大的文件不可能直接声明和他大小一样的缓冲区,必须要分包发送,这样字节数据的分割有讲究,最少效率不能拖后腿,影响全局传输速度

3、对于一个大的数据来说,分割成小的数据包后,对于服务器或者客户端来说都是零散的数据,并不知道当前传来的包是干嘛的,这时候每个数据包里应该都设计好数据协议,用来保证每个包正确的完成他的功能

 

首先解决第一个问题

缓冲区大小的设定,在我的程序里我是根据TCP协议(毕竟我的通信协议是TCP)的数据包大小来设定的,虽然不知道这样会不会对传输速度有实质性的改变,但总比猜出一个数字来要好一点,TCP数据包的最大容量是1492 - IP头(20) - TCP头(20) = 1452(BYTES),我的代码里减掉两个字节当做后面提到的协议位,剩下1450B来当数据位,这样程序的头就出来了(当然我这里直接封装成类了)

Dim Sockets As Socket
'用来存储要发送的数据包的大小,不包含指令位,TCP协议数据包的Data区域大小为1452
Dim _DataLength As Integer = 1450

'--------------------------------协议--------------------------------
'所有数据按照DataLength的长度来分割,第一位和第二位为指令位
'第一位表明这个包的状态,具体说明在PackageState枚举内
'第二位表明这个包作用,具体说明在PackageAction枚举内
'--------------------------------------------------------------------

''' <summary>
''' 构造函数
''' </summary>
''' <param name="S">传入一个Socket对象,用来接受和发送数据</param>
''' <remarks></remarks>
Public Sub New(ByVal S As Socket)
    Sockets = S
End Sub

''' <summary>
''' 构造函数
''' </summary>
''' <remarks></remarks>
Public Sub New()

End Sub

 

之后再来解决第二个问题,关于字节数据的分割,看上面的代码我们知道每个包已经定为1450B,也就是说当传入一个数据时,要根据这个数为单位分割成多个小数据包(比如10M的文件,就会分成7232个包,最后一个包肯定不是满的),这里直接上代码,原本以为用For还是会影响效率,但运行起来还是挺不错的,For循环里用Array类去操作字节数组保证了效率

''' <summary>
''' 分割字节数组,将一个长的字节数组分成指定元素长度的数组集合
''' </summary>
''' <param name="data">源字节数组</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function SplitByteArray(ByVal data As Byte()) As List(Of Byte())
    Dim list As New List(Of Byte())
    For i = 0 To data.Length - 1 Step _DataLength
        Dim tmp() As Byte
        If data.Length - i < _DataLength Then
            ReDim tmp(data.Length - i - 1)
            Array.ConstrainedCopy(data, i, tmp, 0, data.Length - i)
        Else
            ReDim tmp(_DataLength - 1)
            Array.ConstrainedCopy(data, i, tmp, 0, _DataLength)
        End If
        list.Add(tmp)
    Next
    Return list
End Function

 

最后就要解决最难的一个问题了,协议

在我的这个类里,我把协议分成两个字节放在了数据包的前两位,第一位表明这个包的状态,具体说明在PackageState枚举内,第二位表明这个包作用,具体说明在PackageAction枚举内,然后我就直接上枚举,这个枚举可以根据具体使用情况来定制

''' <summary>
''' 枚举此数据包的用途
''' </summary>
''' <remarks></remarks>
Enum PackageAction
    下载 = CByte(1)
    获取所有项目 = CByte(2)
    获取指定项目 = CByte(3)
    废包 = CByte(4)
End Enum

''' <summary>
''' 枚举此数据包的状态
''' </summary>
''' <remarks></remarks>
Enum PackageState
    后续还有包 = CByte(1)
    后续没有包 = CByte(2)
    请求下一个包 = CByte(3)
    心跳包 = CByte(4)
End Enum

好了,基本到这里的话Socket分包的核心问题都已经解决了,接下来就是该怎么发怎么收了,这个类的完整代码在下一篇文章发出