• 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
    			}
    
    			//提取用户的消息(去除'
    ')
    			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 + ":" + "在线...
    "
    			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("当前用户名被使用
    ")
    		} 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 + "
    ")
    		}
    
    	} else if len(msg) > 4 && msg[:3] == "to|" {
    		//消息格式:  to|张三|消息内容
    
    		//1 获取对方的用户名
    		remoteName := strings.Split(msg, "|")[1]
    		if remoteName == "" {
    			this.SendMsg("消息格式不正确,请使用 "to|张三|你好啊"格式。
    ")
    			return
    		}
    
    		//2 根据用户名 得到对方User对象
    		remoteUser, ok := this.server.OnlineMap[remoteName]
    		if !ok {
    			this.SendMsg("该用户名不不存在
    ")
    			return
    		}
    
    		//3 获取消息内容,通过对方的User对象将消息内容发送过去
    		content := strings.Split(msg, "|")[2]
    		if content == "" {
    			this.SendMsg("无消息内容,请重发
    ")
    			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 + "
    "))
    	}
    }
    

    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
    "
    	_, 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 + "
    
    "
    				_, 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 + "
    "
    			_, 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 + "
    "
    	_, 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()
    }
    
  • 相关阅读:
    c++中利用宏定义简化for循环使用
    UVA1152- 枚举 /二分查找
    acm 模板
    Xwindow的文章
    编程语言博客
    csh与bash比较
    关于锁与并发的资料总结
    linux su和sudo命令的区别
    对Memcached使用的总结和使用场景
    iptables配置——NAT地址转换
  • 原文地址:https://www.cnblogs.com/dech/p/14941579.html
Copyright © 2020-2023  润新知