hprose / hprose-golang

Hprose is a cross-language RPC. This project is Hprose for Golang.
MIT License
1.26k stars 205 forks source link

如果客户端在第一次推送请求没有超时之前发起第二次请求会导致无法接收到结果 #77

Closed wuhelong closed 2 years ago

andot commented 6 years ago

超时之后客户端会重新发起请求,这样修改之后,第二个请求会让第一个请求立即返回 nil,而第一个请求返回之后,会立即发起重试,重试的请求又会立刻让第二个请求立即返回 nil,然后就会两个请求不断的交替返回 nil 了。这样不对吧。

所以,第一次推送请求没有超时之前,不应该发起第二次请求才对。

wuhelong commented 6 years ago

对是有问题研究中

wuhelong commented 6 years ago

因为超时有1两分钟, 客户端有可能已经重新启动了,如果使用相同ID就会导致出错

wuhelong commented 6 years ago
type signals int32

var heartbeatSignals = new(interface{})
var clearBeforeSignals int32 = 0

// Publish the hprose push topic
func (service *BaseService) Publish(
    topic string,
    timeout time.Duration,
    heartbeat time.Duration) Service {
    if timeout <= 0 {
        timeout = service.Timeout
    }
    if heartbeat <= 0 {
        heartbeat = service.Heartbeat
    }
    t := newTopic(heartbeat)
    service.topicLock.Lock()
    service.topics[topic] = t
    service.topicLock.Unlock()
    return service.AddFunction(topic, func(id string) interface{} {
        message := t.get(id)
        cid := signals(atomic.AddInt32(&clearBeforeSignals, 1))
        if message == nil {
            message = make(chan interface{})
            t.put(id, message)
            fireSubscribeEvent(topic, id, service)
        }
        select {
        case message <- cid:
        default:
        }

    receiveMessage:
        select {
        case result := <-message:
            if result == heartbeatSignals {
                goto receiveMessage
            }
            if result == cid {
                goto receiveMessage
            }
            switch result.(type) {
            case signals:
                return nil
            }
            return result
        case <-time.After(timeout):
            go func() {
                select {
                case message <- heartbeatSignals:
                    break
                case <-time.After(t.heartbeat):
                    service.offline(t, topic, id)
                }
            }()
            return nil
        }
    }, Options{})
}

这样可以不

andot commented 6 years ago

这个应该不是单纯改服务器能解决的,需要客户端配合才行。比如当第一个客户端还未超时,第二个客户端连上来时,服务器端返回一个特定异常,第一个客户端如果收到这个异常时,就不再重试。如果第一个客户端已经关掉了,当然也就收不到这个异常,也不会重试,这样的话,就可以解决你说的问题了。但是这需要客户端和服务器端都做修改才行,每种语言的实现都需要修改才能通用。

在不做修改的情况下,客户端最好是不要用同样的 id 比较好。

wuhelong commented 6 years ago

当前我使用AccessToken作为ID每个设备是有不同ID的,只是想避免同一客户端多次发送订阅会导致的故障。当前实现,如果在android系统客户端因为内存不足被系统停止,然后在服务器超时前客户端重新启动并发起订阅使用之前的相同AccessToken就会出错,服务器发送的消息会被之前已经断开的连接接收,新的连接无法接收到消息。

andot commented 6 years ago

是会有这个问题,所以如果要用同一个 ID,还不产生问题,只能服务器和客户端都做修改才行。最近打算写 Hprose 3.0,这个推送到时候一起升级吧。

还有什么好的建议,欢迎提出哈。

wuhelong commented 6 years ago

这是一个好消息 如果提供一个机制可以在方法被调用时取得上下文会比较好,例如在调用方法时如果第一个参数是预定义的上下文类型,就把上下文发送过去 比如现在如果想获得调用方IP当前好像不能实现(新手如果可以请指正) 如果可以集成身份验证就更好了 谢谢

andot commented 6 years ago

方法调用时,服务器端的方法可以将最后一个方法定义为上下文对象,客户端不需要传入,服务器端会自动把上下文对象传入。所以,你在服务器端的方法中可以方便的存取上下文。只不过现在服务器端上下文和客户端上下文是独立的。Hprose 3.0 会增加一个客户端和服务器端共享的上下文,相当于http的Header,但是它是独立于Http的,在任何底层协议上都可以传递。

wuhelong commented 6 years ago

当前以及支持了吗?刚刚我测试好像不可用

andot commented 6 years ago

当前还不支持,Hprose 3.0 目前还在方案设计阶段:https://github.com/hprose/hprose/issues/6 你可以来贡献你的想法和解决方案。

angwangiot commented 6 years ago

使用Hprose时,最近正好用到了推送的功能,看到了这个问题,现在还没出3.0,我在想,可以通过业务上的处理来暂时规避掉这个问题。只要在订阅的ID中的AccessToken前面或者后面加一段uuid,这样既保证了每次订阅的ID里都有固定的Token信息,也保证了不会出现完全重复的id导致交替连接的情况。

andot commented 2 years ago

现在 3.0 已经出来了,这个问题可以关闭了。