前言:
上篇文章說(shuō)道我們?cè)趺赐ㄟ^(guò)不同的數(shù)據(jù)去調(diào)用框架中不同的函數(shù),在socket編程中,服務(wù)器做的不僅僅是接受信息,還需要推送消息,最典型的場(chǎng)景就是聊天室了,每當(dāng)一個(gè)客戶端發(fā)送一個(gè)一條消息,服務(wù)端就需要把這條消息,循環(huán)發(fā)送給其它所有客戶端。已經(jīng)把所有代碼整合了,希望給個(gè)星星支持一下 microSocket。
實(shí)現(xiàn)思路:
我們知道在go語(yǔ)言socket編程時(shí),服務(wù)端向客戶端發(fā)送信息只需要 調(diào)用 conn.write()即可,一個(gè)連接就對(duì)應(yīng)一個(gè)conn,每次服務(wù)端從一個(gè)客戶端消息的時(shí)候,想要推送給別的客戶端信息就需要?jiǎng)e的客戶端的coon,所以我們的解決思路就是
- 每次客戶端和服務(wù)端握手成功后,就把當(dāng)前的conn,保存到全局 協(xié)程安全的map里
- 當(dāng)客戶端與服務(wù)端斷開(kāi)連接的時(shí)候就把這個(gè)coon從map里面刪除
每次服務(wù)器要廣播全體的時(shí)候,就把當(dāng)前的 map遍歷一遍,一次推送就好了
代碼實(shí)現(xiàn):
每次連接成功的時(shí)候就把coon 封裝成一個(gè)session
for {
//不斷地結(jié)束新的客戶端連接
conn, err := tcpListen.Accept()
if err != nil {
log.Println(err)
continue
}
//把conn封裝成一個(gè)session
sess := NewSession(fd, conn)
//把session 保存到sessionMaster
this.SessionMaster.SetSession(fd, sess)
fd++
go this.connHandle(conn, sess)
}
session 和sessionMaster的實(shí)現(xiàn)
//-------------------------------------------一個(gè)session代表一個(gè)連接------------------------------------------
type session struct {
id uint32
con net.Conn
times int64
lock sync.Mutex
}
func NewSession(id uint32, con net.Conn) *session {
return &session{
id: id,
con: con,
times: time.Now().Unix(),
}
}
//向conn寫(xiě)數(shù)據(jù)
func (this *session) write(msg string) error {
this.lock.Lock()
defer this.lock.Unlock()
_ ,errs := this.con.Write([]byte(msg))
return errs
}
//關(guān)閉conn
func (this *session)close(){
this.con.Close()
}
//.......................................SESSION管理類.......................................
type SessionM struct {
sessions map[uint32]*session
num uint32
lock sync.RWMutex
}
func NewSessonM() *SessionM {
return &SessionM{
sessions: make(map[uint32]*session),
num: 0,
}
}
func (this *SessionM) GetSessionById(id uint32) *session {
if v, exit := this.sessions[id]; exit {
return v
}
return nil
}
func (this *SessionM) SetSession(id uint32, sess *session) {
this.lock.Lock()
defer this.lock.Unlock()
this.sessions[id] = sess
}
//關(guān)閉連接并刪除
func (this *SessionM) DelSessionById(id uint32) {
this.lock.Lock()
defer this.lock.Unlock()
if v,exit := this.sessions[id];exit{
v.con.Close()
}
delete(this.sessions, id)
}
func (this *SessionM) WriteByid(id uint32, msg string) bool {
if v, exit := this.sessions[id]; exit {
if err := v.write(msg); err != nil {
this.DelSessionById(id)
return false
} else {
return true
}
}
return false
}
代碼解釋:
- 首先我們要封裝一個(gè)session類,一個(gè)session就對(duì)應(yīng)一個(gè)conn,大家可能發(fā)現(xiàn)到了,為什么conn.write
我們要加鎖呢。原因也很簡(jiǎn)單,就是為了保證,同一時(shí)間,最多只會(huì)有一個(gè)協(xié)程向一個(gè)coon寫(xiě)數(shù)據(jù),如果有多個(gè)協(xié)程同時(shí)寫(xiě)的話,就會(huì)發(fā)生數(shù)據(jù)混亂,所以這里的鎖是很有必要的! - 接下來(lái)就是sessionMaster類,服務(wù)端就是通過(guò)它來(lái)對(duì)所有客戶端的連接進(jìn)行操作,它的核心,其實(shí)就是一個(gè)sessions 的map,該map在刪除和插入新數(shù)據(jù)時(shí)都有加鎖處理,確保協(xié)程安全。當(dāng)服務(wù)端想要像客戶端推送消息的時(shí)候直接調(diào)用write方法就好了如下:
ser.SessionMaster.WriteByid(uint32(fd), "Hello")
總結(jié):
- 通過(guò)全局sessionMaster對(duì)所有客戶端的連接進(jìn)行統(tǒng)一管理,方便推送客戶端數(shù)據(jù)
- 每次對(duì)客戶端推送數(shù)據(jù)時(shí)必須要加鎖,不然數(shù)據(jù)可能會(huì)錯(cuò)亂
至此我們的socket框架分享,已經(jīng)差不多講完了,有什么不懂得,或者意見(jiàn)歡迎留言評(píng)論。