golang网络聊天室案例(代码片段)

2019ab 2019ab     2023-02-02     157

关键词:

1.聊天室设计分析

一. 概览

实现 个网络聊天室(群)
功能分析:

  1. 上线下线
  2. 聊天,其他人,自己都可以看到聊天消息
  3. 查询当前聊天室用户名字 who
  4. 可以修改自己名字 rename | Duke
  5. 超时踢出

技术点分析:
1 . sock tcp 编程
2 . map结构 (存储当前用户,map遍历,map删除)
3 . go程,channel
4 . select(超时退出,主动退出)
5 . timer定时器

二、实现基础

第一阶段:
tcp socket,建立多个连接

package main

import (
	"fmt"
	"net"
)

func main()
	// 创建服务器
	listener,err := net.Listen("tcp",":8088")
	if err != nil
		fmt.Println("net.Listen err:",err)
		return
	
	fmt.Println("服务器启动成功,监听中...")

	for 
		fmt.Println("==>主go程监听中......")
		// 监听
		conn,err := listener.Accept()
		if err != nil
			fmt.Println("listener.Accept err:",err)
			return
		

		// 建立连接
		fmt.Println("建立连接成功!")
		// 启动处理业务的go程
		go handler(conn)

	


// 处理具体业务
func handler(conn net.Conn)
	for
		fmt.Println("启动业务...")
		// TODO // 代表这里以后再具体实现
		buf := make([]byte,1024)
		// 读取客户端发送来的数据
		cnt,err := conn.Read(buf)
		if err != nil
			fmt.Println("listener.Read err:",err)
			return
		
		fmt.Println("客户端接收客户端发来的数据为:",string(buf[:cnt-1]),",cnt:",cnt)
	
	

go run chatroom.go
启动nc

nc下载地址

2、定义User/map结构

type User struct 
	// 名字
	name string 
	// 唯一 的 id
	id string
	// 管道
	msg chan string



// 创建一个全局的map结构,用于保存所有的用户
var allUsers = make(map[string]User)

在Handler中调用

// 处理具体业务
func handler(conn net.Conn)
	for
		fmt.Println("启动业务...")
//
		// 客户端与服务器建立连接的时候,公有ip和port --> 当成user的id
		clientAddr := conn.RemoteAddr().String()
		fmt.Println("clientAddr:",clientAddr)
		// 创建user
		newUser := User
			id:clientAddr,// id 我们不会修改,这个作为map中的key
			name:clientAddr,// 可以修改,会提供rename命令修改,建立连接时,初始值与id相同
			msg:make(chan string), // 注意需要make空间,否则无法写入数据
		
		// 添加user到map结构
		allUsers[newUser.id] = newUser
/
		buf := make([]byte,1024)
		// 读取客户端发送来的数据
		cnt,err := conn.Read(buf)
		if err != nil
			fmt.Println("listener.Read err:",err)
			return
		
		fmt.Println("客户端接收客户端发来的数据为:",string(buf[:cnt-1]),",cnt:",cnt)
	
	

3.定义message管道


创建监听广播go程函数

// 向所有的用户广播消息,启动一个全局唯一的go程
func broadcast()
	fmt.Println("广播go程启动成功...")
	// 1. 从message中读取数据
	info := <-message 
	// 2. 将数据写入到每一个用户的msg管道中
	for _,user := range allUsers
		user.msg <- info 
	

启动,全局唯一

写入上线数据

当前整体源码

package main

import (
	"fmt"
	"net"
)

type User struct 
	// 名字
	name string 
	// 唯一 的 id
	id string
	// 管道
	msg chan string



// 创建一个全局的map结构,用于保存所有的用户
var allUsers = make(map[string]User)

// 定义一个message全局通道,用于接收任何人发送过来消息
var message = make(chan string,10)

func main()
	// 创建服务器
	listener,err := net.Listen("tcp",":8087")
	if err != nil
		fmt.Println("net.Listen err:",err)
		return
	
	// 启动全局唯一的go程,负责监听message通道,写给所有的用户
	go broadcast()

	fmt.Println("服务器启动成功,监听中...")

	for 
		fmt.Println("==>主go程监听中......")
		// 监听
		conn,err := listener.Accept()
		if err != nil
			fmt.Println("listener.Accept err:",err)
			return
		

		// 建立连接
		fmt.Println("建立连接成功!")
		// 启动处理业务的go程
		go handler(conn)

	


// 处理具体业务
func handler(conn net.Conn)
	for
		fmt.Println("启动业务...")
		// 客户端与服务器建立连接的时候,公有ip和port --> 当成user的id
		clientAddr := conn.RemoteAddr().String()
		fmt.Println("clientAddr:",clientAddr)
		// 创建user
		newUser := User
			id:clientAddr,// id 我们不会修改,这个作为map中的key
			name:clientAddr,// 可以修改,会提供rename命令修改,建立连接时,初始值与id相同
			msg:make(chan string,10), // 注意需要make空间,否则无法写入数据
		
		// 添加user到map结构
		allUsers[newUser.id] = newUser

		// 向message写入数据,当我用户上线的消息,用于通知所有人(广播)
		loginInfo := fmt.Sprintf("[%s][%s] ===> |上线了login!!",newUser.id,newUser.name)
		message <- loginInfo

		buf := make([]byte,1024)
		// 读取客户端发送来的数据
		cnt,err := conn.Read(buf)
		if err != nil
			fmt.Println("listener.Read err:",err)
			return
		
		fmt.Println("客户端接收客户端发来的数据为:",string(buf[:cnt-1]),",cnt:",cnt)
	
	


// 向所有的用户广播消息,启动一个全局唯一的go程
func broadcast()
	fmt.Println("广播go程启动成功...")
	defer fmt.Println("broadcast 程序退出!")

	for 
		// 1. 从message中读取数据
		fmt.Println("broadcast监听message中...")
		info := <-message 
		// 2. 将数据写入到每一个用户的msg管道中
		for _,user := range allUsers
			// 如果msg是非缓冲,那么会在这里阻塞
			user.msg <- info 
		
	

4.user监听通道go程

每个用户应该还有一个用来监听自己msg管道的go程,负责将数据返回给客户端

// 每个用户应该还有一个用来监听msg管道的go程,负责将数据返回给客户端
func writeBackToClient(user *User,conn net.Conn)
	fmt.Printf("user:%s 的go程正在监听自己的msg管道:\\n",user.name)
	for data := range user.msg
		fmt.Printf("user:%s 写回给客户端的数据为:%s\\n",user.name,data)

		// Write(b []byte)(n int,err error)
		_,_ = conn.Write([]byte(data))
	


当前代码整体为

package main

import (
	"fmt"
	"net"
)

type User struct 
	// 名字
	name string 
	// 唯一 的 id
	id string
	// 管道
	msg chan string



// 创建一个全局的map结构,用于保存所有的用户
var allUsers = make(map[string]User)

// 定义一个message全局通道,用于接收任何人发送过来消息
var message = make(chan string,10)

func main()
	// 创建服务器
	listener,err := net.Listen("tcp",":8087")
	if err != nil
		fmt.Println("net.Listen err:",err)
		return
	
	// 启动全局唯一的go程,负责监听message通道,写给所有的用户
	go broadcast()

	fmt.Println("服务器启动成功,监听中...")

	for 
		fmt.Println("==>主go程监听中......")
		// 监听
		conn,err := listener.Accept()
		if err != nil
			fmt.Println("listener.Accept err:",err)
			return
		

		// 建立连接
		fmt.Println("建立连接成功!")
		// 启动处理业务的go程
		go handler(conn)

	


// 处理具体业务
func handler(conn net.Conn)

	fmt.Println("启动业务...")
	// 客户端与服务器建立连接的时候,公有ip和port --> 当成user的id
	clientAddr := conn.RemoteAddr().String()
	fmt.Println("clientAddr:",clientAddr)
	// 创建user
	newUser := User
		id:clientAddr,// id 我们不会修改,这个作为map中的key
		name:clientAddr,// 可以修改,会提供rename命令修改,建立连接时,初始值与id相同
		msg:make(chan string,10), // 注意需要make空间,否则无法写入数据
	
	// 添加user到map结构
	allUsers[newUser.id] = newUser

	// 启动go程,负责将msg的信息返回给客户端
	go writeBackToClient(&newUser,conn)

	// 向message写入数据,当我用户上线的消息,用于通知所有人(广播)
	loginInfo := fmt.Sprintf("[%s][%s] ===> |上线了login!!",newUser.id,newUser.name)
	message <- loginInfo

	for
		// 具体业务逻辑

		buf := make([]byte,1024)
		// 读取客户端发送来的数据
		cnt,err := conn.Read(buf)
		if err != nil
			fmt.Println("listener.Read err:",err)
			return
		
		fmt.Println("客户端接收客户端发来的数据为:",string(buf[:cnt-1]),",cnt:",cnt)
	
	


// 向所有的用户广播消息,启动一个全局唯一的go程
func broadcast()
	fmt.Println("广播go程启动成功...")
	defer fmt.Println("broadcast 程序退出!")

	for 
		// 1. 从message中读取数据
		fmt.Println("broadcast监听message中...")
		info := <-message 
		// 2. 将数据写入到每一个用户的msg管道中
		for _,user := range allUsers
			// 如果msg是非缓冲,那么会在这里阻塞
			user.msg <- info 
		
	


// 每个用户应该还有一个用来监听msg管道的go程,负责将数据返回给客户端
func writeBackToClient(user *User,conn net.Conn)
	fmt.Printf("user:%s 的go程正在监听自己的msg管道:\\n",user.name)
	for data := range user.msg
		fmt.Printf("user:%s 写回给客户端的数据为:%s\\n",user.name,data)

		// Write(b []byte)(n int,err error)
		_,_ = conn.Write([]byte(data))
	

三、增加功能

  1. 查询用户
    查询命令:who==>将当前所有的登录的用户,展示出来,id,name,返回给当前用户
package main

import (
	"fmt"
	"net"
	"strings"
)

type User struct 
	// 名字
	name string 
	// 唯一 的 id
	id string
	// 管道
	msg chan string



// 创建一个全局的map结构,用于保存所有的用户
var allUsers = make(map[string]User)

// 定义一个message全局通道,用于接收任何人发送过来消息
var message = make(chan string,10)

func main()
	// 创建服务器
	listener,err := net.Listen("tcp",":8087")
	if err != nil
		fmt.Println("net.Listen err:",err)
		return
	
	// 启动全局唯一的go程,负责监听message通道,写给所有的用户
	go broadcast()

	fmt.Println("服务器启动成功,监听中...")

	for 
		fmt.Println("==>主go程监听中......")
		// 监听
		conn,err := listener.Accept()
		if err != nil
			fmt.Println("listener.Accept err:",err)
			return
		

		// 建立连接
		fmt.Println("建立连接成功!")
		// 启动处理业务的go程
		go handler(conn)

	


// 处理具体业务
func handler(conn net.Conn)

	fmt.Println("启动业务...")
	// 客户端与服务器建立连接的时候,公有ip和port --> 当成user的id
	clientAddr := conn.RemoteAddr().String()
	fmt.Println("clientAddr:",clientAddr)
	// 创建user
	newUser := User
		id:clientAddr,// id 我们不会修改,这个作为map中的key
		name:clientAddr,// 可以修改,会提供rename命令修改,建立连接时,初始值与id相同
		msg:make(chan string,10), // 注意需要make空间,否则无法写入数据
	
	// 添加user到map结构
	allUsers[newUser.id] = newUser

	// 启动go程,负责将msg的信息返回给客户端
	go writeBackToClient(&newUser,conn)

	// 向message写入数据,当我用户上线的消息,用于通知所有人(广播)
	loginInfo := fmt.Sprintf("[%s][%s] ===> |上线了login!!",newUser.id,newUser.name)
	message <- loginInfo

	for
		// 具体业务逻辑

		buf := make([]byte,1024)
		// 读取客户端发送来的数据
		cnt,err := conn.Read(buf)
		if err != nil
			fmt.Println("listener.Read err:",err)
			return
		
		fmt.Println("客户端接收客户端发来的数据为:",string(buf[:cnt-1]),",cnt:",cnt)
		// -------------业务逻辑处理  开始-------------	
		// 1.查询当前所有的用户 who
		// a. 先判断接收的数据是不是who  ==》 长度&&字符串
		userInput := string(buf[:cnt-1])  // 这是用户输入的数据,最后一个是回车,我们去掉他
		if len(userInput) == 3 && userInput == "who"
			// b.遍历allUser这个map(key:userid value:user 本身)。将id和name拼接成一个字符,返回给客户端
			fmt.Println("用户即将查询所有用户信息!")
			// 这个切片包含所有的用户信息
			var userInfos []string
			for _,user := range allUsers
				userInfo := fmt.Sprintf("userid:%s,username:%s",user.id,user.name)
				userInfos = append(userInfos,userInfo)
			

			// 最终写入到通道中,一定是一个字符串
			r := strings.Join(userInfos,"\\n")

			// 将数据返回到客户端
			newUser.msg <- r
		else
			// 如果不是用户输入的命令,只是聊天信息,那么只需要写到广播中即可,由其他的go程常规转发
			message <- userInput
		

		// -------------业务逻辑处理  结束-------------	

	
	


// 向所有的用户广播消息,启动一个全局唯一的go程
func broadcast()
	fmt.Println("广播go程启动成功...")
	defer fmt.Println("broadcast 程序退出!")

	for 
		// 1. 从message中读取数据
		fmt.Println("broadcast监听message中...")
		info := <-message 
		// 2. 将数据写入到每一个用户的msg管道中
		for _,user := range allUsers
			// 如果msg是非缓冲,那么会在这里阻塞
			user.msg <- info 
		
	


// 每个用户应该还有一个用来监听msg管道的go程,负责将数据返回给客户端
func writeBackToClient(user *User,conn net.Conn)
	fmt.Printf("user:%s 的go程正在监听自己的msg管道:\\n",user.name)
	for data := range user.msg
		fmt.Printf("user:%s 写回给客户端的数据为:%s\\n",user.name,data)

		// Write(b []byte)(n int,err error)
		_,_ = conn.golang简单的golang聊天服务器(代码片段)

查看详情

java基础——udp实现dos聊天室案例(代码片段)

利用UDP网络编程UDP协议不严格区分发送端和接收端发送端//udp打包 byte[]b="固定".getBytes(); //DatagramPacket(byte[]buf,intlength,InetAddressaddress,intport) //构造用于发送长度的分组的数据报包length指定主机上到指定的端口号。 Datagra... 查看详情

golang一个简单的命令行聊天室,如irc(代码片段)

查看详情

p2p网络编程-3-案例实践:pubsub(代码片段)

...的模式,官方给出了订阅发布模式的一个案例=>聊天室在此学习记录一下官方代码地址:https://github.com/libp2p/go-libp2p/tree/master/examples/pubsub一、效果演示二、代码理解2.1总体框架总的来说代码构成由这五个步骤&# 查看详情

java网络编程:案例四:多个客户端群聊(代码片段)

  需求:模拟聊天室群聊  客户端要先登录,登录成功之后才能发送和接收消息  分析:  服务器端,需要为每个客户端开启一个线程通信,这样才能实现多个客户端“同时”与服务器通信  客户端,需要把... 查看详情

golang✔️实战✔️聊天室☢️建议手收藏☢️(代码片段)

【Golang】✔️实战✔️聊天室☢️建议手收藏☢️概述服务端实现客户端实现日志概述今天我们会结合之前几节课的知识来综合实战一下,实现一个聊天室.服务端实现运行的时候我们可以开启一个服务端和N个客户端,来实现聊天... 查看详情

golang✔️实战✔️聊天室☢️建议手收藏☢️(代码片段)

【Golang】✔️实战✔️聊天室☢️建议手收藏☢️概述服务端实现客户端实现日志概述今天我们会结合之前几节课的知识来综合实战一下,实现一个聊天室.服务端实现运行的时候我们可以开启一个服务端和N个客户端,来实现聊天... 查看详情

golang学习十:网络编程(代码片段)

...2.获取文件属性:3.客户端实现4.服务端实现:四、示例-并发聊天室:1模块简述2.广播用户上线3.广播用户消息4.展示在线用户:5.修改用户名:5.用户退出与超时处理:一、网络协议:1.典型协议:传输层:常见协议有TCP/UDP协议;应用层:常见的... 查看详情

golang学习之路—json(代码片段)

JSONJSON基本介绍JSON数据格式说明JSON的序列化应用案例JSON的反序列化应用案例JSON基本介绍JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易与人阅读和编写。同时也易于机器解析和生成。JSON是在2001年开始推广使用的数据... 查看详情

window下golang使用grpc入门案例(代码片段)

一、检查golang的安装环境https://golang.org/dl/需要墙,或者在这里下载https://pan.baidu.com/s/12tTmrVIel6sfeBInpt9lQA最新版本1.10下载msi安装即可goversion验证安装 查看详情

三.netty入门到超神系列-聊天室案例(代码片段)

...NIO的三大核心做了学习,这章我们来基于NIO来做一个聊天室案例。聊天室案例先来看下我们要实现的效果对于服务端而言需要做如下事情selector监听客户端的链接如果有“读”事件,就从通道读取数据把数据转发给其他所... 查看详情

如何创建 golang 网络套接字聊天?

】如何创建golang网络套接字聊天?【英文标题】:Howtocreategolangwebsocketchat?【发布时间】:2015-03-2321:39:50【问题描述】:下午好。如何创建一个能够向选定用户发送消息的golangwebsocket聊天,而不是全部?【问题讨论】:在这里进... 查看详情

golang类型断言(代码片段)

Golang类型断言目录Golang类型断言案例定义例子案例funcmain() //定义一个空接口类型 varxinterface varyfloat32=1.1 //多态 x=y //y=x错误 y=x.(float32) fmt.Println(y)定义由于接口时一般类型,不知道具体类型,如果要转为具体类型,就需要类型断... 查看详情

golang-爬虫案例实践(代码片段)

目录Golang-爬虫案例实践1.爬虫步骤2.正则表达式3.并发爬取美图Golang-爬虫案例实践1.爬虫步骤明确目标(确定在哪个网址搜索)爬(爬下数据)取(去掉没用的数据)处理数据(按具体业务去使用数据)2.正则表达式文档:https://s... 查看详情

基于websocket实现一个简单的网站在线客服聊天室案例(代码片段)

...f0c;我们经常会有在线客服的功能,通过网站开启一个聊天室,进行客服解答功能,本节我们使用websocket实现一个简单的网站在线客服聊天功能,效果如下:正文后端引入websocket的pom依赖<dependency> <groupId&... 查看详情

golang一个使用go的网络模块的例子(代码片段)

查看详情

golang网络通信超时设置(代码片段)

...用到网络连接超时、读写超时的设置。本文结合例子简介golang的连接超时和读写超时设置。1.超时设置1.1连接超时funcDialTimeout(network,addressstring,timeouttime.Duration)(Conn,error)第三个参数timeout可以用来设置连接超时设置。如果超过timeout... 查看详情

java网络编程案例---聊天室

  网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。  java.net包中JavaSE的API包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注... 查看详情