学习参考视频:39-即时通信系统-V0.2用户上线及广播功能_哔哩哔哩_bilibili
基础server搭建
1、创建server类型 (java中的类包含属性和方法,这段代码相当于java中的属性)
//创建server类型
type Server struct {
Ip string
Port int
}
2、创建server接口(相当于java类中的构造函数)
// 创建server的接口,用于创建一个server类型的实例
func NewServer(ip string, port int) *Server {
server := &Server{
Ip: ip,
Port: port,
}
return server
}
//返回的是指向Server类型的指针
//server := NewServer("127.0.0.1",8888) #实例
3、启动函数(相当于java类中的方法)
// 启动服务器的端口
func (s *Server) Start() {
...
}
//其中(s *Server)是方法接收器,表示Start()方法是绑定在指向Server类型对象的指针上的,使得Start()成为了Server类型的方法。
4、main函数
func main() {
server := NewServer("127.0.0.1", 8888) //创建实例
server.Start()
}
笔记:
go语言不是纯粹的面向对象的编程语言,但我们可以通过结构体和方法来实现面向对象的编程风格
方法的定义格式:
func(接收器变量 接收器类型)方法名 (参数)(返回值){
...
}
接收器变量:命名建议为类型首字母小写,如 p *Person
接收器类型:指针类型或非指针类型
举例一个非指针类型:
//定义一个Person结构体 type Person struct { name string age int } //构造函数 func newPerson(name string, age int) *Person { person := &Person{ name: name, age: age, } return person } //print方法,和Person结构体绑定,只有Person构造体的实例化变量才能调用该方法 func (p Person) Print() { fmt.Printf("名字%s,年龄%d", p.name, p.age) } func main() { person := newPerson("lihua", 18) person.Print() //名字lihua,年龄18 }
基础client搭建
整体框架和server一样
其中在启动函数中加入连接server功能
添加读写功能
用户上线功能
客户端上线提醒功能:客户端发送一个连接请求,server知道它上线了,将user加入OnlineMap中。将上线消息发送给Message通道,会有一个goroutine会监听这个管道,一旦管道里有消息就会遍历Map并给每个用户发送这个消息,即发给每个user的管道。
1、给server增加两个属性:OnlineMap、Message channel
2、对于server增加两个方法
1)用户上线将用户加入map中,并将用户上线消息广播
广播方法:将上线消息写到Message通道中
2)监听Message Channel ,一旦有消息就发送给全部的在线User
最后在启动函数里 accept之前 加入启动监听Message的goroutine
3、对于user增加方法:监听当前user通道,一旦有消息就发给客户端
笔记:
运行:go run user.go server.go
另一个终端:go run client.go
附加现阶段代码
//server.go
package main
import (
"fmt"
"net"
"sync"
)
// 创建server类型
type Server struct {
Ip string
Port int
//增加onlineMap
OnlineMap map[string]*User //map是哈希列表,键是字符串类型,值是指向User类型对象的指针
mapLock sync.RWMutex //读写锁,用于保护map
//消息广播的channel
Message chan string
}
// 创建server的接口,用于创建一个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 (s *Server) ListenMessager() {
for {
msg := <-s.Message //从管道中读数据
//将msg发送给全部的在线User
s.mapLock.Lock()
for _, cli := range s.OnlineMap { //key不关心,只关心value,value就是指向User的指针
cli.C <- msg
}
s.mapLock.Unlock()
}
}
// 广播消息的方法 将上线消息写到Message通道中
func (s *Server) Broadcast(user *User, msg string) {
sendMsg := "[" + user.Addr + "]" + ":" + msg
//将sendMsg写到通道中
s.Message <- sendMsg
}
func (s *Server) Handler(conn net.Conn) {
user := NewUser(conn)
//用户上线,将用户加入map中
s.mapLock.Lock()
s.OnlineMap[user.Name] = user
s.mapLock.Unlock()
//广播当前用户上线消息
s.Broadcast(user, "user is online")
}
// 启动服务器的端口
func (s *Server) Start() {
//socket listen
fmt.Println("服务端开始监听...")
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port))
if err != nil {
fmt.Println("listen err=", err)
return
}
defer listener.Close() //close
//启动监听Message的goroutine
go s.ListenMessager()
//accept
for {
//等待客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("Accept err=", err)
} else {
fmt.Printf("连接到客户端ip=%v\n", conn.RemoteAddr().String()) //查看conn
}
//do handler
go s.Handler(conn)
}
}
func main() {
server := NewServer("127.0.0.1", 8888) //创建实例
server.Start()
}
//client.go
package main
import (
"bufio"
"fmt"
"net"
"os"
"strings"
)
// 创建client类型
type Client struct {
ServerIp string
Serverport int
}
// 构造函数
func NewClient(ip string, port int) *Client {
client := &Client{
ServerIp: ip,
Serverport: port,
}
return client
}
// 发送数据功能
func (c *Client) send(conn net.Conn) {
reader := bufio.NewReader(os.Stdin) //os.Stdin 代表标准输入,就是终端
for {
//从终端读取一行用户的输入,并发送给服务器
line, err := reader.ReadString('\n') //:= 前的是返回值
if err != nil {
fmt.Println("readString err=", err)
}
//去掉转折符
line = strings.Trim(line, "\r\n")
//如果用户输入的是 exit 就退出
if line == "exit" {
break
}
//再将line 发送给服务器
_, err = conn.Write([]byte(line + "\n")) //conn write返回(n int, err eror)
if err != nil {
fmt.Println("conn.Write err=", err)
}
}
}
// 接收数据,并写在终端上
func (c *Client) recv(conn net.Conn) {
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err != nil {
fmt.Println("接收数据失败:", err)
return
}
fmt.Println("收到消息:", string(buf[:n]))
}
}
// 启动函数
func (c *Client) run() {
//连接server
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", c.ServerIp, c.Serverport))
if err != nil {
fmt.Println("client dial err=", err)
return
}
defer conn.Close()
go c.send(conn)
go c.recv(conn)
// 阻塞主goroutine,保持程序运行
select {}
}
func main() {
client := NewClient("127.0.0.1", 8888)
client.run()
}
//user.go
package main
import "net"
//User类型
type User struct {
Name string
Addr string
C chan string //表示通道里传的string类型
conn net.Conn
}
//构造函数
func NewUser(conn net.Conn) *User {
userAddr := conn.RemoteAddr().String() //拿到客户端的地址
user := &User{
Name: userAddr,
Addr: userAddr,
C: make(chan string), //创建一个通道
conn: conn,
}
go user.ListenMessage() //启动监听
return user
}
//方法:监听当前user通道,一旦有消息就发给客户端
func (u *User) ListenMessage() {
for {
msg := <-u.C 从channel中接收数据,并赋值给msg
u.conn.Write([]byte(msg + "\n")) //并把msg转为二进制发出去
}
}