golang 简单的golang聊天服务器

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang 简单的golang聊天服务器相关的知识,希望对你有一定的参考价值。

package main

import (
	"bufio"
	"net"
)

type Client struct {
	incoming chan string
	outgoing chan string
	reader   *bufio.Reader
	writer   *bufio.Writer
}

func (client *Client) Read() {
	for {
		line, _ := client.reader.ReadString('\n')
		client.incoming <- line
	}
}

func (client *Client) Write() {
	for data := range client.outgoing {
		client.writer.WriteString(data)
		client.writer.Flush()
	}
}

func (client *Client) Listen() {
	go client.Read()
	go client.Write()
}

func NewClient(connection net.Conn) *Client {
	writer := bufio.NewWriter(connection)
	reader := bufio.NewReader(connection)

	client := &Client{
		incoming: make(chan string),
		outgoing: make(chan string),
		reader: reader,
		writer: writer,
	}

	client.Listen()

	return client
}

type ChatRoom struct {
	clients []*Client
	joins chan net.Conn
	incoming chan string
	outgoing chan string
}

func (chatRoom *ChatRoom) Broadcast(data string) {
	for _, client := range chatRoom.clients {
		client.outgoing <- data
	}
}

func (chatRoom *ChatRoom) Join(connection net.Conn) {
	client := NewClient(connection)
	chatRoom.clients = append(chatRoom.clients, client)
	go func() { for { chatRoom.incoming <- <-client.incoming } }()
}

func (chatRoom *ChatRoom) Listen() {
	go func() {
		for {
			select {
			case data := <-chatRoom.incoming:
				chatRoom.Broadcast(data)
			case conn := <-chatRoom.joins:
				chatRoom.Join(conn)
			}
		}
	}()
}

func NewChatRoom() *ChatRoom {
	chatRoom := &ChatRoom{
		clients: make([]*Client, 0),
		joins: make(chan net.Conn),
		incoming: make(chan string),
		outgoing: make(chan string),
	}

	chatRoom.Listen()

	return chatRoom
}

func main() {
	chatRoom := NewChatRoom()

	listener, _ := net.Listen("tcp", ":6666")

	for {
		conn, _ := listener.Accept()
		chatRoom.joins <- conn
	}
}

Golang✔️实战✔️ 聊天室 ☢️建议手收藏☢️

【Golang】✔️实战✔️ 聊天室 ☢️建议手收藏☢️

概述

今天我们会结合之前几节课的知识来综合实战一下, 实现一个聊天室.

服务端实现

运行的时候我们可以开启一个服务端和 N 个客户端, 来实现聊天室.


代码:

package main

import (
	"fmt"
	"log"
	"net"
	"os"
	"strings"
	"time"
)

// ================常量================
const (

	// 日志路径
	log_path = "chat_room/"
)


// ===============全局变量==============

// 文件日志
var logFile *os.File

// 日志类
var logger *log.Logger

// 客户端连接, key: ip端口, value: 连接对象
var onlineConns = make(map[string]net.Conn)

// 消息队列, 缓冲区
var message_quene = make(chan string, 1024)

// 消息, 处理程序退出
var quitchan = make(chan bool)

// 消息协程
func comsume_msg() {
	for {
		select {
		// 取出消息
		case msg := <- message_quene:
			process_msg(msg)
		// 处理退出
		case <- quitchan:
			break
		}
	}
}

// 消息解析协程
func process_msg(message string) {

	// 字符串切割
	contents := strings.Split(message, "#")
	if len(contents) > 1 {

		// 取出地址
		address := contents[0]

		// 取出消息
		message := contents[1]

		// 删除首尾
		address = strings.Trim(address, " ")

		// 在线连接
		conn, ok := onlineConns[address]

		if ok {
			_, err := conn.Write([]byte(message))
			fmt.Println("发送消息:", message, "目的地:", address)
			if err != nil {
				fmt.Println("在线连接发送失败")
			}
		}
	} else {


		// 查看list
		contents = strings.Split(message, "&")
		if contents[1] == "list" {

			var str = ""

			// 向每个客户端发送信息
			for i := range onlineConns {

				//
				str += "||||" + i
			}

			// 在线连接
			conn, ok := onlineConns[contents[0]]

			if ok {
				_, err := conn.Write([]byte(str))
				if err != nil {
					fmt.Println("在线发送失败:", err)
				}
			}
			fmt.Println("发送消息:", str, "目的地:", conn.RemoteAddr())
			logger.Println("发送消息:", str, "目的地:", conn.RemoteAddr())
		}
	}
}


// 接收消息
func receive_info(conn net.Conn) {

	// 缓冲
	buffer := make([]byte, 1024)

	// 循环读取
	for {

		// 读取数据
		nums, err := conn.Read(buffer)
		if err != nil {
			break
		}

		if nums != 0 {

			// 获取地址
			address := conn.RemoteAddr()

			// 获取消息
			message := string(buffer[:nums])

			// 调试输出
			fmt.Println("收到消息:", message, "来自:", address)
			logger.Println("收到消息:", message, "来自:", address)

			// 处理客户端退出
			if message == "exit" {

				// 调试输出
				fmt.Println("客户端:", conn.RemoteAddr(), "正在退出...")
				logger.Println("客户端:", conn.RemoteAddr(), "正在退出...")

				// 退出
				client_exit(conn)
			} else{
				// 消息队列存储消息
				message_quene <- message
			}
		}
	}
}

// 处理退出
func client_exit(conn net.Conn) {

	// 获取地址
	address := fmt.Sprint(conn.RemoteAddr())

	// 客户端退出时, 从map中移除地址
	delete(onlineConns, address)

	// 关闭连接
	conn.Close()

	// 输出当前列表
	fmt.Println("客户端列表:\\n--------------------")
	logger.Println("客户端列表:\\n--------------------")


	for i := range onlineConns {
		fmt.Println(i)
	}
}

// 错误处理
func error_check(err error) {
	if err != nil {
		fmt.Println("Error:", err)
		os.Exit(1)
	}
}

func main() {

	fmt.Println("服务端正在启动...")

	// 打开日志文件
	name := fmt.Sprintf("%d_%02d_%02d_%02d_%02d_%02d_server.log",
		time.Now().Year(),
		time.Now().Month(),
		time.Now().Day(),
		time.Now().Hour(),
	    time.Now().Minute(),
		time.Now().Second(),
	)

	log_file, err := os.OpenFile((log_path + name), os.O_RDWR | os.O_CREATE, 0)
	if err != nil {
		fmt.Println("日志文件打开失败:", err.Error())
		os.Exit(-1)
	}

	// 关闭
	defer log_file.Close()

	// 创建一个日志对象
	logger = log.New(log_file, "\\r\\n", log.Ldate | log.Ltime | log.Llongfile)
	logger.Println("写入日志, 服务器正在启动...")

	// 创建TCP服务端
	listen_socket, err := net.Listen("tcp", "127.0.0.1:8888")
	error_check(err)

	// 关闭
	defer listen_socket.Close()

	fmt.Println("服务端启动完毕, 等待连接...")

	// 协程
	go comsume_msg()

	// 启动连接
	for {

		// 连接新客户端
		conn, err := listen_socket.Accept()
		error_check(err)

		fmt.Println("服务端连接到客服端, 客户端地址:", conn.RemoteAddr())
		logger.Println("服务端连接到客服端, 客户端地址:", conn.RemoteAddr())


		// 获取地址
		address := fmt.Sprint(conn.RemoteAddr())

		// 添加到全局变量
		onlineConns[address] = conn

		// 遍历每一个连接
		fmt.Println("客户端列表:\\n--------------------")
		logger.Println("客户端列表:\\n---------------------------------------------------")
		for i := range onlineConns {
			fmt.Println(i)
			logger.Println(i)
		}

		// 发送消息
		go receive_info(conn)
	}

}

客户端实现

package main

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

// 发送消息
func send_msg(conn net.Conn) {

	// 循环发送
	for {

		// 读取键盘输入
		reader := bufio.NewReader(os.Stdin)

		// 读取一行
		data, _, _ := reader.ReadLine()

		// 发送输入的字符串
		_, err := conn.Write(data)
		fmt.Println("发送消息:", string(data))
		if err != nil {
			conn.Close()
			fmt.Println("Error:", err, "客户端关闭")
			os.Exit(0)
		}

		// 收到exit, 关闭客户端
		if string(data) == "exit" {
			fmt.Println("客户端关闭")
			os.Exit(0)
		}
	}
}

func main() {

	fmt.Println("客服端正在建立连接...")

	// 建立网络连接
	conn, err := net.Dial("tcp", "127.0.0.1:8888")
	if err != nil {
		fmt.Println("网络连接错误")
		os.Exit(1)
	}
	fmt.Println("客户端成功连接到服务端, 服务端地址:", conn.RemoteAddr())

	// 发送消息
	go send_msg(conn)

	// 接收消息
	buffer := make([]byte, 1024)

	// 循环接收
	for {
		// 读取消息
		nums, err := conn.Read(buffer)
		if err != nil {
			fmt.Println("读取消息出错, 退出客户端")
			os.Exit(0)
		}
		fmt.Println("收到消息:", string(buffer[:nums]))
	}

}

输出结果:
server:

服务端正在启动...
服务端启动完毕, 等待连接...
服务端连接到客服端, 客户端地址: 127.0.0.1:63776
客户端列表:
--------------------
127.0.0.1:63776
服务端连接到客服端, 客户端地址: 127.0.0.1:63777
客户端列表:
--------------------
127.0.0.1:63776
127.0.0.1:63777
收到消息: 127.0.0.1:63776&list 来自: 127.0.0.1:63776
发送消息: ||||127.0.0.1:63776||||127.0.0.1:63777 目的地: 127.0.0.1:63776
收到消息: 127.0.0.1:63777#hi, client2 来自: 127.0.0.1:63776
发送消息: hi, client2 目的地: 127.0.0.1:63777
收到消息: 127.0.0.1:63777&list 来自: 127.0.0.1:63777
发送消息: ||||127.0.0.1:63776||||127.0.0.1:63777 目的地: 127.0.0.1:63777
收到消息: 127.0.0.1:63776#hi, client1 来自: 127.0.0.1:63777
发送消息: hi, client1 目的地: 127.0.0.1:63776
收到消息: exit 来自: 127.0.0.1:63776
客户端: 127.0.0.1:63776 正在退出...
客户端列表:
--------------------
127.0.0.1:63777
收到消息: exit 来自: 127.0.0.1:63777
客户端: 127.0.0.1:63777 正在退出...
客户端列表:
--------------------

client1:

客服端正在建立连接...
客户端成功连接到服务端, 服务端地址: 127.0.0.1:8888
127.0.0.1:63776&list
发送消息: 127.0.0.1:63776&list
收到消息: ||||127.0.0.1:63776||||127.0.0.1:63777
127.0.0.1:63777#hi, client2
发送消息: 127.0.0.1:63777#hi, client2
收到消息: hi, client1
exit
发送消息: exit
客户端关闭

client2:

客服端正在建立连接...
客户端成功连接到服务端, 服务端地址: 127.0.0.1:8888
收到消息: hi, client2
127.0.0.1:63777&list
发送消息: 127.0.0.1:63777&list
收到消息: ||||127.0.0.1:63776||||127.0.0.1:63777
127.0.0.1:63776#hi, client1
发送消息: 127.0.0.1:63776#hi, client1
exit
发送消息: exit
客户端关闭

日志

2021/08/27 01:08:04 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:209: 写入日志, 服务器正在启动...

2021/08/27 01:08:07 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:231: 服务端连接到客服端, 客户端地址: 127.0.0.1:63776

2021/08/27 01:08:07 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:242: 客户端列表:
---------------------------------------------------

2021/08/27 01:08:07 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:245: 127.0.0.1:63776

2021/08/27 01:08:09 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:231: 服务端连接到客服端, 客户端地址: 127.0.0.1:63777

2021/08/27 01:08:09 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:242: 客户端列表:
---------------------------------------------------

2021/08/27 01:08:09 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:245: 127.0.0.1:63776

2021/08/27 01:08:09 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:245: 127.0.0.1:63777

2021/08/27 01:08:20 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:135: 收到消息: 127.0.0.1:63776&list 来自: 127.0.0.1:63776

2021/08/27 01:08:20 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:104: 发送消息: ||||127.0.0.1:63776||||127.0.0.1:63777 目的地: 127.0.0.1:63776

2021/08/27 01:08:31 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:135: 收到消息: 127.0.0.1:63777#hi, client2 来自: 127.0.0.1:63776

2021/08/27 01:08:42 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:135: 收到消息: 127.0.0.1:63777&list 来自: 127.0.0.1:63777

2021/08/27 01:08:42 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:104: 发送消息: ||||127.0.0.1:63776||||127.0.0.1:63777 目的地: 127.0.0.1:63777

2021/08/27 01:08:53 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:135: 收到消息: 127.0.0.1:63776#hi, client1 来自: 127.0.0.1:63777

2021/08/27 01:09:04 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:135: 收到消息: exit 来自: 127.0.0.1:63776

2021/08/27 01:09:04 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:142: 客户端: 127.0.0.1:63776 正在退出...

2021/08/27 01:09:04 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:168: 客户端列表:
--------------------

2021/08/27 01:09:11 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:135: 收到消息: exit 来自: 127.0.0.1:63777

2021/08/27 01:09:11 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:142: 客户端: 127.0.0.1:63777 正在退出...

2021/08/27 01:09:11 C:/Users/Windows/Desktop/project2/chat_room/server2/server.go:168: 客户端列表:
--------------------

以上是关于golang 简单的golang聊天服务器的主要内容,如果未能解决你的问题,请参考以下文章

Golang之一个简单的聊天机器人

Golang的聊天服务器实践(群聊,广播)

Golang✔️实战✔️ 聊天室 ☢️建议手收藏☢️

Golang✔️实战✔️ 聊天室 ☢️建议手收藏☢️

手撸golang GO与微服务 ChatServer之1

手撸golang GO与微服务 ChatServer之2