首页 > 其他分享 >每日一抄 Go语言聊天服务器

每日一抄 Go语言聊天服务器

时间:2022-12-09 11:13:37浏览次数:40  
标签:一抄 err goroutine go Go 服务器 main conn log

server.go

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
)

/*
服务端程序中包含 4 个 goroutine,分别是一个主 goroutine 和广播(broadcaster)goroutine,每一个连接里面又包含一个连接处理(handleConn)goroutine 和一个客户写入(clientwriter)goroutine。

广播器(broadcaster)是用于如何使用 select 的一个规范说明,因为它需要对三种不同的消息进行响应。

主 goroutine 的工作是监听端口,接受连接客户端的网络连接,对每一个连接,它将创建一个新的 handleConn goroutine。

完整的示例代码如下所示:
*/
func main() {

	/*
		1.main函数就是获得listener对象,然后不停的获取连接上来的conn对象,最后把这些对象丢给处理连接函数进行处理
		2.在使用handleConn方法处理conn对象的时候,对不同的连接都启用一个goroutine去并发处理每个conn这样则无须等待。
		3.由于要给所有在线的用户发送消息,而不同的用户的conn对象都在不同的goroutine里面,但是Go语言中有channel来处理
		各个不同goroutine之间的消息传递,所以这里我们选择使用channel在各个不同的goroutine之间传递广播消息
	*/

	//开启一个监听端口
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	//开启广播协程
	go broadcaster()

	//不停的监听请求
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Fatal(err)
		}

		//开启处理请求的协程
		go handleConn(conn)
	}

}

/*
介绍broadcaster广播器,它使用局部变量clients来记录当前连接客户集合,每个客户唯一记录的信息是其对外发送消息通道的ID,下面是细节
*/
//对外发送消息的通道
type client chan<- string

var (
	entering = make(chan client)
	leaving  = make(chan client)
	messages = make(chan string) //所有连接的客户端
)

/*
在main函数中使用了一个goroutine开启了一个broadcaster函数来负责广播所有用户发送的信息
这里使用一个字典在保存clients,字典的key是各种连接申明的单向并发队列
*/
func broadcaster() {
	clients := make(map[client]bool)
	for {
		/*
			使用select开启一个多路复用:
			1.每当有广播消息从messages发送进来,都会循环clients对里面的每个channel发消息
			2.每当有消息从entering里面发送过来,就会生成一个新的key-value,相当于给clients里面增加一个新的client
			3.每当有消息从leaving里面发过来就删掉这个key-value对,并关闭对应的channel
		*/
		select {
		case msg := <-messages:
			//把所有接收到的消息广播给所有客户端
			//发送消息通道
			for cli := range clients {
				cli <- msg
			}
		//上线
		case cli := <-entering:
			clients[cli] = true

		//下线
		case cli := <-leaving:
			delete(clients, cli)
		}
	}
}

/*
下面再来看下每个用户自己的goroutine
handleConn函数创建一个对外发送消息的新通道,然后通过entering通道通知广播新客户到来,
接着它读取客户发来的每一行文本,通过全局接收消息通道将每一行发送给广播,发送时在每一条消息前面加上发送者ID作为前缀
一旦从客户端读取完毕消息,handleConn通过leaving通道通知客户离开然后关闭连接
*/

/*
handleConn函数会为每一个过来处理的conn创建一个新的channel,开启一个新的goroutine去把发送这个channel的消息写进conn
handleConn函数的执行过程可以简单地总结为一下几步
1.获取连接过来的IP地址和端口号
2.把欢迎信息写进channel返回给客户端
3.生成一条广播消息写进messages里
4.把这个channel加入到客户端集合,也就是entering <- ch
5.监听客户端往conn里写入数据,每扫描到一条就讲这条消息发送到广播channel中
6.如果关闭了客户端,如果关闭了客户端,那么把队列离开写入 leaving 交给广播函数去删除这个客户端并关闭这个客户端;
7.广播通知其他客户端该客户端已关闭;
8.最后关闭这个客户端的连接 Conn.Close()。
*/
func handleConn(conn net.Conn) {
	ch := make(chan string) //对外发送客户消息的通道
	go clientWriter(conn, ch)
	who := conn.RemoteAddr().String()
	ch <- "欢迎" + who
	messages <- who + "上线"
	entering <- ch

	input := bufio.NewScanner(conn)
	for input.Scan() {
		messages <- who + ":" + input.Text()
	}
	//注意input.Err()中可能的错误
	leaving <- ch
	messages <- who + "下线"
	conn.Close()
}

//ch <-chan string只写管道
func clientWriter(conn net.Conn, ch <-chan string) {
	for msg := range ch {
		fmt.Fprintln(conn, msg) //注意:忽略网络层面的错误
	}
}

client.go

package main

import (
	"io"
	"log"
	"net"
	"os"
)

/*
前面对服务端做了简单的介绍,下面介绍客户端,这里将其命名为“client.go”,完整代码如下所示:
*/

//client 是一个简单的TCP服务器读/写客户端
func main() {
	conn, err := net.Dial("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	done := make(chan struct{})
	go func() {
		io.Copy(os.Stdout, conn) //注意:忽略错误
		log.Println("done")
		done <- struct{}{} //向主goroutine发出信号
	}()
	mustCopy(conn, os.Stdin)
	conn.Close()
	<-done //等待后台goroutine完成
}

func mustCopy(dst net.Conn, src io.Reader) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Fatal(err)
	}
}

client1.go

package main

import (
	"io"
	"log"
	"net"
	"os"
)

/*
前面对服务端做了简单的介绍,下面介绍客户端,这里将其命名为“client.go”,完整代码如下所示:
*/

//client 是一个简单的TCP服务器读/写客户端
func main() {
	conn, err := net.Dial("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	done := make(chan struct{})
	go func() {
		io.Copy(os.Stdout, conn) //注意:忽略错误
		log.Println("done")
		done <- struct{}{} //向主goroutine发出信号
	}()
	mustCopy(conn, os.Stdin)
	conn.Close()
	<-done //等待后台goroutine完成
}

func mustCopy(dst net.Conn, src io.Reader) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Fatal(err)
	}
}

client2.go

package main

import (
	"io"
	"log"
	"net"
	"os"
)

/*
前面对服务端做了简单的介绍,下面介绍客户端,这里将其命名为“client.go”,完整代码如下所示:
*/

//client 是一个简单的TCP服务器读/写客户端
func main() {
	conn, err := net.Dial("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	done := make(chan struct{})
	go func() {
		io.Copy(os.Stdout, conn) //注意:忽略错误
		log.Println("done")
		done <- struct{}{} //向主goroutine发出信号
	}()
	mustCopy2(conn, os.Stdin)
	conn.Close()
	<-done //等待后台goroutine完成
}

func mustCopy2(dst net.Conn, src io.Reader) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Fatal(err)
	}
}

标签:一抄,err,goroutine,go,Go,服务器,main,conn,log
From: https://www.cnblogs.com/jianjiana/p/16968357.html

相关文章

  • 腾讯企业邮箱imap/pop3/smtp服务器信息(用于客户端)
    腾讯企业邮箱支持pop/imap/exchange协议从邮件服务器上获取邮件的信息、下载邮件等。IMAP/SMTP协议接收邮件服务器:imap.exmail.qq.com,使用SSL,端口号993发送邮件......
  • Golang常量与变量-Go语法1
    标识符在编程语言中标识符就是程序员定义的具有特殊意义的词,就是为变量名、常量名、函数名等等取的一个见名知义的名称。Go语言中标识符由字母数字和_(下划线)组成,并且只......
  • Django网站开发下
    文章目录​​1mysql版本太低的bug​​​​安装一些需要的应用​​​​<1>退出虚拟环境​​​​<2>supervisor​​​​<3>在虚拟环境安装gunicorn​​​​<4>在项目根......
  • python http 服务器
    官方自带http模块python-mSimpleHTTPServer8000添加上传功能github仓库simple_http_server#!/usr/bin/python#-*-coding:UTF-8-*-"""SimpleHTTPServ......
  • 一文带你搞懂 Google 发布的新开源项目 GUAC
    随着软件供应链攻击的显著增加,以及Log4j漏洞带来的灾难性后果和影响,软件供应链面临的风险已经成为网络安全生态系统共同关注的最重要话题之一。根据业内权威机构Sonaty......
  • 想早点下班?试试Aorm库吧,更方便的进行Go数据库操作
    使用go进行项目开发,大多数人会使用gorm,但是gorm有一些缺点,我无法接受。于是开发出了aorm,目前能有满足日常开发需求,并且完善了使用文档,希望能够帮助到大家。AormGolang操......
  • python之路45 初识django框架
    纯手撸web框架1.web框架的本质理解1:连接前端与数据库的中间介质理解2:socket服务端2.手写web框架1.编写socket服务端代码2.浏览器访问响应无效>>>:HTTP......
  • web框架和django简介
    纯手撸web框架基于wsgiref模块代码封装优化动静态网页jinja2模板语法python主流web框架django框架简介django基本操作命令django小白必会三板斧......
  • 初识Django
    初识Django前言:本章主要是对web框架做一个基础了解,为了接下来学习前端框架做铺垫必备知识点:什么是路由路由简单的来说就是根据用户请求的URL链接来判断对应的处理......
  • django框架
    web框架的本质理解1:连接前端与数据库的中间介质 理解2:socket服务端纯手撸web框架1.搭建socket服务端importsocketserver=socket.socket()server.bind(('127......