Golang 开发 Socket 通信时常用的 TCP 封包和解包协议

在开发 Socket 通信时,由于 TCP 协议的特性,在网络状况不佳的情况下,数据传输过程中经常会出现半包或粘包。为解决这一问题,通常我们需要自定义一个通信协议,增加一个 HEADER 部分,并在其中对数据包的长度进行声明,下面分享一段封包和解包的示例代码,可用于 Golang 开发 Socket 时处理数据传输,具体代码如下:

package protocol
 
import (
    "bytes"
    "encoding/binary"
)
 
const (
    TCP_HEADER     = "TCPHEADER"
    TCP_HEADER_LEN = 9
    TCP_DATA_LEN   = 4
)
 
// 封包
func Enpack(msg []byte) []byte {
    return append(append([]byte(TCP_HEADER), IntToBytes(len(msg))...), msg...)
}
 
// 解包
func Depack(buffer []byte, readerChannel chan []byte) []byte {
    length := len(buffer)
 
    var i int
    for i = 0; i < length; i++ {
        if length < i + TCP_HEADER_LEN + TCP_DATA_LEN {
            break
        }
        if string(buffer[i:i + TCP_HEADER_LEN]) == TCP_HEADER {
            msgLen := BytesToInt(buffer[i + TCP_HEADER_LEN : i + TCP_HEADER_LEN + TCP_DATA_LEN])
            if length < i + TCP_HEADER_LEN + TCP_DATA_LEN + msgLen {
                break
            }
            data := buffer[i + TCP_HEADER_LEN + TCP_DATA_LEN : i + TCP_HEADER_LEN + TCP_DATA_LEN + msgLen]
            readerChannel <- data
 
            i += TCP_HEADER_LEN + TCP_DATA_LEN + msgLen - 1
        }
    }
 
    if i == length {
        return make([]byte, 0)
    }
    
    return buffer[i:]
}
 
// 整形转换成字节
func IntToBytes(n int) []byte {
	x := int32(n)
    bytesBuffer := bytes.NewBuffer([]byte{})
    binary.Write(bytesBuffer, binary.BigEndian, x)
    
    return bytesBuffer.Bytes()
}
 
// 字节转换成整形
func BytesToInt(b []byte) int {
    bytesBuffer := bytes.NewBuffer(b)
    var x int32
    binary.Read(bytesBuffer, binary.BigEndian, &x)
    
    return int(x)
}

 

Linux进程间通信的六种主要手段

1.管道(Pipe)及有名管道(named pipe)

管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;

2.信号(Signal)

信号是比较复杂的通信方式,用于通知接受进程有某种事件生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期 信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上, 该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,sigaction函数重新实现了signal函数);

3.报文(Message)队列(消息队列)

消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。

阅读更多

苹果APNS推送效率研究总结

年底这段时间一直在研究苹果的APNS(英文全称:Apple Push Notification Service)服务,进行了很多尝试,积累了一些经验。写出来总结一下,有不对的地方欢迎指正。

关于推送效率,苹果官方给出的建议是当建立一个Socket通道后,尽可能将需要推送消息和接受的devicetoken连续发送至APNS服务器端。

但是,这里需要注意如果消息队列中存在不正确的devicetoken时,苹果会在接受到这个devicetoken时,强制中断当前的Socket通道,这样会造成后面的消息无法正常发送给APNS服务器。

怎么办?

可能会有人建议每推送一条消息就断开Socket通道重新连接一次,来保证推送成功率。这样做成功率的确可以保证,但效率实在太低。

那怎么办?

很简单,我的做法是在一个消息队列中,每发送一条消息,就去read当前的Socket通道,苹果会在遇到错误的devicetoken后进行标记,我们可以read到这个数据,从而将错误的devicetoken从队列中剔除,并尝试重新建立一个Socket通道,然后从错误的devicetoken后面继续推送。

这样,我们就可以放心大胆的去连续推送一个消息队列,而不用担心由于错误的devicetoken造成推送半途中断。

还有什么办法可以提升推送效率?

最简单的办法就是多线程或多进程处理消息队列,我们团队的做法是多进程,通过HASH将一个消息队列平均分布到多个服务器端的进程上,从而进一步加快推送的速度。

采用多进程还有一个考虑,那就是担心若采用多线程万一遇到某些未知的异常,结果很可能是全军覆没,所有线程一起挂掉。而多进程的状态下,一个进程出现问题,其他的进程还可以继续工作,尽可能将影响降至最低。

速度还能再快吗?

没问题,速度还想进一步提升,就要从网络带宽和服务器方面下功夫了。用n台服务器组成一个消息推送阵列,通过某种策略来分担一定量级的推送任务,每台服务器中再通过前面提到的多进程方式运作,相信效率能够提升的非常明显。

关于feedback

APNS的feedback是一个非常贴心的服务,他会告诉你近期推送的消息,有哪些设备由于卸载了应用而无法在通知中显示消息。

那么,我们通过定期从feedback中获得这些devicetoken后,在数据库中进行标记,在下次的推送中,从消息队列中剔除这些devicetoken,这样减少了无用功,推送一次会完成的更快。

其他建议

本着不重复发明轮子的思想,推荐一下ApnsPHP这个开源项目,这里就不放链接了,github上搜吧:)

阳光部落原创,更多内容请访问http://www.sunbloger.com/