這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
初學GO不到兩周,本著熟悉語言的目標寫了這個小程式,漏洞很多,實現上寫的也有些渣渣,歡迎大家閱讀指點。
下載地址:https://github.com/yinxin630/gochat
簡單思路描述:
0、服務端監聽用戶端請求,完成會話轉寄的任務
1、服務端採用心跳包維護使用者線上狀態
2、用戶端通知服務端自己的監聽地址,建立服務端-用戶端資訊通道
服務端:
package mainimport ("fmt""net""os""strconv""time")//使用者資訊type User struct { userName string userAddr *net.UDPAddr userListenConn *net.UDPConn chatToConn *net.UDPConn}//伺服器監聽連接埠const LISTENPORT = 1616//緩衝區const BUFFSIZE = 1024var buff = make([]byte, BUFFSIZE)//線上使用者var onlineUser = make([]User, 0)//線上狀態判斷緩衝區var onlineCheckAddr = make([]*net.UDPAddr, 0)//錯誤處理func HandleError(err error) {if err != nil {fmt.Println(err.Error())os.Exit(2)}}//訊息處理func HandleMessage(udpListener *net.UDPConn) {n, addr, err := udpListener.ReadFromUDP(buff)HandleError(err)if n > 0 {msg := AnalyzeMessage(buff, n)switch msg[0] {//串連資訊case "connect "://擷取暱稱+連接埠userName := msg[1]userListenPort := msg[2]//擷取使用者ipip := AnalyzeMessage([]byte(addr.String()), len(addr.String()))//顯示登入資訊fmt.Println(" 暱稱:", userName, " 地址:", ip[0], " 使用者監聽連接埠:", userListenPort, " 登入成功!")//建立對使用者的串連,用於訊息轉寄userAddr, err := net.ResolveUDPAddr("udp4", ip[0] + ":" + userListenPort)HandleError(err)userConn, err := net.DialUDP("udp4", nil, userAddr)HandleError(err)//因為串連要持續使用,不能在這裡關閉串連//defer userConn.Close()//添加到線上使用者onlineUser = append(onlineUser, User{userName, addr, userConn, nil})case "online "://收到心跳包onlineCheckAddr = append(onlineCheckAddr, addr)case "outline "://退出訊息,未實現case "chat "://會話請求//尋找請求對象index := -1for i := 0; i < len(onlineUser); i++ {if onlineUser[i].userName == msg[1] {index = i}}//將所請求對象的串連添加到要求者中if index != -1 {nowUser, _ := FindUser(addr)onlineUser[nowUser].chatToConn = onlineUser[index].userListenConn}case "get "://向要求者返回線上使用者資訊index, _ := FindUser(addr)onlineUser[index].userListenConn.Write([]byte("當前共有" + strconv.Itoa(len(onlineUser)) + "位使用者線上"))for i, v := range onlineUser {onlineUser[index].userListenConn.Write([]byte("" + strconv.Itoa(i + 1) + ":" + v.userName))}default://訊息轉寄//擷取目前使用者index, _ := FindUser(addr)//擷取時間nowTime := time.Now()nowHour := strconv.Itoa(nowTime.Hour())nowMinute := strconv.Itoa(nowTime.Minute())nowSecond := strconv.Itoa(nowTime.Second())//請求會話對象是否存在if onlineUser[index].chatToConn == nil {onlineUser[index].userListenConn.Write([]byte("對方不線上"))} else {onlineUser[index].chatToConn.Write([]byte(onlineUser[index].userName + " " + nowHour + ":" + nowMinute + ":" + nowSecond + "\n" + msg[0]))}}}}//訊息解析,[]byte -> []stringfunc AnalyzeMessage(buff []byte, len int) ([]string) {analMsg := make([]string, 0)strNow := ""for i := 0; i < len; i++ {if string(buff[i:i + 1]) == ":" {analMsg = append(analMsg, strNow)strNow = ""} else {strNow += string(buff[i:i + 1])}}analMsg = append(analMsg, strNow)return analMsg}//尋找使用者,返回(位置,是否存在)func FindUser(addr *net.UDPAddr) (int, bool) {alreadyhave := falseindex := -1for i := 0; i < len(onlineUser); i++ {if onlineUser[i].userAddr.String() == addr.String() {alreadyhave = trueindex = ibreak}}return index, alreadyhave}//處理使用者線上資訊(暫時僅作刪除使用者使用)func HandleOnlineMessage(addr *net.UDPAddr, state bool) {index, alreadyhave := FindUser(addr)if state == false {if alreadyhave {onlineUser = append(onlineUser[:index], onlineUser[index + 1:len(onlineUser)]...) }}}//線上判斷,心跳包處理,每5s查看一次所有已線上使用者狀態func OnlineCheck() {for {onlineCheckAddr = make([]*net.UDPAddr, 0)sleepTimer := time.NewTimer(time.Second * 5)<- sleepTimer.Cfor i := 0; i < len(onlineUser); i++ {haved := falseFORIN:for j := 0; j < len(onlineCheckAddr); j++ {if onlineUser[i].userAddr.String() == onlineCheckAddr[j].String() {haved = truebreak FORIN}}if !haved {fmt.Println(onlineUser[i].userAddr.String() + "退出!")HandleOnlineMessage(onlineUser[i].userAddr, false)i--}}}}func main() {//監聽地址udpAddr, err := net.ResolveUDPAddr("udp4", "127.0.0.1:" + strconv.Itoa(LISTENPORT))HandleError(err)//監聽串連udpListener, err := net.ListenUDP("udp4", udpAddr)HandleError(err)defer udpListener.Close()fmt.Println("開始監聽:")//線上狀態判斷go OnlineCheck()for {//訊息處理HandleMessage(udpListener)}}
用戶端:
package mainimport ("fmt""os""strconv""net""bufio""math/rand""time")//資料包頭,標識資料內容var reflectString = map[string]string {"串連": "connect :","線上": "online :","聊天": "chat :","線上使用者": "get :",}//伺服器連接埠const CLIENTPORT = 1616//資料緩衝區const BUFFSIZE = 1024var buff = make([]byte, BUFFSIZE)//錯誤訊息處理func HandleError(err error) {if err != nil {fmt.Println(err.Error())os.Exit(2)}}//發送訊息func SendMessage(udpConn *net.UDPConn) {scaner := bufio.NewScanner(os.Stdin)for scaner.Scan() {if scaner.Text() == "exit" {return}udpConn.Write([]byte(scaner.Text()))}}//接收訊息func HandleMessage(udpListener *net.UDPConn) {for {n, _, err := udpListener.ReadFromUDP(buff)HandleError(err)if n > 0 {fmt.Println(string(buff[:n]))}}}/*func AnalyzeMessage(buff []byte, len int) ([]string) { analMsg := make([]string, 0) strNow := "" for i := 0; i < len; i++ { if string(buff[i:i + 1]) == ":" { analMsg = append(analMsg, strNow) strNow = "" } else { strNow += string(buff[i:i + 1]) } } analMsg = append(analMsg, strNow) return analMsg}*///發送心跳包func SendOnlineMessage(udpConn *net.UDPConn) {for {//每間隔1s向伺服器發送一次線上資訊udpConn.Write([]byte(reflectString["線上"]))sleepTimer := time.NewTimer(time.Second)<- sleepTimer.C}}func main() {//判斷命令列參數,參數應該為伺服器ipif len(os.Args) != 2 {fmt.Println("程式命令列參數錯誤!")os.Exit(2)}//擷取iphost := os.Args[1]//udp地址udpAddr, err := net.ResolveUDPAddr("udp4", host + ":" + strconv.Itoa(CLIENTPORT))HandleError(err)//udp串連udpConn, err := net.DialUDP("udp4", nil, udpAddr)HandleError(err)//本地監聽連接埠newSeed := rand.NewSource(int64(time.Now().Second()))newRand := rand.New(newSeed)randPort := newRand.Intn(30000) + 10000//本地監聽udp地址udpLocalAddr, err := net.ResolveUDPAddr("udp4", "127.0.0.1:" + strconv.Itoa(randPort))HandleError(err)//本地監聽udp串連udpListener, err := net.ListenUDP("udp4", udpLocalAddr)HandleError(err)//fmt.Println("監聽", randPort, "連接埠")//使用者暱稱userName := ""fmt.Printf("請輸入暱稱:")fmt.Scanf("%s", &userName)//向伺服器發送串連資訊(暱稱+本地監聽連接埠)udpConn.Write([]byte(reflectString["串連"] + userName + ":" + strconv.Itoa(randPort)))//關閉連接埠defer udpConn.Close()defer udpListener.Close()//發送心跳包go SendOnlineMessage(udpConn)//接收訊息go HandleMessage(udpListener)command := ""for {//擷取命令fmt.Scanf("%s", &command)switch command {case "chat" :people := ""//fmt.Printf("請輸入對方暱稱:")fmt.Scanf("%s", &people)//向伺服器發送聊天對象資訊udpConn.Write([]byte(reflectString["聊天"] + people))//進入會話SendMessage(udpConn)//退出會話fmt.Println("退出與" + people + "的會話")case "get" ://請求線上情況資訊udpConn.Write([]byte(reflectString["線上使用者"]))}}}
運行環境:本地多終端類比運行
運行圖:
服務端:
用戶端:
使用者一:
使用者二:
使用者三: