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"
)
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.根據客戶端接收到不同的訊息做相應的處理 這裡特別重要 *******************************
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 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
}