GoLang Socket 聊天实例

 收发消息实体

package model

// 发送或者接收的消息信息
type SmsMessage struct {
    // 消息 类型  1 注册   2登陆  3在线状态  4私聊信息   5群发信息 6下线线状态   7 服务端返回信息  8 服务端返回客户端发送失败或错误信息
    Type int32 `json:"type"`
    // 消息体
    Data string `json:"data"`
}

// 登陆请求信息
type SmsLogin struct {
    UserId int32 `json:"userid"`
    Pwd string   `json:"pwd"`
    UserName string `json:username`
}

// 发送注册信息
type SmsRegister struct {
    UserInfo
}

// 发送在线状态信息
type SmsLineStatus struct {
    // 当前用户Id
    UserId int32 `json:"userid"`
    // 在线状态   1, 在线,2为 离线
    Status int32 `json:"status"`
}

// 私聊信息
type SmsToOne struct {
    // 信息发送者Id
    SendUserId int32 `json:"senduserid"`
    // 接收者Id
    ToUserId int32 `json:"touserid"`
    // 发送内容
    Context string `json:"context"`
}

// 群发信息
type SmsToAll struct {
    // 消息发送者Id
    SendUserId int32 `json:"senduserid"`
    Context string `json:"context"`
}

// 服务端返回客户端信息
type SmsResponse struct {
    Code int32 `json:"code"`
    OnLineUser []int32  `json:"onlineuser"`
    Error string `json:"error"`
}

// 服务端返回客户端发送失败或错误信息
type SmsErrorResponse struct {
    Code int32 `json:"code"`
    Error string `json:"error"`
}

用户信息实体

package model

import "net"

// 用户信息
type UserInfo struct {
    UserId int32 `json:"userid"`
    UserName string `json:"username"`
    Pwd string `json:"pwd"`
}

type UserLineMsg struct {
    UserId int32 `json:"userid"`
    Conn net.Conn `json:"conn"`
}

客户端收发消息 服务Struct

package services

import (
    "MySocket/Commit/model"
    "encoding/binary"
    "encoding/json"
    "fmt"
    "net"
)
// 客户端读写实体
type SmsReadWriteClientService struct{
    Conn net.Conn
}

// 客户端读取服务端发来的数据
func (this *SmsReadWriteClientService) ReadPkg() (sms model.SmsMessage,err error){
    fmt.Println("客户端读取服务器端发来的数据……")
    var Buf [8096]byte
    _,err =this.Conn.Read(Buf[:4])
    if err!=nil{
         fmt.Println("客户端读取服务器端发来的数据长度出错:",err)
         return
    }
    var pkgLen uint32
    pkgLen=binary.BigEndian.Uint32(Buf[0:4])
    n,err:=this.Conn.Read(Buf[:pkgLen])
    if n!=int(pkgLen){
        fmt.Println("客户端读取服务器端发来的数据长度与接收的长度不致!")
        return
    }
    if err!=nil{
        fmt.Println("客户端读取服务器端发来的数据出错:",err)
        return
    }
    err =json.Unmarshal(Buf[:pkgLen],&sms)
    if err!=nil{
        fmt.Println("客户端读取服务器端发来的数据返序列化出错:",err)
        return
    }
    return
}

// 客户端发关消息到服务器
func (this *SmsReadWriteClientService) WritePkg(data []byte) (err error){
    var Buf [8096]byte
   var pkgLen uint32
   pkgLen=uint32(len(data))
   binary.BigEndian.PutUint32(Buf[0:4],pkgLen)
   // 发送长度
   n,err :=this.Conn.Write(Buf[:4])
   if n!=4 {
       fmt.Println("客户端发送信息长度写实际长度不一致!")
          return
   }
   if err!=nil{
       fmt.Println("客户端发送信息长度信息错误:",err)
       return
   }

   // 发送消息本身
   n,err =this.Conn.Write(data)
   if n!=int(pkgLen) {
       fmt.Println("客户端发送消息体写实际长度不一致!")
   }
   if err!=nil{
       fmt.Println("客户端发送消息体写错误:",err)
   }
   return
}

客户端思路:

    定义全局变量    

package services
import (
    "MySocket/Client/model"
)
type  ConstInfo struct {

}
var (
    // 当前用户含net.Conn的信息
    CurUer model.CurUser
)

 

// 当前用户含net.Conn的信息
var CurUer model.CurUser

// 全局变量 在线的好友用户Id
var oneLineUser []int32

   *用户端连接服务器 先进行登陆,登陆成功后:

                                          1.实体化【当前用户含net.Conn的信息】

                                           2.显示当前所有好友 (这个信息在登陆成功时返回)

                                           3.使 用协程保持与服务器连接 去循环读取服务端发来消息,根据不同的消息类型做不同的处理:如:好友上线,私聊、群聊

                                           4.循环读取前端待发的消息体,在循环体内通用Conn向服务器发送消息 

代码如下:

   a.  客户端Main方法 建入用户名密码,构建消息实体

package main

import (
    "MySocket/Client/services"
    "MySocket/Commit/model"
    "encoding/json"
    "fmt"
)


func main()  {
    for {
        var user model.UserInfo
        var UserId, UserName, Pwd = 0, "", ""
        fmt.Println("请输入UserId……")
        fmt.Scanln(&UserId)
        user.UserId = int32(UserId)
        fmt.Println("请输入UserName……")
        fmt.Scanln(&UserName)
        user.UserName = UserName
        fmt.Println("请输入Pwd……")
        fmt.Scanln(&Pwd)
        user.Pwd = Pwd
        // 向服务器发送消息的结构体
        var sms model.SmsMessage
        byteUser, err := json.Marshal(user)
        if err != nil {
            fmt.Println("客户端输入用户信息转Json出错:", err)
            return
        }
        sms.Data = string(byteUser)
        // 消息类型=2 表示登陆
        sms.Type = 2
        sendInfo := services.UserSmsProcessService{}
        // 调用这个方法进行登陆
        err = sendInfo.SendLogin(sms,user)
        if err != nil {
            fmt.Println("登陆失败:", err)
        }
    }



}

b.      sendInfo.SendLogin(sms,user)  向服务器发送登陆信息,当登陆成功后,

                                           1.实体化【当前用户含net.Conn的信息】

                                           2.显示当前所有好友 (这个信息在登陆成功时返回)

                                           3.使 用协程保持与服务器连接 去循环读取服务端发来消息,根据不同的消息类型做不同的处理:如:好友上线,私聊、群聊

                                           4.循环读取前端待发的消息体,在循环体内通用Conn向服务器发送消息 

 

package services

import (
    "MySocket/Commit/model"
    "encoding/json"
    "fmt"
    "io"
    "net"
    "strings"
)
var (
    // 全局变量 在线好友的用户Id
    oneLineUser []int32
)

type UserSmsProcessService struct {
    // Conn net.Conn
}

// 用户 登陆操作
func (this UserSmsProcessService) SendLogin(sms model.SmsMessage,user model.UserInfo) (err error){
    // 建立连接
    Conn,err :=net.Dial("tcp","127.0.0.1:8090")
    if err!=nil{
        fmt.Println("客户端连接服务器出错……")
        return
    }
    defer Conn.Close()
    byteSms,err :=json.Marshal(sms)
    if err!=nil{
        fmt.Println("客户端登陆数据序列化出错:",err)
        return
    }
    sendInfo := &SmsReadWriteClientService{Conn: Conn}
    err= sendInfo.WritePkg(byteSms)
    if err!=nil{
        fmt.Println("客户端登陆发送到服务器出错:",err)
        return
    }
    // 获取登陆返回信息
    ReadInfo := &SmsReadWriteClientService{Conn: Conn}
    loginResultMsg,err := ReadInfo.ReadPkg()
    if err!=nil{
        fmt.Println("客户端送接收登陆返回结果错误:",err)
        return
    }
     var response model.SmsResponse
    err = json.Unmarshal([]byte(loginResultMsg.Data),&response)
    if err!=nil{
        fmt.Println("客户端送接收登陆返回结果返序列化错误:",err)
        return
    }
    if response.Code==200{
        fmt.Println("登陆200……")
        //  1.实体化【当前用户含net.Conn的信息】
        CurUer.UserId=user.UserId
        CurUer.UserName=user.UserName
        CurUer.Conn=Conn
        oneLineUser= make([]int32,0)
        // 将当前在线的用户添加到客户端 全局 在线用户上去
        oneLineUser = append(oneLineUser, response.OnLineUser...)
        // 2.显示当前在线信息
        showinfo :=&ShowInfo{}
        showinfo.ShowOnLineUer(oneLineUser)
        /// *************** 3.保留该方法与服务端保持连接 *******************
        // 3.根据客户端接收到不同的消息做相应的处理    这里特别重要 *******************************
        go this.ServiceAndClientLine(Conn)
        for{    // 4.然后根据需要 随时向服务器发送想发的消息  注意:这里特别重要……**************************
            // 构造待发的消息体  通过输入的方式
            smsWaitSend,err:= showinfo.ShowMenuAndEnterSendContext()
            if err!=nil{
                fmt.Println("构建群聊或私聊包时出错:",err)
                continue
            }
            // 发送想要发的消息
            err= sendInfo.WritePkg(smsWaitSend)
            if err!=nil{
                fmt.Println("发送群聊或私聊出错:",err)
            }
        }
    }else {
        fmt.Println("登陆500……")
        fmt.Println("登陆失败")
        return
    }
    return
}



/// *************** 保留该方法与服务端保持连接 *******************
func (this *UserSmsProcessService) ServiceAndClientLine(Conn net.Conn){
    readOrWrite :=&SmsReadWriteClientService{Conn: Conn}
    for {
        fmt.Println("客户端正在等待服务端发送消息")
        sms,err := readOrWrite.ReadPkg()
        if err!=nil{
            if strings.Contains(err.Error(),"wsarecv: An existing connection was forcibly closed by the remote host."){
                Conn.Close()
                fmt.Println("与服务器断开了链接……")
                return
            }
            if err==io.EOF{
                fmt.Println("与服务器断开了链接……")
                return
            }
            fmt.Println("客户端正在等待服务端发送消息")
            continue
        }
        switch sms.Type {
            case 3:  // 有新用户上线
                var modelStatus model.SmsLineStatus
                err:= json.Unmarshal([]byte(sms.Data),&modelStatus)
                if err!=nil{
                    fmt.Println("接收服务器用户上线状态反序列化失败:",err)
                    continue
                }
                // 将新上线的用户添加到  在线用户列表中
                oneLineUser = append(oneLineUser, modelStatus.UserId)
                // 重新显示在线用户列表
                showinfo :=&ShowInfo{}
                showinfo.ShowOnLineUer(oneLineUser)
            case 4:     // 接到到的是私聊信息
                var toOne model.SmsToOne
                err:= json.Unmarshal([]byte(sms.Data),&toOne)
                if err!=nil{
                    fmt.Println("接收服务器用户私聊信息反序列化失败:",err)
                    continue
                }
                fmt.Println("用户:",toOne.SendUserId,"对你发来私信:",toOne.Context)
            case 8:
                fmt.Println(sms.Data)
            default:
        }
        // 用户自己也可以进行发送信息操作
        //showInfo :=&ShowInfo{}
        //showInfo.ShowMenuAndEnterSendContext()

    }
}

 

            上面的客户终端输入构建消息实体的方法及显示在线用户 见下:

package services

import (
    "MySocket/Commit/model"
    "encoding/json"
    "fmt"
)


type ShowInfo struct {

}

// 显示当前在线的用户
func (this *ShowInfo) ShowOnLineUer(users []int32){
    fmt.Println("当前在线用户Start:")
    for _,v :=range users{
        fmt.Println(v)
    }
    fmt.Println("当前在线用户End!")
}

/// 显示聊天菜单,根据自身需要建立发送聊天消息,准备发送
func (this *ShowInfo) ShowMenuAndEnterSendContext() (byteSms []byte,err error){
    var codeEnter int32
    var userId int32
    var Context string
    var sms model.SmsMessage
    fmt.Println("-------4. 私聊消息---------")
    fmt.Println("-------5. 群聊消息---------")
    fmt.Scanln(&codeEnter)
    sms.Type=codeEnter
    if codeEnter==4{
         var toone model.SmsToOne
         toone.SendUserId=CurUer.UserId
        fmt.Println("-------输入私聊人的UserId---------")
        fmt.Scanln(&userId)
         toone.ToUserId=userId
        fmt.Println("-------输入私聊人内容---------")
        fmt.Scanln(&Context)
         toone.Context=Context
         byteToone,err:=json.Marshal(toone)
         if err!=nil{
             fmt.Println("私聊内容序列化出错!",err)
             return this.ShowMenuAndEnterSendContext()
         }
         sms.Data=string(byteToone)

    }else  if codeEnter==5{
         var toall model.SmsToAll
         toall.SendUserId=CurUer.UserId
         fmt.Println("-------输入群聊内容---------")
         fmt.Scanln(&Context)
         byteToall,err :=json.Marshal(toall)
        if err!=nil{
            fmt.Println("私聊内容序列化出错!",err)
            return this.ShowMenuAndEnterSendContext()
        }
        sms.Data=string(byteToall)

    }else {
        fmt.Println("-------4. 私聊消息或5. 群聊消息---------")
        return this.ShowMenuAndEnterSendContext()
    }
    byteSms,err= json.Marshal(sms)
    if err!=nil{
        fmt.Println("构建聊天消息体序列化时出错:",err)
        return this.ShowMenuAndEnterSendContext()
    }
    return

}

 

服务端思路:

                      1.先监听端口,循环接收客户端连接,每个连接用一个协程来进行处理,

                      2.循环读取各连接发来的信息

                      3.根据发来不同类型的数据进行不同处理 ,如 登陆 -- 不管成功与否---返回客户端信息(成功还要返回在线的好友信息)

                                                                                                私聊信息-----定位私聊用户-------向其发送私聊信息

                                                                                                 群聊信息-----遍历当前所有在线用户-------分别发送群聊信息(注意发送给各用户的net.Conn使用*********特别注意)

服务器收发信息方法代码

package services

import (
    "MySocket/Commit/model"
    "encoding/binary"
    "encoding/json"
    "fmt"
    "net"
)

// 发送和读取信息
type  SmsReadWriteService struct {
    Conn net.Conn
}

// 读取客户端发来的信息
func (this *SmsReadWriteService) ReadPkg() (msg model.SmsMessage,err error){
    var Buf [8096]byte //这时传输时,使用缓冲
    fmt.Println("读取客户端发送的信息……")
    _,err =this.Conn.Read(Buf[:4])
    if err!=nil{
        fmt.Println("读取客户端送的字节数出错:",err,"……")
        return
    }
    var pkgLen uint32
    // 将读取到的字节数组转化成Uint32的数据长度
    pkgLen=binary.BigEndian.Uint32(Buf[0:4])
    // 读取该长度的数据
    n,err :=this.Conn.Read(Buf[:pkgLen])
    if n!=int(pkgLen){
        fmt.Println("服务端读取的字节数与接收到的字节数不一致^")
        return
    }
    if err!=nil{
        fmt.Println("服务端读取数据错误 :",err,"……")
        return
    }
    err = json.Unmarshal(Buf[:pkgLen],&msg)
    if err!=nil{
        fmt.Println("服务端读取的Buf[:pkgLen]返序列化成SmsMessage出错:",err,"……")
    }
    return
}

func (this *SmsReadWriteService) WritePkg(data []byte) (err error){
    // 先发送一个长度给对方
    var Buf [8096]byte //这时传输时,使用缓冲
    var pkgLen uint32
    pkgLen =uint32(len(data))
    binary.BigEndian.PutUint32(Buf[0:4],pkgLen)
    // 发送长度
    n,err :=this.Conn.Write(Buf[:4])
    if n!=4{
        fmt.Println("服务端写入数据与发送长度不一致^")
        return
    }
    if err!=nil{
        fmt.Println("服务端写入的Buf[:pkgLen]出错:",err,"……")
        return
    }
    // 发送消息本身
    n,err=this.Conn.Write(data)
    if n!=int(pkgLen) {
        fmt.Println("服务端写入数据与发送的【消息体】长度不一致^")
        return
    }
    if err!=nil{
        fmt.Println("服务端写入消息体出错:",err,"……")
        return
    }
    return

}

 

 

全局变量      

// 当前在线用户Map集合 在init()方法里实例化
OnLineUserMap map[int32]model.UserLineMsg

Main方法入口
 1.先监听端口,循环接收客户端连接

package main

import (
    "MySocket/Service/services"
    "fmt"
    "net"
)

func ResponseClientRequest(conn net.Conn){
    defer conn.Close()
    processService :=&services.ProcessorService{}
    // 循环接收客户端发来的信息,然后根据消息类型来做相应的处理
    processService.ProcessorRead(conn)
}

func main()  {
    fmt.Println("服务器开始监听127.0.0.1:8090!")
    listen,err :=net.Listen("tcp","127.0.0.1:8090")
    if err!=nil {
        fmt.Println("服务器监听127.0.0.1:8090失败!")
        return
    }
    defer listen.Close()
    for{
        conn,errCon:= listen.Accept()
        if errCon!=nil{
            fmt.Println("有一个连接失败……")
            continue
        }
        fmt.Println("客户端连接成功……")
        go ResponseClientRequest(conn)
    }

}

每个连接用一个协程来进行处理,循环读取各连接发来的信息

package services

import (
    "MySocket/Commit/model"
    "fmt"
    "io"
    "net"
)
var (
    // 当前在线用户Map集合
    OnLineUserMap map[int32]model.UserLineMsg
)
func init(){
    // 初始化当前  当前在线用户Map集合
    OnLineUserMap=make(map[int32]model.UserLineMsg)
}

type ProcessorService struct {
    // Conn net.Conn
}

// 循环读取客户端发送过来的信息
func (this *ProcessorService)  ProcessorRead(Conn net.Conn) (err error){
    // 循环读取客户端发送过来的信息
    for{
        read :=&SmsReadWriteService{
            Conn: Conn,
        }
        msg,err :=read.ReadPkg()
        if err!=nil{
            if err==io.EOF{
                fmt.Println("客户端退出,服务器端也退出..")
                return err
            }else {
                fmt.Println("readPkg err=", err)
                return err
            }
        }
        // 根据服务端接收的消息类型来做相应处理
        err =this.ProcessorReadMsg(&msg,Conn)
        if err!=nil{
            return err
        }
    }

}

// 根据服务端接收的消息类型来做相应处理
func (this *ProcessorService) ProcessorReadMsg(msg *model.SmsMessage,Conn net.Conn) (err error){

    //看看是否能接收到客户端发送的群发的消息
    fmt.Println("mes=", msg)
    // 消息 类型  1 注册   2登陆  3在线状态  4私聊信息   5群发信息
    switch msg.Type {
    case 1: // 处理注册信息
    case 2:
        one := &UserMsgProcessService{ // 用户信息处理
            Conn: Conn,
        }
        // 传递上线信息
        one.LoginInfo(msg)
    case 3: // 3在线状态 息
    case 4:         //4私聊信息
        toone := &UserMsgProcessService{ // 用户信息处理
             Conn: Conn,
        }
        toone.SendToOneMsg(msg)
    case 5: // 5群发信息

    }


    return
}

3.根据发来不同类型的数据进行不同处理 

package services

import (
    "MySocket/Commit/model"
    "encoding/json"
    "fmt"
    "net"
)

// 用户消息处理服务
type  UserMsgProcessService struct {
    Conn net.Conn
}


//// 处理登陆信息       Start
// 处理客户端发送过来的登陆信息
func (this *UserMsgProcessService) LoginInfo(msg *model.SmsMessage)  (err error){
    var login model.SmsLogin
    err =json.Unmarshal([]byte(msg.Data),&login)
    if err !=nil{
        fmt.Println("服务器处理登陆时出错:",err)
        return err
    }
    var smsResponse model.SmsResponse
    if login.Pwd=="123"{
        smsResponse.Code=200
        var model model.UserLineMsg
        model.UserId=login.UserId
        model.Conn=this.Conn
        OnLineUserMap[login.UserId]=model   // 将某人上线的信息写入到在线Map中
        userArr :=this.SendOneOnLine(login.UserId)   // 通知相关好友 当前用户上线了,并返回当前在线的人的IduserArr
        smsResponse.OnLineUser=userArr
    } else {
        smsResponse.Code=500
        smsResponse.Error="密码错误,请重新输入……"
    }
    byteSms,err :=json.Marshal(smsResponse)
    if err!=nil{
        fmt.Println("服务器处理返回信息时smsResponse转JSon出错:",err)
        return err
    }
    var response model.SmsMessage
    response.Type=7
    response.Data=string(byteSms)
    // 向客户端发送登陆是否成功的 状态码和错误 信息
    err = this.SendLoginOverToClient(response)
    return err
}


// 服务器告知相关好友,某人上线了  并返回当前在线的人的Id集userArr
func (this *UserMsgProcessService) SendOneOnLine(UserId int32) (userArr []int32){
    userArr=make([]int32,0)
    for _,v :=range OnLineUserMap{
        userArr = append(userArr,v.UserId )
        if v.UserId==UserId{
            continue
        }
        // 发送格式下的  上下线 实体
        var lineStatus model.SmsLineStatus
        lineStatus.UserId=UserId   // 谁上线了
        lineStatus.Status=3   // 上线
        // 向v用户发送UserId 上线状态 *****************************
        this.SendOneOnLineMsg(v,lineStatus)
    }
    return userArr
}

// 向某单个好友 发送上线用户的上线壮态或下线壮态  status=3 上线  status=6下线
// lineStatus model.SmsLineStatus   发送的上下线实体
// lineStatus.UserId=UserId   // 谁上线了
// lineStatus.Status=3    // 上线3 下线 6
func (this *UserMsgProcessService) SendOneOnLineMsg(u model.UserLineMsg,lineStatus model.SmsLineStatus){

    byteStatus,err :=json.Marshal(lineStatus)
    if err!=nil{
        fmt.Println("服务器处理byteStatus发送用户上线状态时出错:",err)
        return
    }
    // 发送消息实体
    var model model.SmsMessage
    // 消息类型 为上下线状态
    model.Type=lineStatus.Status
    model.Data=string(byteStatus)
    byteData,err :=json.Marshal(model)
    if err !=nil{
        fmt.Println("服务器处理byteData发送用户上线状态时出错:",err)
        return
    }
    //创建消息读写服务实例
    sendInfo :=&SmsReadWriteService{
        Conn: u.Conn,   // 注意  这里的Conn 是要发给谁的Conn *****************
    }
    // 向当前u用户发送某用户上下线消息
    sendInfo.WritePkg(byteData)
}

// 向客户端发送登陆是否成功的 状态码和错误 信息
func (this *UserMsgProcessService) SendLoginOverToClient(model model.SmsMessage) (err error){
    byteModel,err :=json.Marshal(model)
    if err!=nil{
        fmt.Println("服务器处理登陆结果转Json时出错:",err)
        return  err
    }
    sendInfo :=&SmsReadWriteService{
        Conn: this.Conn,
    }
    err= sendInfo.WritePkg(byteModel)
    return err

}

//// 处理登陆信息       End


// 有客户端发送私聊信息  处理
func (this UserMsgProcessService) SendToOneMsg(sms *model.SmsMessage) (err error){
    byteSms,err :=json.Marshal(sms)
    if err!=nil{
        fmt.Println("服务器序列化私聊整体消息时出错:",err)
        return
    }
    var smsToOne model.SmsToOne
    err= json.Unmarshal([]byte(sms.Data),&smsToOne)
    if err!=nil{
        fmt.Println("服务器返序列化私聊消息对像时出错:",err)
        return
    }
// 定义接收人是否存在
var userExists bool=false for _,v :=range OnLineUserMap{ if v.UserId==smsToOne.ToUserId{ userExists=true sendInfo :=&SmsReadWriteService{ Conn: v.Conn, // 注意这里Conn的传递,不要传错***************** }
// 向私聊人发送私聊消息 err
=sendInfo.WritePkg(byteSms) if err!=nil{ fmt.Println("服务器发送私聊消息时出错:",err) } return } }
// 假如接收人不存在
if !userExists{ var errSms model.SmsErrorResponse errSms.Code=500 errSms.Error="找不到该用户" sms.Type=8 byteErrContext,err:= json.Marshal(errSms) if err!=nil{ fmt.Println("服务器发找不到私聊用户返回内部信息序列化出错:",err) return err } sms.Data=string(byteErrContext) byteErrSms,err :=json.Marshal(sms) if err!=nil{ fmt.Println("服务器发找不到私聊用户返回整体信息序列化出错:",err) return err } sendInfo :=&SmsReadWriteService{ Conn: this.Conn, } err= sendInfo.WritePkg(byteErrSms) } return }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章