golang 即時通信系統


main.go

package main

func main() {
	server := NewServer("127.0.0.1", 8888)
	server.Start()
}

server.go

package main

import (
	"fmt"
	"io"
	"net"
	"sync"
	"time"
)

type Server struct {
	Ip   string
	Port int

	//在線用戶的列表
	OnlineMap map[string]*User
	mapLock   sync.RWMutex

	//消息廣播的channel
	Message chan string
}

//創建一個server的接口
func NewServer(ip string, port int) *Server {
	server := &Server{
		Ip:        ip,
		Port:      port,
		OnlineMap: make(map[string]*User),
		Message:   make(chan string),
	}

	return server
}

//監聽Message廣播消息channel的goroutine,一旦有消息就發送給全部的在線User
func (this *Server) ListenMessager() {
	for {
		msg := <-this.Message

		//將msg發送給全部的在線User
		this.mapLock.Lock()
		for _, cli := range this.OnlineMap {
			cli.C <- msg
		}
		this.mapLock.Unlock()
	}
}

//廣播消息的方法
func (this *Server) BroadCast(user *User, msg string) {
	sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg

	this.Message <- sendMsg
}

func (this *Server) Handler(conn net.Conn) {
	//...當前鏈接的業務
	//fmt.Println("鏈接建立成功")

	user := NewUser(conn, this)

	user.Online()

	//監聽用戶是否活躍的channel
	isLive := make(chan bool)

	//接受客戶端發送的消息
	go func() {
		buf := make([]byte, 4096)
		for {
			n, err := conn.Read(buf)
			if n == 0 {
				user.Offline()
				return
			}

			if err != nil && err != io.EOF {
				fmt.Println("Conn Read err:", err)
				return
			}

			//提取用戶的消息(去除'\n')
			msg := string(buf[:n-1])

			//用戶針對msg進行消息處理
			user.DoMessage(msg)

			//用戶的任意消息,代表當前用戶是一個活躍的
			isLive <- true
		}
	}()

	//當前handler阻塞
	for {
		select {
		case <-isLive:
			//當前用戶是活躍的,應該重置定時器
			//不做任何事情,為了激活select,更新下面的定時器

		case <-time.After(time.Second * 300):
			//已經超時
			//將當前的User強制的關閉

			user.SendMsg("你被踢了")

			//銷毀用的資源
			close(user.C)

			//關閉連接
			conn.Close()

			//退出當前Handler
			return //runtime.Goexit()
		}
	}
}

//啟動服務器的接口
func (this *Server) Start() {
	//socket listen
	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))
	if err != nil {
		fmt.Println("net.Listen err:", err)
		return
	}
	//close listen socket
	defer listener.Close()

	//啟動監聽Message的goroutine
	go this.ListenMessager()

	for {
		//accept
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener accept err:", err)
			continue
		}

		//do handler
		go this.Handler(conn)
	}
}

user.go

package main

import (
	"net"
	"strings"
)

type User struct {
	Name string
	Addr string
	C    chan string
	conn net.Conn

	server *Server
}

//創建一個用戶的API
func NewUser(conn net.Conn, server *Server) *User {
	userAddr := conn.RemoteAddr().String()

	user := &User{
		Name: userAddr,
		Addr: userAddr,
		C:    make(chan string),
		conn: conn,

		server: server,
	}

	//啟動監聽當前user channel消息的goroutine
	go user.ListenMessage()

	return user
}

//用戶的上線業務
func (this *User) Online() {

	//用戶上線,將用戶加入到onlineMap中
	this.server.mapLock.Lock()
	this.server.OnlineMap[this.Name] = this
	this.server.mapLock.Unlock()

	//廣播當前用戶上線消息
	this.server.BroadCast(this, "已上線")
}

//用戶的下線業務
func (this *User) Offline() {

	//用戶下線,將用戶從onlineMap中刪除
	this.server.mapLock.Lock()
	delete(this.server.OnlineMap, this.Name)
	this.server.mapLock.Unlock()

	//廣播當前用戶上線消息
	this.server.BroadCast(this, "下線")

}

//給當前User對應的客戶端發送消息
func (this *User) SendMsg(msg string) {
	this.conn.Write([]byte(msg))
}

//用戶處理消息的業務
func (this *User) DoMessage(msg string) {
	if msg == "who" {
		//查詢當前在線用戶都有哪些

		this.server.mapLock.Lock()
		for _, user := range this.server.OnlineMap {
			onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "在線...\n"
			this.SendMsg(onlineMsg)
		}
		this.server.mapLock.Unlock()

	} else if len(msg) > 7 && msg[:7] == "rename|" {
		//消息格式: rename|張三
		newName := strings.Split(msg, "|")[1]

		//判斷name是否存在
		_, ok := this.server.OnlineMap[newName]
		if ok {
			this.SendMsg("當前用戶名被使用\n")
		} else {
			this.server.mapLock.Lock()
			delete(this.server.OnlineMap, this.Name)
			this.server.OnlineMap[newName] = this
			this.server.mapLock.Unlock()

			this.Name = newName
			this.SendMsg("您已經更新用戶名:" + this.Name + "\n")
		}

	} else if len(msg) > 4 && msg[:3] == "to|" {
		//消息格式:  to|張三|消息內容

		//1 獲取對方的用戶名
		remoteName := strings.Split(msg, "|")[1]
		if remoteName == "" {
			this.SendMsg("消息格式不正確,請使用 \"to|張三|你好啊\"格式。\n")
			return
		}

		//2 根據用戶名 得到對方User對象
		remoteUser, ok := this.server.OnlineMap[remoteName]
		if !ok {
			this.SendMsg("該用戶名不不存在\n")
			return
		}

		//3 獲取消息內容,通過對方的User對象將消息內容發送過去
		content := strings.Split(msg, "|")[2]
		if content == "" {
			this.SendMsg("無消息內容,請重發\n")
			return
		}
		remoteUser.SendMsg(this.Name + "對您說:" + content)

	} else {
		this.server.BroadCast(this, msg)
	}
}

//監聽當前User channel的 方法,一旦有消息,就直接發送給對端客戶端
func (this *User) ListenMessage() {
	for {
		msg := <-this.C

		this.conn.Write([]byte(msg + "\n"))
	}
}

client.go

package main

import (
	"flag"
	"fmt"
	"io"
	"net"
	"os"
)

type Client struct {
	ServerIp   string
	ServerPort int
	Name       string
	conn       net.Conn
	flag       int //當前client的模式
}

func NewClient(serverIp string, serverPort int) *Client {
	//創建客戶端對象
	client := &Client{
		ServerIp:   serverIp,
		ServerPort: serverPort,
		flag:       999,
	}

	//鏈接server
	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))
	if err != nil {
		fmt.Println("net.Dial error:", err)
		return nil
	}

	client.conn = conn

	//返回對象
	return client
}

//處理server回應的消息, 直接顯示到標准輸出即可
func (client *Client) DealResponse() {
	//一旦client.conn有數據,就直接copy到stdout標准輸出上, 永久阻塞監聽
	io.Copy(os.Stdout, client.conn)
}

func (client *Client) menu() bool {
	var flag int

	fmt.Println("1.公聊模式")
	fmt.Println("2.私聊模式")
	fmt.Println("3.更新用戶名")
	fmt.Println("0.退出")

	fmt.Scanln(&flag)

	if flag >= 0 && flag <= 3 {
		client.flag = flag
		return true
	} else {
		fmt.Println(">>>>請輸入合法范圍內的數字<<<<")
		return false
	}

}

//查詢在線用戶
func (client *Client) SelectUsers() {
	sendMsg := "who\n"
	_, err := client.conn.Write([]byte(sendMsg))
	if err != nil {
		fmt.Println("conn Write err:", err)
		return
	}
}

//私聊模式
func (client *Client) PrivateChat() {
	var remoteName string
	var chatMsg string

	client.SelectUsers()
	fmt.Println(">>>>請輸入聊天對象[用戶名], exit退出:")
	fmt.Scanln(&remoteName)

	for remoteName != "exit" {
		fmt.Println(">>>>請輸入消息內容, exit退出:")
		fmt.Scanln(&chatMsg)

		for chatMsg != "exit" {
			//消息不為空則發送
			if len(chatMsg) != 0 {
				sendMsg := "to|" + remoteName + "|" + chatMsg + "\n\n"
				_, err := client.conn.Write([]byte(sendMsg))
				if err != nil {
					fmt.Println("conn Write err:", err)
					break
				}
			}

			chatMsg = ""
			fmt.Println(">>>>請輸入消息內容, exit退出:")
			fmt.Scanln(&chatMsg)
		}

		client.SelectUsers()
		fmt.Println(">>>>請輸入聊天對象[用戶名], exit退出:")
		fmt.Scanln(&remoteName)
	}
}

func (client *Client) PublicChat() {
	//提示用戶輸入消息
	var chatMsg string

	fmt.Println(">>>>請輸入聊天內容,exit退出.")
	fmt.Scanln(&chatMsg)

	for chatMsg != "exit" {
		//發給服務器

		//消息不為空則發送
		if len(chatMsg) != 0 {
			sendMsg := chatMsg + "\n"
			_, err := client.conn.Write([]byte(sendMsg))
			if err != nil {
				fmt.Println("conn Write err:", err)
				break
			}
		}

		chatMsg = ""
		fmt.Println(">>>>請輸入聊天內容,exit退出.")
		fmt.Scanln(&chatMsg)
	}

}

func (client *Client) UpdateName() bool {

	fmt.Println(">>>>請輸入用戶名:")
	fmt.Scanln(&client.Name)

	sendMsg := "rename|" + client.Name + "\n"
	_, err := client.conn.Write([]byte(sendMsg))
	if err != nil {
		fmt.Println("conn.Write err:", err)
		return false
	}

	return true
}

func (client *Client) Run() {
	for client.flag != 0 {
		for client.menu() != true {
		}

		//根據不同的模式處理不同的業務
		switch client.flag {
		case 1:
			//公聊模式
			client.PublicChat()
			break
		case 2:
			//私聊模式
			client.PrivateChat()
			break
		case 3:
			//更新用戶名
			client.UpdateName()
			break
		}
	}
}

var serverIp string
var serverPort int

//./client -ip 127.0.0.1 -port 8888
func init() {
	flag.StringVar(&serverIp, "ip", "127.0.0.1", "設置服務器IP地址(默認是127.0.0.1)")
	flag.IntVar(&serverPort, "port", 8888, "設置服務器端口(默認是8888)")
}

func main() {
	//命令行解析
	flag.Parse()

	client := NewClient(serverIp, serverPort)
	if client == nil {
		fmt.Println(">>>>> 鏈接服務器失敗...")
		return
	}

	//單獨開啟一個goroutine去處理server的回執消息
	go client.DealResponse()

	fmt.Println(">>>>>鏈接服務器成功...")

	//啟動客戶端的業務
	client.Run()
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM