收發消息實體
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
}