收发消息实体
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
}