Go语言练习:聊天室(一)——server搭建、client搭建、用户上线提醒

学习参考视频: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转为二进制发出去
	}

}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值