Go 聊天室 ( goroutine )

前言

看了無聞老師的一節關於 goroutine 與 channel 的講解課堂,感受不是很明白,因此決定來實現一個聊天室的功能服務器

爲何是羣聊呢?tcp

由於羣聊相對邏輯簡單些函數

注:本栗子只用到了 goroutine 並無用到 channel學習

概述

1.聊天室的組成

聊天室分爲兩個部分,分別是:spa

  • 服務端
  • 客戶端

而後,通常狀況下咱們互相聊天使用的都只是客戶端而已,服務端只是起到調度的做用線程

2.信息發送與接收的流程

假設咱們有 服務端(S) 客戶端(C1) 客戶端(C2) 客戶端(C3)code

而且 S 已經 與 C1 C2 C3 創建了鏈接blog

理論上的流程是這樣的:圖片

  1. C1S 發出信息
  2. S 接收到信息
  3. S 將接收到的信息廣播給 C2 C3
  4. C2 C3 接收信息

圖片描述

實踐

1.服務端

1) 代碼ip

package main

import (
    "time"
    "fmt"
    "net"
)

// 客戶端 map 
var client_map =  make(map[string]*net.TCPConn)

// 監聽請求
func listen_client(ip_port string) {
    tcpAddr, _ := net.ResolveTCPAddr("tcp", ip_port)
    tcpListener, _ := net.ListenTCP("tcp", tcpAddr)
    for {// 不停地接收
        client_con, _ := tcpListener.AcceptTCP()// 監聽請求鏈接
        client_map[client_con.RemoteAddr().String()] = client_con// 將鏈接添加到 map
        go add_receiver(client_con)
        fmt.Println("用戶 : ", client_con.RemoteAddr().String(), " 已鏈接.")
    }
}

// 向鏈接添加接收器
func add_receiver(current_connect *net.TCPConn) {
    for {
        byte_msg := make([]byte, 2048)
        len, err := current_connect.Read(byte_msg)
        if err != nil { current_connect.Close() }
        fmt.Println(string(byte_msg[:len]))
        msg_broadcast(byte_msg[:len], current_connect.RemoteAddr().String())
    }
}

// 廣播給全部 client
func msg_broadcast(byte_msg []byte, key string) {
    for k, con := range client_map {
        if k != key { con.Write(byte_msg) }
    }
}

// 主函數
func main() {
    fmt.Println("服務已啓動...")
    time.Sleep(1 * time.Second)
    fmt.Println("等待客戶端請求鏈接...")
    go listen_client("127.0.0.1:1801")
    select{}
}

b) 描述

能夠看到,撇開 main 函數,一共有 2 個 routine,分別是:

  1. 監聽鏈接
  2. 接收消息(廣播消息也在這裏)

2.客戶端

a) 代碼

package main

import (
    "fmt"
    "net"
    "os"
    "bufio"
)

// 用戶名
var login_name string

// 本機鏈接
var self_connect *net.TCPConn

// 讀取行文本
var reader = bufio.NewReader(os.Stdin)

// 創建鏈接
func connect(addr string) {
    tcp_addr, _ := net.ResolveTCPAddr("tcp", addr) // 使用tcp
    con, err := net.DialTCP("tcp", nil, tcp_addr) // 撥號
    self_connect = con
    if err != nil {
        fmt.Println("服務器鏈接失敗")
        os.Exit(1)
    }
    go msg_sender()
    go msg_receiver()
}

// 消息接收器
func msg_receiver() {
    buff := make([]byte, 2048)
    for {
        len, _ := self_connect.Read(buff) // 讀取消息
        fmt.Println(string(buff[:len]))
    }
}

// 消息發送器
func msg_sender() {
    for {
        read_line_msg, _, _ := reader.ReadLine()
        read_line_msg = []byte(login_name + " : " + string(read_line_msg))
        self_connect.Write(read_line_msg)
    }
}

// 主函數
func main() {
    fmt.Println("請問您怎麼稱呼?")
    name, _, _ := reader.ReadLine()
    login_name = string(name)
    connect("127.0.0.1:1801")
    select{}
}

b) 描述

一樣,客戶端也是有兩個 routine 組成:

  1. 消息接收器
  2. 消息發送器

創建鏈接在主線程完成

3.效果圖

a) 服務端

圖片描述

b) 客戶端_1

圖片描述

c) 客戶端_2

圖片描述

結尾

這裏並無用到 channel

小栗子僅爲經驗總結,學習交流而記

相關文章
相關標籤/搜索