Giter Site home page Giter Site logo

link's Introduction

Go Foundation Go Report Card Build Status codecov GoDoc

介绍

最初开发这个包的目的是提炼一套可以在公司内多个项目间共用的网络层,因为在项目中我发现不同的网络应用一直重复一些相同或相类似的东西,比如最常用到的就是会话管理,不管是做游戏的前端连接层还是做服务器和服务器之间的RPC层或者是游戏的网关,虽然协议不一样但是它们都会需要会话的管理。会话管理看似简单,但是任何简单的问题一旦涉及到并发都会变得复杂起来,所以看似简单的会话管理每次实现起来都得再配套做单元测试甚至线上实际运行几个版本才能放心。所以我决定提取这些公共的部分,避免那些容易引入BUG的重复劳动。

但是在提取这些公共部分的时候并没有期初想象的那么容易,因为不同的应用场景有不同的需求,比如有的场景需要异步,有的场景需要同步,有的协议需要握手过程,有的则需要keepalive。从代码的提交历史里面可以看出这个包前后经过了很多次大的调整,因为要做一个能满足所有需求的通用网络层真的很难。

重构过程就像在简化公式,经过不断的提炼目前link变得十分的简单,同时它的定位也很清楚。link不是一个完整网络层也不是一个框架,它只是一个脚手架,它可以帮助你快速的实现出你所需要的网络层或者通讯框架,帮你约束网络层的实现方式,不至于用不合理的方式实现网络层,除此之外它不会管更多的事情。

link是协议无关的,使用link只需要理解少数几个概念就可以上手了。

核心

link包的核心是SessionSession的字面含义是会话,就是一次对话过程。每一个连接的生命周期被表达为一个会话过程,这个过程中通讯双方的消息有来有往。

会话过程所用的具体通讯协议通过Codec接口解耦。通过Codec接口可以自定义通讯的IO实现方式,如:TCP、UDP、UNIX套接字、共享内存等,也可以自定义流的实现方式,如:压缩、加密、校验等,也可以实现自定义的协议格式,如:JSON、Gob、XML、Protobuf等。

在实际项目中,通常不会只有一个会话,所以link提供了几种不同的Session管理方式。

Manager是最基础的Session管理方式,它负责创建和管理一组SessionManager是不与通讯形式关联的,与通讯有关联的ManagerServer,它的行为比Manager更具体,它负责从net.Listener上接收新连接并创建Session,然后在独立的goroutine中处理来自新连接的消息。

link还提供了Channel用于对Session进行按需分组,Channel用key-value的形式管理SessionChannel的key类型通过代码生成的形式来实现自定义。

示例

link在codec目录下实现了集中简单的协议类型,可作为示例。

示例,创建一个使用Json作为消息格式的TCP服务端:

package main

import (
	"log"
	"net"

	"github.com/funny/link"
	"github.com/funny/link/codec"
)

type AddReq struct {
	A, B int
}

type AddRsp struct {
	C int
}

type Server struct{}

func main() {
	json := codec.Json()
	json.Register(AddReq{})
	json.Register(AddRsp{})

	listen, err := net.Listen("tcp", "")
	checkErr(err)
	server := link.NewServer(listen, json, 1024, new(Server))
	go server.Serve()
	addr := server.Listener().Addr()

	clientSession, err := link.Dial(addr.Network(), addr.String(), json, 1024)
	checkErr(err)
	clientSessionLoop(clientSession)
}

func (*Server) HandleSession(session *link.Session) {
	for {
		req, err := session.Receive()
		checkErr(err)

		err = session.Send(&AddRsp{
			req.(*AddReq).A + req.(*AddReq).B,
		})
		checkErr(err)
	}
}

func clientSessionLoop(session *link.Session) {
	for i := 0; i < 10; i++ {
		err := session.Send(&AddReq{
			i, i,
		})
		checkErr(err)
		log.Printf("Send: %d + %d", i, i)

		rsp, err := session.Receive()
		checkErr(err)
		log.Printf("Receive: %d", rsp.(*AddRsp).C)
	}
}

func checkErr(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

补充说明

这个文件里实现了Channel类型用于管理一组Session,通常用于发送广播和维护在线列表。

之前版本的Channel类型,用的是Session.ID()做key,这个设计会导致实际项目种出现类似这样的操作逻辑:

取用户ID -> 从自己维护的映射关系中取用户ID对应的Session ID -> 到Channel里取Session

而新的Channel类型可以自定义key类型,在上述场景中就可以直接用用户ID做key来索引Session

取用户ID -> 到Channel里取Session

除了直观的可以看出少了一次map操作之外,其实额外维护一份Session ID映射关系也不是一件容易的事情,你需要重复Channel内部做的所有事情,而又不能重用Channel的代码。

使用Channel.Fetch()进行遍历发送广播的时候,请注意存在IO阻塞的可能,如果IO阻塞会影响业务处理,就需要使用异步发送。

相关项目

参与项目

欢迎提交通过github的issues功能提交反馈或提问。

技术群:474995422

link's People

Contributors

astaxie avatar bg5sbk avatar cofyc avatar name5566 avatar oikomi avatar qinlai avatar retervision avatar wab301 avatar yourihua avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

link's Issues

packet, N

我看 N ∈ [1,2,4,8], 我觉得3也可以加上,mysql就是3

请问下处理性能如何?

简单的写了个server, 用link.Packet包上自己的一个CodecType, 解包有时候需要2ms, 有时候会快些400微秒

我写的codectype解包只有这样的的语句(大根10个这样的字段):
头部
binary.Read(reader, binary.BigEndian, ...)

body部分:
ReadFull

是不是我的姿势不对?

Exit channel when session close

in the demo"\src\github.com\funny\link\examples\broadcast\main.go",line 41:
channel.Exit(session);

can this be done automatic when session is closed?
BTW,many other network framework do this internally,line socket.io in node.js

示例中的Manager同时只支持32个session吗

`func (manager *Manager) putSession(session *Session) {
smap := &manager.sessionMaps[session.id%sessionMapNum]
smap.Lock()
defer smap.Unlock()

smap.sessions[session.id] = session
manager.disposeWait.Add(1)

}`

`const sessionMapNum = 32

type Manager struct {
sessionMaps [sessionMapNum]sessionMap
disposeFlag bool
disposeOnce sync.Once
disposeWait sync.WaitGroup
}`

按照README说的 session的字面含义是会话,就是一次对话过程。每一个连接的生命周期被表达为一个会话过程,这个过程中通讯双方的消息有来有往。
意思是说同时只能有32个连接在线?

tcp连接写,是否有必要加锁

你好,在阅读你源码的时候发现个问题想请教下,源码中session的send()方法同步情况下调用session.codec.Send(msg)加了个写锁的目的是什么?如果是为了保证写入数据包的完整性,是没有必要的,tcp底层已经有这个锁了,参见net/fd_unix.go的netFd.Write().

move func newSession to server.go

hi @idada
in file session.go the following codes should move to server.go , agree?

func (server *Server) newSession(id uint64, conn net.Conn) *Session {
session := NewSession(id, conn, server.protocol, server.sendChanSize, server.readBufferSize)
session.server = server
session.server.putSession(session)
return session
}

link程序 , 复杂包头, 如何进行拆包?

例如包头:

typedef struct _yar_header {
    unsigned int   id;            // transaction id
    unsigned short version;       // protocl version
    unsigned int   magic_num;     // default is: 0x80DFEC60
    unsigned int   reserved;
    unsigned char  provider[32];  // reqeust from who
    unsigned char  token[32];     // request token, used for authentication
    unsigned int   body_len;      // request body len
}

重构之后的异步发送问题

codec_async.go 文件已经不存在了,如何区分现有功能模块中的同步/异步发送?是否异步发送是只要设置Serve中的最后一个sendChanSize!=0即可?

请教点问题

我非常的喜欢golang,所以想仔细认真的学习这门语言。
我准备做一个简易的规则引擎服务器小平台,就是向服务传相应的参数 可以返回数字或者是xml
一方面加深我的学习,一方面以后的工作中很可能使用到这个平台,
但是有些问题我没想明白,所以希望大神能够忙里抽闲,指点我一下,或者给我一个思路。
我打算做一个socket 服务器,这样这边的java 用socket连接 发送数据过来就直接可以得到 规则生成的数值了
连接这部分应该不算难,也没有涉及到页面,只是数据传输,所以我觉得就不必使用HTTP了
但涉及到浏览器页面对数据进行的管理的时候,我就有点想不明白了,这块应该如何 想 http协议 那样进行请求 管理数据啊?
大神有没有做过 这样的服务啊?能不能帮忙 给点建议?使用socket是不是合理啊?

backend_link.go

backend_link.go中的dispathMsg函数中
select default中是使用的go conn.close(true)

如果conn.recvChan被写满了堵塞了的话 后续还有消息到来 会抛出异常send on closed channel

问题

1.manager为啥要加一个32的list的session啊?
2.manager和session的各种加锁操作,为啥不通过协程来规避掉, 是因为锁的效率大于协程的处理吗?

Performance benchmarking?

Is there a performance benchmarking for link? What is the maximized session numbers can be supported simultaneously?

It will be interesting to see some real life use cases 🍻

session.go 中的 closeChan 字段有什么用?

在整个项目中全局搜索, 发现这个字段只在 session.goClose() 函数中出现过, 而且是判断 != nil 后就关闭了. 请问这个字段具体是用来做什么的呢?

内置消息分发接口?

目前的接口抽象度太高,从零开始搭建一个服务端的过程不够流畅,前期准备工作太多。

对于网关和游戏这种分包 + 多消息类型的项目,都会需要重头构建一套消息识别和派发的CodecType。

是不是应该内置进消息识别和分发的接口,就可以配合fastbin或者别的协议工具快速搭建项目。

想象中应该像这样:

server, _ := link.Serve("tcp", "0.0.0.0:10010", 
    link.Packet(2, 65535, 4096, link.Uint16Handlers{
        1: PingMsgHandler,
        2: PongMsgHandler,
    }),
)

session, _ := server.Accept()

var msg link.Request

session.Receive(&msg)

msg.Process()

执行效率和封装性的矛盾

春哥在用link重写游戏网关的时候遇到这样的问题:

网关和游戏服之间的通讯协议,是为了执行效率特别优化过的,游戏服发送给网关的消息包当中包含了要发送给客户端的完整消息包,也就是包头+包体,而不是只有包体。

在用link之前,网关是完全手工实现,没有库的依赖,所以网关读取和解析游戏服发送来的消息包之后,剩余的内容就可以直接发送给客户端,不需要做二次封包。

但是用link包的时候,由于link包没有提供直接发送完整消息包的接口,就只能把包体分离出来,然后交给link的发送接口,接着里面再重新封包。

这里就存在着封装性和执行效率的矛盾。如果link包直接提供了发送原始数据的接口给外部,那么link包的协议封装就被破坏了,细节就泄漏到了应用层。

但是如果不提供这样的接口,一些应用场景下又有局限性,无法做到最优。

大家怎么看这个问题呢?

  1. 是否应该提供发送原始数据的接口
  2. 接口以什么形式提供
  3. 是否可以做到不破坏封装

请教解码

我看了一下代码,你这个编码器的解码编码是直接挂接到Connection上的,这个时候session的Recive函数做解码处理的时候
msg, err := session.codec.Receive()
if err != nil {
session.Close()
}
这个直接从Connection中获取数据解码,你这个不处理粘包问题?始终能够保证,你这个Con读取出来的都是完整的Json包么?这个无法保证吧,而我去看了Json的,解码代码,他是读取,然后解码失败,就返回了,所以感觉,你这个地方直接绑定,对于粘包的处理,好像没做考虑?

server.go

hi @idada

// Get listener address.
func (server *Server) Listener() net.Listener {
return server.listener
}
// Get packet protocol.
func (server *Server) Protocol() PacketProtocol {
return server.protocol
}

change to

// Get listener address.
func (server *Server) GetListener() net.Listener {
return server.listener
}
// Get packet protocol.
func (server *Server) GetProtocol() PacketProtocol {
return server.protocol
}

PacketN is not a good factory function name

hi @idada

func PacketN(n uint, bo binary.ByteOrder) *PNProtocol {
return &PNProtocol{
n: n,
bo: bo,
}
}

maybe NewPNProtocol is more golang style, PacketN can be a commet

func NewPNProtocol(n uint, bo binary.ByteOrder) *PNProtocol {
return &PNProtocol{
n: n,
bo: bo,
}
}

return err

hi @idada

we must monitor return success or failed in application

// Broadcast to channel sessions.
func (channel *Channel) Broadcast(message Message) {
channel.broadcaster.Broadcast(channel, message)
}

change to

// Broadcast to channel sessions.
func (channel *Channel) Broadcast(message Message) error {
return channel.broadcaster.Broadcast(channel, message)
}

请教一个问题,关于atomic在link中的使用

读您的代码,在很多地方看到了CAS的使用,但是我理解,在很多地方应该不会有多个线程同时访问的,比如server.go的line 89(为new session分配id),以及stop server的地方。
能否简单解释一下呢,什么情况下会多线程访问?

消息接收、发送错位

现在github上的最新的link库 由于session中的Send、Receive去掉了lock
在codec_packet中的encodeHead和decodeHead未做lock处理 会引发消息接收、发送错位
而codec_safe只是对body进行了lock

overflows int

➜  basetcpserver go run server.go 
# github.com/funny/link/codec
../../github.com/funny/link/codec/fixlen.go:57: constant 4294967295 overflows int
../../github.com/funny/link/codec/fixlen.go:58: constant 4294967295 overflows int
../../github.com/funny/link/codec/fixlen.go:60: constant 4294967295 overflows int
../../github.com/funny/link/codec/fixlen.go:61: constant 4294967295 overflows int
../../github.com/funny/link/codec/fixlen.go:137: c.fixlenReadWriter.recvBuf.Reset undefined (type bytes.Reader has no field or method Reset)

运行demo的时候报了这个错,我是新手

对 broadcast.go 的 疑问

broadcast.go

func (channel *Channel) Fetch(callback func(*Session)) {
    channel.mutex.RLock()
    defer channel.mutex.RUnlock()

    for _, sesssion := range channel.sessions {
        callback(sesssion.Session)
    }
}

callback 里面使用AsyncSend timeout 5s ,那么是不是这个广播也将 阻塞5s

关于session的close

session close的实现是直接关闭了closeChan, 但是如果此时sendChan中还有没法送完的消息,直接close的话,会导致这些消息不会发送成功,所以这个close代表着强制关闭,如果我想要有序的关闭,应该怎么做呢?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.