GithubHelp home page GithubHelp logo

issue with client about arpc HOT 14 CLOSED

lesismal avatar lesismal commented on September 27, 2024
issue with client

from arpc.

Comments (14)

spxvszero avatar spxvszero commented on September 27, 2024 1

不过想想感觉用处好像也不大= =
唉,就这样吧,谢谢作者解惑

from arpc.

lesismal avatar lesismal commented on September 27, 2024

你好,请问如果在连接成功的时候,客户端需要发送一条消息给服务端,是可以像以下代码这样发送消息么?

创建后就是用你传入的dialer连接好的了:

client, err := arpc.NewClient(dialer) // 如果你的dialer失败、会返回err:
if err != nil {
	log.Println("NewClient failed:", err)
	return
}
// 这里就可以进行一些初始化工作

因为意外断开时client会自动重连,重连后需要重新初始化这些,这时候就需要在 client.Handler.HandleConnected 的回调中进行,比如自带的 pubsub 扩展中的实现:
arpc.NewClient(dialer) 成功后就进行认证初始化:
https://github.com/lesismal/arpc/blob/master/examples/pubsub/client/client.go#L68
在OnConnected中处理重连后的认证初始化:
https://github.com/lesismal/arpc/blob/master/extension/pubsub/client.go#L215

client.Handler.HandleConnected(func(connectedClient *arpc.Client) {
		req := ""
		err := connectedClient.Call("/callAfterConnected","", &req, time.Second * 5)
		if err == nil {
			fmt.Println("Call After Connected Success.")
		}else {
			fmt.Println("Call After Connected Failed. ", err, "\n", req)
		}
	})

我尝试了一下,发现很奇怪的现象,有时候这个 Handler 不会触发,如果触发该 Handler ,则必定错误,原因均为 timeout,此时 ARPC 会报 [WRN] [ARPC CLI] OnMessage: session not exist or expired 错误。

备注:当前 client 并不需要执行 Stop 操作,所以确定不是因为 Stop 触发的。

client.Call如果超时后会清理掉这次 call 的 session,如果超时清理后才收到server的响应,就会报这个错:[WRN] [ARPC CLI] OnMessage: session not exist or expired,另一种可能是client端代码bug导致这次call 的 session 找不到(暂时没有发现存在这个问题)

如果需要更详细的信息,请附上能够复现的完整代码和步骤说明

from arpc.

spxvszero avatar spxvszero commented on September 27, 2024

arpcTest.zip
复现步骤:
1、新开一个终端,运行 Server 端。
2、注释 Server 的运行代码,新开一个终端运行 Client 端。

就能看到 HandleConnected 并没有执行,多次重新运行 Client 端,偶尔 HandleConnected 会执行,但是 Call 的调用会出现 timeout 错误。

from arpc.

lesismal avatar lesismal commented on September 27, 2024

类似这个: #3
因为OnConnected是在client启动后创建的读协程中调用的:
https://github.com/lesismal/arpc/blob/master/client.go#L737
所以按照你的代码的方式,设置HandleConnected和调用在不同协程、无法保证他俩的先后顺序,所以有时候会调用有时候不会调用

正确的写法主要有两种方式,第一种是像我上面说的,NewClient成功后手动调用初始化,然后再注册 HandleConnected:

onConnected := func(connectedClient *arpc.Client) {
	req := ""
	err := connectedClient.Call("/callAfterConnected","", &req, time.Second * 5)
	if err == nil {
		fmt.Println("Call After Connected Success.")
	}else {
		fmt.Println("Call After Connected Failed. ", err, "\n", req)
	}
}


client, err := arpc.NewClient(func() (net.Conn, error) {
	return net.DialTimeout("tcp", "localhost:8888", time.Second*3)
})
if err != nil {
	panic(err)
}
onConnected(client) // 这种方式是同步的,比如服务启动期间,如果初始化相关的失败了应该退出服务、人员检查错误日志排查问题而不是继续运行
client.Handler.HandleConnected(onConnected)

第二种是先把handler注册HandleConnected,然后再用该handler创建client:

onConnected := func(connectedClient *arpc.Client) {
	req := ""
	err := connectedClient.Call("/callAfterConnected","", &req, time.Second * 5)
	if err == nil {
		fmt.Println("Call After Connected Success.")
	}else {
		fmt.Println("Call After Connected Failed. ", err, "\n", req)
	}
}
arpc.DefaultHandler.HandleConnected(onConnected) // 这种方式是异步调用的,不方便像第一种方式那样在初始化阶段做启动失败的处理
client, err := arpc.NewClient(func() (net.Conn, error) {
	return net.DialTimeout("tcp", "localhost:8888", time.Second*3)
})
if err != nil {
	panic(err)
}

// 也可以不适用DefaultHandler、自己创建一个handler
// handler := NewHandler()
// handler.HandleConnected(onConnected) // 这种方式是异步调用的,不方便像第一种方式那样在初始化阶段做启动失败的处理
// client, err := arpc.NewClient(func() (net.Conn, error) {
// 	return net.DialTimeout("tcp", "localhost:8888", time.Second*3)
// }, handler)
// if err != nil {
// 	panic(err)
// }

from arpc.

spxvszero avatar spxvszero commented on September 27, 2024

正确的写法主要有两种方式,第一种是像我上面说的,NewClient成功后手动调用初始化,然后再注册 HandleConnected

第一种方式能理解,手动调用一次回调,但是这个仅仅只能解决 HandleConnected 偶尔没有调用的问题,当 HandleConnected 触发之后,反而这里的初始化工作调用了两次,需要做更多的容错处理。

第二种是先把handler注册HandleConnected,然后再用该handler创建client

第二种方式指的是以下这种初始化方式么?

clientHandler := arpc.NewHandler()

clientHandler.HandleConnected(func(connectedClient *arpc.Client) {
    req := ""
    err := connectedClient.Call("/callAfterConnected","", &req, time.Second * 5)
    if err == nil {
	    fmt.Println("Call After Connected Success.")
    }else {
	    fmt.Println("Call After Connected Failed. ", err, "\n", req)
    }
})

client, err := arpc.NewClient(func() (net.Conn, error) {
    return net.DialTimeout("tcp", "localhost:8888", time.Second*3)
}, clientHandler)

实测是解决了偶然没有调用的问题,还是 timeout 错误依旧没有解决

from arpc.

lesismal avatar lesismal commented on September 27, 2024

还是 timeout 错误依旧没有解决

这得看你自己的handler代码,如果确实响应慢或者timeout参数设置不合理比如过短,就是会timeout,Call返回会有err,日志报warnning是为了方便发现问题,但是个别调用timeout通常并不影响后续的继续Call

也可以把你的能够复现的代码和流程发出来看下

from arpc.

lesismal avatar lesismal commented on September 27, 2024

第一种方式能理解,手动调用一次回调,但是这个仅仅只能解决 HandleConnected 偶尔没有调用的问题,当 HandleConnected 触发之后,反而这里的初始化工作调用了两次,需要做更多的容错处理。

如果你的初始化包括了client.Call相关的,就不会调用两次。因为Call涉及一次完整的Call流程、包括send request 和 read response,而read response是在read loop中进行的然后才会触发client.Call返回,进入read loop后只有断线重连才会再次触发、而不会触发初始化时候的那次你说的重复调用

from arpc.

lesismal avatar lesismal commented on September 27, 2024

第二种方式指的是以下这种初始化方式么?

第二种方式就是我上面代码里结尾注释的那种,也就是你这里的这个,但是个人觉得不如第一种好(注释里解释的那样)

from arpc.

spxvszero avatar spxvszero commented on September 27, 2024

这得看你自己的handler代码,如果确实响应慢或者timeout参数设置不合理比如过短,就是会timeout,Call返回会有err,日志报warnning是为了方便发现问题,但是个别调用timeout通常并不影响后续的继续Call

也可以把你的能够复现的代码和流程发出来看下

代码:
arpTest.zip

复现步骤和之前一样

复现步骤:
1、新开一个终端,运行 Server 端。
2、注释 Server 的运行代码,新开一个终端运行 Client 端。

Server端结果:

2022/03/08 13:39:47.693 [INF] [ARPC SVR] Running On: "127.0.0.1:8888"
2022/03/08 13:39:54.919 [INF] [ARPC SVR]        127.0.0.1:63966 Connected
Handler Receive callAfterConnected
2022/03/08 13:39:59.925 [ERR] [ARPC SVR]        127.0.0.1:63966 Disconnected: EOF

Client端结果:

2022/03/08 13:39:54.918 [INF] [ARPC CLI]        127.0.0.1:8888  Connected
2022/03/08 13:39:59 Call failed: timeout
Call After Connected Failed.  timeout 
 
2022/03/08 13:39:59.924 [WRN] [ARPC CLI] OnMessage: session not exist or expired

from arpc.

lesismal avatar lesismal commented on September 27, 2024

如果一定要在 OnConnected 中,可以改成异步调用,否则因为Call已经阻塞了、read loop中的read还没开始、无法收到响应:

clientHandler.HandleConnected(func(connectedClient *arpc.Client) {
	go func() {
		req := ""
		err := connectedClient.Call("/callAfterConnected", "", &req, time.Second*5)
		if err == nil {
			fmt.Println("Call After Connected Success.")
		} else {
			fmt.Println("Call After Connected Failed. ", err, "\n", req)
		}
	}()
})

但我还是建议用我上面说的第一种方式,因为作为初始化阶段,通常是应该在服务启动阶段检查是否正常、如果不正常就应该终止启动流程、人工之类的排查问题(比如配置错了地址),这种用同步的方式会更好,OnConnected回调这种异步的方式你虽然也可以终止服务比如exit,但是如果网络波动断线自动重连、每次重连上都会回调这个,而波动导致的断线重连成功后如果产生错误跟启动阶段终止服务还是有一些区别的
还有一些情况,就是OnConnected可能适合用于初始化一些参数,比如设置tcp的参数、ws的相关,框架层这里应该是保留同步的方式更好些

之前也是有考虑过用户可能在OnConnected回调中进行Call,所以自动重连后框架内是用的异步回调:
https://github.com/lesismal/arpc/blob/master/client.go#L772
但是初始化阶段,是用户手动调用后就可以解决,所以初始化阶段的没有用异步:
https://github.com/lesismal/arpc/blob/master/client.go#L737

from arpc.

lesismal avatar lesismal commented on September 27, 2024

我在考虑是否把初始化阶段的这个也改成异步,然后即使你使用第二种方式也不会产生 session not exist or expired 的情况,其实我更偏向于把重连的也改回同步,由用户自己在回调函数里去异步处理需要异步的功能

from arpc.

lesismal avatar lesismal commented on September 27, 2024

我在考虑是否把初始化阶段的这个也改成异步,然后即使你使用第二种方式也不会产生 session not exist or expired 的情况,其实我更偏向于把[重连[(https://github.com/lesismal/arpc/blob/master/client.go#L737)的也改回同步,由用户自己在回调函数里去异步处理需要异步的功能

怕都改成同步有其他人已经依赖的代码产生bug,改成初始化阶段也异步回调了,已经发布 https://github.com/lesismal/arpc/releases/tag/v1.2.6 , 更新下试试吧

另外,像你例子中的调用,OnConnected里的Call和NewClient后直接Call的,因为是在不同的协程,这种调用的先后顺序框架是没法保证的、需要你的应用自己处理,比如在OnConnected回调里的Call或其他初始化流程成功后再进行其他Call

from arpc.

spxvszero avatar spxvszero commented on September 27, 2024

明白了,理解你的想法了,但是原本这样就会导致 OnConnected 使用同一个回调做了两种不同的操作,我觉得有必要的话可以拆开为两个回调,一个建立链接之后的初始化工作,另一个处理 OnConnected 建立链接之后的业务工作,这样也能满足在初始化中出现错误及时中止链接,另一方面不影响正常的业务。

from arpc.

lesismal avatar lesismal commented on September 27, 2024

其实现有方式都可以达到,很多种方式,比如设置tcp的属性也可以在dialer里进行 😂
所以无所谓了,就框架自己异步吧,免得用户再有用法上的困惑,感谢反馈!

from arpc.

Related Issues (20)

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.