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

author author     2023-02-01     412

关键词:

package tools

import (
	"bufio"
	"os"
	"strings"
)

func SanLine() string 
	inputReader := bufio.NewReader(os.Stdin)
	input,_ := inputReader.ReadString('\n')
	return strings.Replace(input, "\n", "", -1)

package main

import (
	"os"
	"fmt"
	"net"
	"log"
	"strings"
	"goLearn/chatroom/ircTest/tools"
)

func main() 
	// conns[string]net.Conn用于保存昵称和conn对应关系的map
	conns := make(map[string]net.Conn)
	//传递消息的channel
	msgCh := make(chan string, 10)

	if len(os.Args) != 2 
		fmt.Println("PLease input: \" cmd [:port] \"")
		os.Exit(0)
	
	port := os.Args[1]
	addr, err := net.ResolveTCPAddr("tcp", port)
	if err != nil 
		log.Fatal(err)
	
	// 开始监听
	listener, err := net.ListenTCP("tcp", addr)
	log.Println("服务器启动")

	// 启动广播协程
	go broadcast(msgCh, &conns)
	// 启动serverTools()协程
	go serverTools(msgCh, &conns)
	for 
		conn, err := listener.Accept()
		if err != nil 
			log.Fatal(err) //todo:这里可能有问题
		
		go ServerHandle(conn, msgCh, &conns)
	


func ServerHandle(conn net.Conn, msgCh chan string, conns *map[string]net.Conn) 
	//发送欢迎信息
	conn.Write([]byte("您已连接"))
	var msg string
	data := make([]byte, 1024)

	for 
		length, err := conn.Read(data)
		// 如果读取错误,可以认为是客户端退出或者关闭了连接,
		// 那么就关闭与客户端的连接,break出for-loop,相当于退出了这个conn的handle()函数
		if err != nil 
			// 这里有问题,如果客户端没有quit,而是直接退出,那么这个昵称及conn的键值对还在conns里,
			// 下次连接时,服务器查询conns,会认为这个昵称已存在
			conn.Close()
			break
		
		// 如果读到消息,那么将消息有效部分之后的一位置为0
		// todo: 暂时不知道为什么这么做
		if length > 0 
			data[length] = 0
		

		// 客户端发来的消息是带"前缀"的,解析消息,将"前缀"和消息体分开
		cmd := strings.Split(string(data[:length]), "|")
		// 打印带有前缀的消息有效部分
		fmt.Println(string(data[:length]))

		// 开始判断消息前缀,做相应的处理
		switch cmd[0] 
		// 前缀"hello"代表客户端请求一个昵称
		case "hello":
			// 昵称在conns中已经存在,代表重名,退出客户端连接
			// todo:这里怎么样可以改成不断开,而是让客户端重新尝试起名字
			if _, ok := (*conns)[cmd[1]]; ok 
				conn.Close()
			 else 
				// 将昵称和conn的键值对存入map,昵称作为key
				(*conns)[cmd[1]] = conn
				msg = fmt.Sprintf("[%s] join...", cmd[1])
			
		case "say":
			msg = fmt.Sprintf("[%s] : %s", cmd[1], cmd[2])
		case "quit":
			msg = fmt.Sprintf("quit|[%s]", cmd[1])
		

		// 将匹配模式生成的消息传给channel
		msgCh <- msg
	


func broadcast(msgCh chan string, conns *map[string]net.Conn) 
	for 
		msg := <-msgCh
		// 遍历所有客户端,向客户端发送消息
		for name, conn := range *conns 
			_, err := conn.Write([]byte(msg))
			// 如果向某一个客户端发送消息失败,那么我们认为该客户端已经退出或者关闭了连接,
			// 那么就需要将该客户端在conns中保存的键值对删掉
			if err != nil 
				delete(*conns, name)
			
		
	


// serverTools() 用来定义server端的一些行为和工具,例如server的发言,server踢人等。
func serverTools(msgCh chan string, conns *map[string]net.Conn)  
	msg := tools.SanLine()
	// cmd是一个[]string
	cmd := strings.Split(string(msg), "|")

	log.Println(cmd)

	if len(cmd) > 1
		switch cmd[0] 
		case "kick":
			if _, ok := (*conns)[cmd[1]]; ok
				(*conns)[cmd[1]].Close()
				msgCh <- "[Server]: Kick out [" + cmd[1] + "]"
			
		default:
			msgCh <- "[Server]" + string(msg)
		
	else
		msgCh <- "[Server]:" + string(msg)
	




package main

import (
	"os"
	"fmt"
	"net"
	"log"
	"strings"
	"goLearn/chatroom/ircTest/tools"
)

func main() 
	// conns[string]net.Conn用于保存昵称和conn对应关系的map
	conns := make(map[string]*net.Conn)
	//传递消息的channel
	msgCh := make(chan string, 10)

	if len(os.Args) != 2 
		fmt.Println("PLease input: \" cmd [:port] \"")
		os.Exit(0)
	
	port := os.Args[1]
	addr, err := net.ResolveTCPAddr("tcp", port)
	if err != nil 
		log.Fatal(err)
	
	// 开始监听
	listener, err := net.ListenTCP("tcp", addr)
	log.Println("服务器启动")

	// 启动广播协程
	go broadcast(msgCh, conns)
	// 启动serverTools()协程
	go serverTools(msgCh, conns)
	for 
		conn, err := listener.Accept()
		if err != nil 
			log.Fatal(err)
		
		go ServerHandle(&conn, msgCh, conns)
	


func ServerHandle(conn *net.Conn, msgCh chan string, conns map[string]*net.Conn) 

	//发送欢迎信息
	(*conn).Write([]byte("您已连接"))
	var msg string
	data := make([]byte, 1024)

	for 
		length, err := (*conn).Read(data)
		// 如果读取错误,可以认为是客户端退出或者关闭了连接,
		// 那么就关闭与客户端的连接,break出for-loop,相当于退出了这个conn的handle()函数
		if err != nil 
			(*conn).Close()
			// 如果客户端没有通过conn.Close()来断开连接,而是直接关闭程序或者网络中断,那么服务器端主动断开链接,
			// 并且删除保存在conns中的项目,以避免下次客户端登陆时,出现因为同名而断开的情况。
			for name, connItem := range conns 
				if conn == connItem 
					delete(conns, name)
				
			
			break
		

		// 客户端发来的消息是带"前缀"的,解析消息,将"前缀"和消息体分开
		cmd := strings.Split(string(data[:length]), "|")
		// 打印带有前缀的消息有效部分
		fmt.Println(string(data[:length]))

		// 开始判断消息前缀,做相应的处理
		switch cmd[0] 
		// 前缀"hello"代表客户端请求一个昵称
		case "hello":
			// 昵称在conns中已经存在,代表重名,退出客户端连接
			// todo:这里怎么样可以改成不断开,而是让客户端重新尝试起名字
			if _, ok := conns[cmd[1]]; ok 
				(*conn).Write([]byte("重名啦!!!!!"))
				(*conn).Close()
			 else 

				// 将昵称和conn的键值对存入map,昵称作为key
				/*
				指针的定以后如果没有指向任何变量,那么为nil(空指针)。*p = *q这种表达式,是把*p指向的内存地址报错
				的数据更改为*q指向内存地址保存的数据,相当于复制了一份,内存地址还是不一样。操作的是数据。所以下面这
				里表达式两遍的指针不能加*,下面代码的目的是为了把conn的内存地址保存在conns中,以便于后续代码操作的
				是同一个conn。如果加了*(前提是conns[cmd[1]]已经被初始化过),那么相当于是把conn指向的值赋值给了
				conns[1]指针指向的内存,两个指针指向的内存还是不一样。
				*/
				conns[cmd[1]] = conn
				msg = fmt.Sprintf("[%s] join...", cmd[1])
			
		case "say":
			msg = fmt.Sprintf("[%s] : %s", cmd[1], cmd[2])
		case "quit":
			msg = fmt.Sprintf("quit|[%s]", cmd[1])
		

		// 将匹配模式生成的消息传给channel
		msgCh <- msg
	


func broadcast(msgCh chan string, conns map[string]*net.Conn) 
	for 
		msg := <-msgCh
		// 遍历所有客户端,向客户端发送消息
		for name, conn := range conns 
			_, err := (*conn).Write([]byte(msg))
			// 如果向某一个客户端发送消息失败,那么我们认为该客户端已经退出或者关闭了连接,
			// 那么就需要将该客户端在conns中保存的键值对删掉
			if err != nil 
				delete(conns, name)
			
		
	


// serverTools() 用来定义server端的一些行为和工具,例如server的发言,server踢人等。
func serverTools(msgCh chan string, conns map[string]*net.Conn) 
	for 
		msg := tools.SanLine()
		// cmd是一个[]string
		cmd := strings.Split(string(msg), " ")
		var kickOutInfo string
		if len(cmd) > 1 
			switch cmd[0] 
			case "kick":
				log.Println("check kick")
				var conn *net.Conn
				conn = conns[cmd[1]]
				if _, ok := conns[cmd[1]]; ok 
					(*conn).Close()
					delete(conns, cmd[1])
					log.Println(conns)
					//msgCh <- "[Server]: Kick out [" + cmd[1] + "]"
					kickOutInfo = "[Server]: Kick out [" + cmd[1] + "]"
				
			default:
				if len(kickOutInfo) > 0 
					msgCh <- kickOutInfo
				 else 
					msgCh <- "[Server]" + string(msg)
				
			
		 else 
			msgCh <- "[Server_say]:" + string(msg)
		
	

package main

import (
	"os"
	"fmt"
	"net"
	"log"
	"strings"
	"goLearn/chatroom/ircTest/tools"
)

func main() 
	if len(os.Args) != 2 
		fmt.Println("PLease input: \" cmd [:port | IPaddress:port] \"")
		os.Exit(0)
	
	port := os.Args[1]
	addr, err := net.ResolveTCPAddr("tcp", port)
	if err != nil 
		log.Fatal(err)
	
	conn, err := net.DialTCP("tcp", nil, addr)
	if err != nil 
		log.Fatal(err)
	

	// 用于保存消息
	msg := make([]byte, 1024)
	// 读取欢迎消息
	conn.Read(msg)
	fmt.Println(string(msg))

	// 输入昵称
	fmt.Print("请输入昵称:")
	nickname := tools.SanLine()
	fmt.Println("Hello", nickname)

	// 向服务器发送"hello|nickname",尝试定义昵称,如果昵称重名,这里会直接退出,尝试修改为可以不断尝试
	conn.Write([]byte("hello|" + nickname))

	// 开启接收数据的协程
	go ClientHandle(conn, nickname)

	// 发送消息
	// 考虑一下放在一个单独的函数中怎么实现
	for
		msg := tools.SanLine()
		if msg == "quit"
			conn.Write([]byte("quit|" + nickname))
			// 如果发送quit,客户端主动关闭conn
			conn.Close()
		
		conn.Write([]byte("say|" + nickname + "|" + msg))
	



func ClientHandle(conn *net.TCPConn, nickname string) 
	for
		msg := make([]byte, 1024)
		_, err := (*conn).Read(msg)
		if err != nil 
			// 如果这里从服务器端conn读取失败,可以认为是服务器关闭连接或者退出了
			log.Fatal(err)
		
		// 将自身的发言屏蔽
		if strings.Contains(string(msg), "["+nickname+"] : ") == false
			fmt.Println(string(msg))
		
	


package main

import (
	"os"
	"fmt"
	"net"
	"log"
	"strings"
	"goLearn/chatroom/ircTest/tools"
)

func main() 
	if len(os.Args) != 2 
		fmt.Println("PLease input: \" cmd [:port | IPaddress:port] \"")
		os.Exit(0)
	
	port := os.Args[1]
	addr, err := net.ResolveTCPAddr("tcp", port)
	if err != nil 
		log.Fatal(err)
	
	conn, err := net.DialTCP("tcp", nil, addr)
	if err != nil 
		log.Fatal(err)
	

	// 用于保存消息
	msg := make([]byte, 1024)
	// 读取欢迎消息
	conn.Read(msg)
	fmt.Println(string(msg))

	// 输入昵称
	fmt.Print("请输入昵称:")
	nickname := tools.SanLine()
	fmt.Println("Hello", nickname)

	// 向服务器发送"hello|nickname",尝试定义昵称,如果昵称重名,这里会直接退出,尝试修改为可以不断尝试
	conn.Write([]byte("hello|" + nickname))

	// 开启接收数据的协程
	go ClientHandle(conn, nickname)

	// 发送消息
	for 
		msg := tools.SanLine()
		if msg == "quit" 
			conn.Write([]byte("quit|" + nickname))
			// 如果发送quit,客户端主动关闭conn
			conn.Close()
		
		conn.Write([]byte("say|" + nickname + "|" + msg))
	


func ClientHandle(conn *net.TCPConn, nickname string) 
	for 
		msg := make([]byte, 1024)
		_, err := (*conn).Read(msg)
		if err != nil 
			// 如果这里从服务器端conn读取失败,可以认为是服务器关闭连接或者退出了
			log.Fatal(err)
		
		// 将自身的发言屏蔽
		if strings.Contains(string(msg), "["+nickname+"] : ") == false 
			fmt.Println(string(msg))
		
	

golang:cobra包简介(代码片段)

Cobra是一个Golang包,它提供了简单的接口来创建命令行程序。同时,Cobra也是一个应用程序,用来生成应用框架,从而开发以Cobra为基础的应用。本文的演示环境为ubuntu18.04(下图来自互联网)。主要功能cobra的主要功能如下,可以说... 查看详情

irc(代码片段)

什么是IRCIRC(Internet Relay Chat的缩写,“因特网中继聊天”)是一种透过网络的即时聊天方式。其主要用于群体聊天,但同样也可以用于个人对个人的聊天。IRC使用的服务器端口有6667(明文传输,如irc://irc.freenode.net)、66... 查看详情

golang命令行库cobra的使用(代码片段)

简介Cobra既是一个用来创建强大的现代CLI命令行的golang库,也是一个生成程序应用和命令行文件的程序。下面是Cobra使用的一个演示:Cobra提供的功能简易的子命令行模式,如appserver,appfetch等等完全兼容posix命令行模式嵌套子命... 查看详情

Twitch IRC 聊天机器人成功连接但未检测到命令

...间】:2015-10-1001:41:17【问题描述】:我开始使用Python制作一个简单的Twitch聊天机器人。它连接良好,并且还可以看到其他人在聊天中发送的消息。然而,我的问题是我似乎无法在使用命令时检测到它们。我可以获取聊天条目的用... 查看详情

golang系列文章:打印命令行参数(代码片段)

记得最早在学校机房学习Java时,写一个最简单main方法,当程序运行并在屏幕上打印出helloworld时,内心还有些小激动呢,相信很多人都有这种经历吧。今天想借助命令行程序,总结一下Go语言的基础知识点。首先,来一个Go语言... 查看详情

golang简单的golang聊天服务器(代码片段)

查看详情

什么是后门病毒?

...,IRC全名为InternetRelayChat,是一款即时聊天工具,类似网络聊天室,但功能更强大。IRC客户端与服务端通信采用的端口和相应协议是公开的,现在网上有很多IRC客户端软件,各有特色。同时,也正是由于协议的公开,给了病毒可乘... 查看详情

连接到 Twitch IRC 聊天

...题描述】:您好,我已尝试,因此我可以尝试为twitch制作一个简单的聊天机器人,但我正在努力使其工作。我得到错误:http://puu.sh/j3HwK/173a0388fb.png这里是代码:<?phpset_time_limit(0);ini_set(\'display_errors\' 查看详情

irc关键操作(代码片段)

...)。此功能是服务器通过提供NickServ服务(其实语法上就是一个用户,类似的服务还有ChanServMemoServ)实现的。假定需要重置密码的用户名为foo,那首先可以查看 查看详情

简易命令行聊天室程序

...到的问题。0、该程序实现的基本功能编写了一个简单的聊天室程序,该聊天室程序能够让所有的用户同时在线群聊,它分为服务器和客户端两个部分。服务器:接收客户端数据,并将该客户端数据发送给其他登录到该服务器上... 查看详情

尝试irc&freenode

...下客户端,命令行版本的我试了一下weechat(和微信就差一个字母),图形的用了TimeChat。 并没有推荐使用的意思,没有用更多的客户端做对比。下面以weechat为例,来写一下基本的使用命令。 安装weechat:Mac里面用 brewin... 查看详情

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

翠花,上代码packagemainimport("bufio""fmt""os""strings")funcmain(){//从标准输入读取数据inputReader:=bufio.NewReader(os.Stdin)fmt.Println("Pleaseinputyourname:")//读取数据直到遇见 位置input,err:=inputReader.ReadString(‘ ‘)if 查看详情

如何使用 Python Twitch IRC Bot 获取聊天消息参数?

...我的PythonTwitchIRCBot使用套接字接收和处理消息命令参数的简单方法。例如,如果我要设置歌曲请求,并且聊天中的用户输入“ 查看详情

一个基于vb的简单irc机器人服务器-genebot&evilbot

...时候的设想。现在实现了。之前用python的irc框架sopel写过一个机器人,但在windows上运行诸多不便。VB看似被众多“大佬”嘲笑,但是简单的windows图形界面快速编程远胜过qt。下面贴几张图:效果图(还 查看详情

golang获取命令行参数(代码片段)

部署golang项目时难免要通过命令行来设置一些参数,那么在golang中如何操作命令行参数呢?可以使用os库和flag库。 1、golangos库获取命令行参数os可以通过变量Args来获取命令参数,os.Args返回一个字符串数组,其中第一个参数... 查看详情

设计模式这样玩泰简单(golang版)-命令模式(代码片段)

场景老板:我们有一个业务的组件实现是第三方,他们暴露的接口非常细粒度,我们要根据不同的接口组成一个指令,并且指令能复用,你有什么好的方案你:好的,老板,那就使用命令模式方案命令模式,就是利用一些实现同一接口的对象... 查看详情

在c++中,如何用不同长度的字符串替换一个命令行参数。(代码片段)

我有一个大型的C++程序,在这个程序中,可以使用以下方法提供几个命令行参数char*argv[].在程序的后面,第一个参数需要用一个新的任意长度的字符串代替。我首先尝试用简单的方法来实现。MyProg_1.cpp#include<cstring>#include<i... 查看详情

支持 IRC 后端的 Ajax 聊天前端

...络聊天解决方案。基本上我有自己的IRC服务器。我想配置一个ajax网络聊天解决方案来连接它。我在Google中发现了大量的ajax网络聊天解决方案。但是没有一个(除了不是开源的mibbit)符合我的标准。你知道吗?如果没 查看详情