• 本文实现一个Echo TCP Server

interface/tcp/Handler.go

type Handler interface {
Handle(ctx context.Context, conn net.Conn)
Close() error
}
  • Handler:业务逻辑的处理接口

    • Handle(ctx context.Context, conn net.Conn) 处理连接

tcp/server.go

type Config struct {
Address string
} func ListenAndServeWithSignal(cfg *Config, handler tcp.Handler) error {
closeChan := make(chan struct{})
listen, err := net.Listen("tcp", cfg.Address)
if err != nil {
return err
}
logger.Info("start listen")
ListenAndServe(listen, handler, closeChan)
return nil
} func ListenAndServe(listener net.Listener,
handler tcp.Handler,
closeChan <-chan struct{}) {
ctx := context.Background()
var waitDone sync.WaitGroup
for true {
conn, err := listener.Accept()
if err != nil {
break
}
logger.Info("accept link")
waitDone.Add(1)
go func() {
defer func() {
waitDone.Done()
}()
handler.Handler(ctx, conn)
}()
}
waitDone.Wait()
}
  • Config:启动tcp服务器的配置

    • Address:监听地址
  • ListenAndServe:ctx是上下文,可以传递一些参数。死循环中接收到新连接时,让一个协程去处理连接
  • 如果listener.Accept()出错了就会break跳出来,这时候需要等待已经服务的客户端退出。使用WaitGroup等待客服端退出
func ListenAndServe(listener net.Listener,
handler tcp.Handler,
closeChan <-chan struct{}) { go func() {
<-closeChan
logger.Info("shutting down...")
_ = listener.Close()
_ = handler.Close()
}() defer func() {
_ = listener.Close()
_ = handler.Close()
}() ......
}

listener和handler在退出的时候需要关掉。如果用户直接kill掉了程序,我们也需要关掉listener和handler,这时候要使用closeChan,一旦接收到关闭信号,就执行关闭逻辑

func ListenAndServeWithSignal(cfg *Config, handler tcp.Handler) error {

    closeChan := make(chan struct{})
sigCh := make(chan os.Signal)
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
go func() {
sig := <-sigCh
switch sig {
case syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
closeChan <- struct{}{}
}
}()
listen, err := net.Listen("tcp", cfg.Address)
if err != nil {
return err
}
logger.Info("start listen")
ListenAndServe(listen, handler, closeChan)
return nil
}

当系统对程序发送信号时,sigCh会接收到信号

tcp/echo.go

type EchoHandler struct {
activeConn sync.Map
closing atomic.Boolean
}

EchoHandler:

  • activeConn:记录连接
  • closing:是否正在关闭,有并发竞争,使用atomic.Boolean
type EchoClient struct {
Conn net.Conn
Waiting wait.Wait
} func (c *EchoClient) Close() error {
c.Waiting.WaitWithTimeout(10 * time.Second)
_ = c.Conn.Close()
return nil
}

EchoClient:一个客户端就是一个连接。Close方法关闭客户端连接,超时时间设置为10s

func MakeHandler() *EchoHandler {
return &EchoHandler{}
} func (h *EchoHandler) Handle(ctx context.Context, conn net.Conn) {
// 连接正在关闭,不接收新连接
if h.closing.Get() {
_ = conn.Close()
} client := &EchoClient{
Conn: conn,
}
h.activeConn.Store(client, struct{}{}) reader := bufio.NewReader(conn)
for {
msg, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
logger.Info("connection close")
h.activeConn.Delete(client)
} else {
logger.Warn(err)
}
return
}
// 正在处理业务,不要关掉
client.Waiting.Add(1)
// 将数据原封不动写回去,测试
b := []byte(msg)
_, _ = conn.Write(b)
client.Waiting.Done()
}
} func (h *EchoHandler) Close() error {
logger.Info("handler shutting down...")
h.closing.Set(true)
h.activeConn.Range(func(key interface{}, val interface{}) bool {
client := key.(*EchoClient)
_ = client.Close()
return true
})
return nil
}
  • MakeEchoHandler:创建EchoHandler
  • Handle:处理客户端的连接。
    • 1.连接正在关闭时,不接收新连接
    • 2.存储新连接,value用空结构体
    • 3.使用缓存区接收用户发来的数据,使用\n作为结束的标志
  • Close:将所有客户端连接关掉

main.go

const configFile string = "redis.conf"

var defaultProperties = &config.ServerProperties{
Bind: "0.0.0.0",
Port: 6379,
} func fileExists(filename string) bool {
info, err := os.Stat(filename)
return err == nil && !info.IsDir()
} func main() {
logger.Setup(&logger.Settings{
Path: "logs",
Name: "godis",
Ext: "log",
TimeFormat: "2022-02-02",
}) if fileExists(configFile) {
config.SetupConfig(configFile)
} else {
config.Properties = defaultProperties
} err := tcp.ListenAndServeWithSignal(
&tcp.Config{
Address: fmt.Sprintf("%s:%d",
config.Properties.Bind,
config.Properties.Port),
},
EchoHandler.MakeHandler())
if err != nil {
logger.Error(err)
}
}

测试

GO实现Redis:GO实现TCP服务器(1)的更多相关文章

  1. redis来共享各个服务器的session,并同时通过redis来缓存一些常用的资源,加快用户获得请求资源的速度(转)

    时间过得真快,再次登录博客园来写博,才发现距离上次的写博时间已经过去了一个月了,虽然是因为自己找了实习,但这也说明自己对时间的掌控能力还是没那么的强,哈哈,看来还需不断的努力啊!(这里得特别说明一下本 ...

  2. php+redis实现多台服务器内网存储session并读取

    大型网站由于大并发的问题会导致系统出现诡异的崩溃性问题这着实让人很是蛋疼,首先考虑的就是负载均衡服务器来处理这个,当然数据库的性能也是非常非常重要的,今天就说下在负载均衡情况下对于session这个问 ...

  3. 【Swoole】简单安装与创建TCP服务器

    pecl install swoole PHP的异步.并行.高性能网络通信引擎,使用纯C语言编写,提供了php语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据 ...

  4. Socket Server-基于线程池的TCP服务器

    了解线程池 在http://blog.csdn.net/ns_code/article/details/14105457(读书笔记一:TCP Socket)这篇博文中,服务器端采用的实现方式是:一个客 ...

  5. Python TCP服务器

    TCP服务器的创建也比较简单: 1.建立一个socket对象 2.绑定要监听的IP地址和端口 3.当有客户端请求时,启动多线程处理客户端连接 import socket import threadin ...

  6. [安卓] 9、线程、VIEW、消息实现从TCP服务器获取数据动态加载显示

    一.前言: 一般情况下从TCP服务器读取数据是放在一个线程里读的,但是刷新界面又不得不放在线程外面,所以需要用消息传递把线程里从TCP里获得的数据传送出来,然后根据数据对页面进行相应的刷新. 二.业务 ...

  7. Mina、Netty、Twisted一起学(一):实现简单的TCP服务器

    MINA.Netty.Twisted为什么放在一起学习?首先,不妨先分别看一下它们官方网站对其的介绍: MINA: Apache MINA is a network application frame ...

  8. TCP服务器不回复SYN的问题

    个人问题发生环境: 1.TCP服务器是虚拟机,IP地址是192.168.8.12. 2.TCP客户端是宿主机,IP地址是192.168.8.11. 3.从宿主机(192.168.8.11)上启动Soc ...

  9. 高性能、高并发TCP服务器(多线程调用libevent)

    from:http://blog.csdn.net/i_am_jojo/article/details/7587838 本文讲述的TCP服务器是模仿memcache中的TCP网络处理框架,其中是基于l ...

  10. python socket之tcp服务器与客户端demo

    python socket之tcp服务器与客户端demo 作者:vpoet mails:vpoet_sir@163.com server: # -*- coding: cp936 -*- ''' 建立 ...

随机推荐

  1. iOS block相关面试题

    一.前言本文重点来研究一下 objc 的 block,并具体来分析一下以下一些面试题目:block 的内部实现,结构体是什么样?block 是类吗?有哪些类型?一个 int 变量被 __block 修 ...

  2. 深入理解snp-calling流程——转载

    ------------恢复内容开始------------ GATK4流程 准备配套数据 明确参考基因组版本!!!b36/b37/hg18/hg19/hg38,记住b37和hg19并不是完全一样的, ...

  3. PHP Redis - zSet(有序集合)

    有序集合与集合一样,string类型元素的集合,不允许重复的成员. 有序集合,每个元素都会关联一个 double 类型的分数.Redis 通过分数为集合的成员进行从小到大的排序 有序集合的成员是唯一的 ...

  4. pnn模型 待整理

    https://blog.csdn.net/qq_18293213/article/details/90262378?spm=1001.2101.3001.6650.5&utm_medium= ...

  5. CEOI 2021

    CEOI 2021 \(pts\):64 + 0 + 4 \(T1 : 64pts\) 首先我们肯定知道对于相同的数,一定是放在一起才是最优的,随意我们对于每段查询的区间要保证有序,然后我们发现,每个 ...

  6. mysql零基础-2

    更新中的数据完整性错误 UPDATE employees SET department_id = 55 WHERE department_id = 110; 删除数据 删除一条记录 DELETE FR ...

  7. 机制设计原理与应用(三)Screening

    目录 3 Screening 3.1 为单个不可分割的项目定价 3.1.1 对\(\theta\)的假设 3.1.2 问题描述 3.1.3 特性 3.2 为无限可分的项目定价 3.2.1 对\(\th ...

  8. upload 上传文件

    func SaveUploadedFile(file *multipart.FileHeader, dst string) error{ src, err := file.Open() if err ...

  9. python中的变量定义

    1 变量名:由下划线.字母和数字组成 2 python中变量名的特殊含义: xx:标准的标识符,共有的 _xx:只是在from - import *时是不会被导入的,其他导入方式会被引入 隐藏变量还有 ...

  10. jmeter的三种参数化方式

    一.通过添加前置处理器(用户参数) 1. 在http层级下添加--前置处理器--用户参数 2.可以修改名称,每次迭代更新一次(一定要勾选上),这样才会每次迭代变量值也更新 ,点击下面添加用户(多次测试 ...