go socket 通信 打包问题

go新手问个问题:为什么这么打包发数据,服务端错误呢,是我哪里写错了还是怎么回事?首先服务端接收的代码是C++写的。之前用PHP写的客户端发送消息是这样子的:

$body = json_encode($body);

$head = pack('IsIsIII', strlen($body), $toType, $toIp, $fromType, $fromIp, $askId, $askId2);

$dataPack = $head . $body;

socket_write($socket, $dataPack, strlen($dataPack));

然后想用go来写个demo,遇到了这个问题。
提示的错误信息:

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x20 pc=0x331c]

goroutine 1 [running]:
panic(0x15c7c0, 0xc82000a0d0)
    /usr/local/go/src/runtime/panic.go:481 +0x3e6
main.main()
    /Users/fbbin/go/src/demo/socket_client.go:77 +0x55c
exit status 2

下面是代码

package main

import (
    "fmt"
    "net"
    "os"
    "bytes"
    "encoding/binary"
)

type Protocol struct {
    length int
    toType int
    toIp int
    fromType int
    fromIp int
    askId int
    askId2 int
    body []byte
}

func (p *Protocol) pack() ([]byte, error) {
    var packetInfo *bytes.Buffer = new(bytes.Buffer)
    var err error
    err = binary.Write(packetInfo, binary.LittleEndian, p.length)
    if err != nil {
        return nil, err
    }
    err = binary.Write(packetInfo, binary.LittleEndian, p.toType)
    if err != nil {
        return nil, err
    }
    err = binary.Write(packetInfo, binary.LittleEndian, p.toIp)
    if err != nil {
        return nil, err
    }
    err = binary.Write(packetInfo, binary.LittleEndian, p.fromType)
    if err != nil {
        return nil, err
    }
    err = binary.Write(packetInfo, binary.LittleEndian, p.fromIp)
    if err != nil {
        return nil, err
    }
    err = binary.Write(packetInfo, binary.LittleEndian, p.askId)
    if err != nil {
        return nil, err
    }
    err = binary.Write(packetInfo, binary.LittleEndian, p.askId2)
    if err != nil {
        return nil, err
    }
    err = binary.Write(packetInfo, binary.LittleEndian, p.body)
    if err != nil {
        return nil, err
    }
    return packetInfo.Bytes(), nil
}

func main() {
    server := "192.168.10.110:20001"
    tcpAddr, err := net.ResolveTCPAddr("tcp4", server)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
    conn, err := net.DialTCP("tcp", nil, tcpAddr)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
    defer conn.Close()
    fmt.Println("Connect Success!")
    msgInfo := "{\"cmdid\":\"Level.Site\",\"uid\":250}"
    // 收不到该消息
    result, err := conn.Write(packet(msgInfo))
    // 能收到,但是包内容错误
    // result, err := conn.Write([]byte(msgInfo))
    fmt.Println(result, err.Error())
}

func packet(message string) []byte {
    var bodyInfo []byte = []byte(message)
    headInfo := &Protocol{
        length:len(message),
        toType:20001,
        toIp:12039112,
        fromType:20003,
        fromIp:3812912,
        askId:12122,
        askId2:12123,
        body:bodyInfo,
    }
    packInfo, err := headInfo.pack()
    if err != nil {
        return nil
    }
    return packInfo
}

建议:

panic内容贴全,因为go的panic内容会准确输出哪个文件的哪几行panic了,以及调用链,对调试用处很大。

并不知你的服务端到底是什么,于是只能猜测。

panic: runtime error: invalid memory address or nil pointer dereference这个错误一般出现在:

  1. 一个对象本不应该为空(null,nil),但是错误地被设置成空了,然后与此同时你又去调用了它的方法。所以说某些语言都会判断 if XXX!=nil,if (XXX!=null)

  2. 使用了错误的数组下标。

仅仅从文中看,应该是

    result, err := conn.Write(packet(msgInfo))
    // 能收到,但是包内容错误
    // result, err := conn.Write([]byte(msgInfo))
    fmt.Println(result, err.Error())

err为nil了,但是你又去调用了err的Error方法,于是报错。

还有个建议。

type Protocol struct {
    length int
    toType int
    toIp int
    fromType int
    fromIp int
    askId int
    askId2 int
    body []byte
}

虽然这样写是非常正规的封包方法,但是仍有部分可以修改的地方。

type Protocol struct {
    length int 
    //所有的int建议修改为uint,并且指定大小,避免不同平台出现问题,例如uint8。
    //一个uint8大小为一个byte,以此类推
    toType int
    toIp int
    fromType int
    fromIp int
    askId int
    askId2 int
    body []byte
}

====
补充

在我这里运行为第79行报错,结果如下:

    msgInfo := "{\"cmdid\":\"Level.Site\",\"uid\":250}"
    // 收不到该消息
    result, err := conn.Write(packet(msgInfo))
    // 能收到,但是包内容错误
    // result, err := conn.Write([]byte(msgInfo))
    fmt.Println(err)                  <-  err为nil
    fmt.Println(result, err.Error())  <-所以这里报错了,nil pointer

很显然,Conn已经创建成功了,并且数据也write成功了,因此result, err := conn.Write(packet(msgInfo))这里的err是nil。然而你又错误的认为这里有错误,强行打印err的信息,因此报错。
====
至于php的pack和go的咋转换……

可能是大端小段问题,或者int长度不一致两种原因

  • go 开多个goroutine,是在一个进程中完成,还是可能在多个进程中完成
  • 在国内服务器上部署revel
  • go语法里 & 符号用法?
  • 关于go的channel阻塞问题
  • gorm无法连接mysql?账号密码都对了
  • golang写代码的正确风格是什么?
  • 请问写go的mvc时候需要用到reflect吗?
  • golang 实现任务的下发与上报?
  • linux环境下golang安装第三方库的时候出错,求解决办法
  • go 有几行代码不懂。求好心人指教
  • 请问如何使用go写一个客户端上传文件?