【问题标题】:Swift and UDP socket packet send twiceSwift 和 UDP 套接字数据包发送两次
【发布时间】:2020-06-08 15:06:56
【问题描述】:

我正在尝试创建一个与 Ingenico POS 配合使用的驱动程序。我必须通过 UDP 向这个设备发送数据包。我使用了这个库CocoaAsyncSocket 的规模(也适用于 UDP 协议),它运行良好,但现在使用这个设备我遇到了一些问题。 要使用此设备,我必须执行以下操作:

  1. 发送 ping 数据包
  2. 发送支付验证包
  3. 监听套接字以获取通知

有时我有一个奇怪的行为:数据包将发送两次并为设备产生问题。 为了理解这个问题,我使用了 WireShark 并得到了这个日志:

正如您在这张图片中看到的,数据包 1832 和数据包 1833 是相同的,实际上设备会回答我两次(参见数据包 1834 和数据包 1835)。 对于 ping 数据包,这不是一个大问题,但对于支付验证数据包,这是一个大问题,事实上,如果我发送了两次支付验证数据包,设备会以错误的方式回答我。 我不明白为什么有时我有这种有线行为,有时它工作得很好,我确信我的代码只发送了一次数据包。 任何人都知道如何解决这个问题? 谢谢

使用询问的代码进行编辑

创建套接字的代码:

private func setupSocket() {
    outSocket = GCDAsyncUdpSocket.init(delegate: self, delegateQueue: DispatchQueue.main)
    outSocket.setIPv4Enabled(true)
    outSocket.setIPv6Enabled(false)
    do {
       try outSocket.connect(toHost:ip ?? "", onPort: port ?? 0)
    } catch {
       outSocket.close()
    }
    do {
       try outSocket.beginReceiving()
    } catch {
       outSocket.close()
    }
}

当套接字在委托中连接时,我执行以下操作:

public func udpSocket(_ sock: GCDAsyncUdpSocket, didConnectToAddress address: Data) {
    outSocket.send(self.generatePacket(), withTimeout: 10, tag: 0)
}

生成数据包的函数如下:

private func generatePacket() -> Data {
    var data = Data()
    switch actualOperation {
    case .FIRST_PACKET:
        data.append(self.generateHeader())
        break
    case .SECOND_PACKET:
        data.append(self.generateHeader())
        data.append(self.generateBody())
        break
    default:
        break
    }
    print("REQUEST PKT: \(data.hexEncodedString(options: .upperCase))")
    return data
}

private func generateHeader() -> Data {
    var data = Data()
    switch actualOperation {
    case .FIRST_PACKET:
        data.append(TransportProtocol.pingFromPosToClient, count: TransportProtocol.pingFromPosToClient.count)
        break
    case .SECOND_PACKET:
        data.append(TransportProtocol.messageFromPosToClient, count: TransportProtocol.messageFromPosToClient.count)
        break
    default:
        break
    }
    data.append(Constant.nodeId, count: Constant.nodeId.count)
    data.append(Constant.portId, count: Constant.portId.count)
    data.append(Constant.groupId, count: Constant.groupId.count)
    data.append(self.generateTransmissionId())
    return data
}
private func generateTransmissionId() -> Data {
    var data = Data()
    if let transId = String(format: "%05ld", transmissionID).data(using: .utf8) {
        data.append(transId)
        transmissionID += 1
        if (transmissionID > 99999) {
            transmissionID = 1
        }
        UserDefaults.standard.set(transmissionID, forKey: "TransmissionID")
    }
    return data
}
private func generateBodyForPayment() -> Data {
    var data = Data()
    data.append(self.getPacketLength())
    data.append(CommandsPosToClient.authRequest, count: 1)
    data.append(OperationTypePosToClient.opPayment, count: 1)
    switch currency {
    case .DANISH_KRONE:
        data.append(CurrencyCode.danishKrone, count: 1)
        break
    case .EURO:
        data.append(CurrencyCode.euro, count: 1)
        break
    case .SWISS_FRANC:
        data.append(CurrencyCode.swissFranc, count: 1)
        break
    case .CZECH_KORUNA:
        data.append(CurrencyCode.czechKoruna, count: 1)
        break
    case .UK_POUND:
        data.append(CurrencyCode.ukPound, count: 1)
        break
    case .US_DOLLAR:
        data.append(CurrencyCode.usDollar, count: 1)
        break
    case .KUWAITI_DINAR:
        data.append(CurrencyCode.kuwaitiDinar, count: 1)
        break
    case .POLISH_ZLOTY:
        data.append(CurrencyCode.polishZloty, count: 1)
        break
    case .HUNGARIAN_FLORINT:
        data.append(CurrencyCode.hungarianFlorint, count: 1)
        break
    default:
        data.append(CurrencyCode.euro, count: 1)
        break
    }
    let amount = Int(toPay * 100)
    if let amountToPay = String(format: "%09ld", amount).data(using: .utf8) {
        data.append(amountToPay)
    }
    data.append(Constant.manTrans, count: Constant.manTrans.count)
    //ID cassiere 8 byte
    if let idEmp = "\(idEmployee ?? 0)".paddingToLeft(upTo: 8).data(using: .utf8) {
        data.append(idEmp)
    }
    //Numero scontrino 8 byte
    if let billNmb = "\(billNumber ?? 0)".paddingToLeft(upTo: 8).data(using: .utf8) {
        data.append(billNmb)
    }
    data.append(Constant.plugIn, count: Constant.plugIn.count)
    data.append(Constant.voidField, count: Constant.voidField.count)
    if let amountToPay = String(format: "%09ld", amount).data(using: .utf8) {
        data.append(amountToPay)
    }
    data.append(Constant.voidField, count: Constant.voidField.count)
    data.append(Constant.voidField, count: Constant.voidField.count)
    data.append(Constant.voidField, count: Constant.voidField.count)
    //TODO: Extra data
    data.append(Constant.voidField, count: Constant.voidField.count)
    data.append(Constant.voidField, count: Constant.voidField.count)

    if let crcData = self.crc16(data.bytes) {
        data.append(crcData)
    }
    return data
}
private func getPacketLength() -> Data {
    var data = Data()
    var packetLength = 2
    packetLength += 1 //Lunghezza del comando --> sempre 1 byte
    packetLength += 1 //Lunghezza dell'operazione --> sempre 1 byte
    switch actualOperation {
    case .SECOND_PACKET:
        packetLength += 1 //Lunghezza della valuta
        packetLength += 9 //Lunghezza dell'importo
        packetLength += 1 //Lunghezza della tipologia della transazione
        packetLength += 8 //Lunghezza del campo dedicato all'ID del cassiere
        packetLength += 8 //Lunghezza del campo dedicato al numero dello scontrino
        packetLength += 1 //Lunghezza di plug-in
        packetLength += 1 //Lunghezza di RFU
        packetLength += 9 //Lunghezza del totale
        packetLength += 1 //Lunghezza di Track 1
        packetLength += 1 //Lunghezza di Track 2
        packetLength += 1 //Lunghezza di Track 3
        packetLength += 1 //Lunghezza di Extra data length
        packetLength += 1 //Lunghezza di Extra data
        break
    default:
        break
    }

    var value = UInt16(littleEndian: UInt16(packetLength))
    var array = withUnsafeBytes(of: &value) { Array($0) }
    array.reverse()
    for var byte in array {
        data.append(Data(bytes: &byte, count: 1))
    }
    return data
}
private func crc16(_ data: [UInt8]) -> Data? {
    guard !data.isEmpty else {
        print("data is empty")
        return nil
    }
    let polynomial: UInt16 = 0xA001
    var accumulator: UInt16 = 0
    for byte in data {
        var tempByte = UInt16(byte)
        for _ in 0 ..< 8 {
            let temp1 = accumulator & 0x0001
            accumulator = accumulator >> 1
            let temp2 = tempByte & 0x0001
            tempByte = tempByte >> 1
            if (temp1 ^ temp2) == 1 {
                accumulator = accumulator ^ polynomial
            }
        }
    }
    var data = Data()
    var value = UInt16(bigEndian: accumulator)
    var array = withUnsafeBytes(of: &value) { Array($0) }
    array.reverse()
    for var byte in array {
        data.append(Data(bytes: &byte, count: 1))
    }
    return data
}

这是分析答案的代码:

public func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) {
    print("OPERATION: \(actualOperation) ANSWER: \(data.hexEncodedString(options: .upperCase))")
    switch actualOperation {
    case .SEND_PING:
        actualOperation = .SEND_PAYMENT_AUTH
        sock.send(self.generatePacket(), withTimeout: 10, tag: 0)
        break
    case .ABORT_PAYMENT:
        sock.close()
        break
    default:
        if (data.count > 21) {
            //Elimino la prima parte del pacchetto che non mi interessa ed ottengo il pacchetto contenente i dati
            let packetData = data.subdata(in: 23..<data.count)
            switch packetData.subdata(in: 0..<1).bytes {
            case ClientToPos.statusNotification:
                self.delegate?.posDeviceInPayment(actualStatus: String(decoding: packetData.subdata(in: 2..<packetData.count - 2), as: UTF8.self), model: .DEVICE)
                break
            case ClientToPos.receiptDataPayment:
                let billDataLength = UInt16(bigEndian: packetData.subdata(in: 87..<89).withUnsafeBytes { $0.load(as: UInt16.self) })
                billData = String(decoding: packetData.subdata(in: 90..<90 + Int(billDataLength)), as: UTF8.self)
                break
            case ClientToPos.transactionDone:
                if (errorCode != "" || errorDescription != "") {
                    self.delegate?.posDeviceOperationFailed(errorCode: errorCode, errorDescription: errorDescription, model: .DEVICE)
                } else {
                    self.delegate?.posDeviceOperationCompleted(extraInfo: billData, model: .DEVICE)
                }
                sock.close()
                break
            case ClientToPos.panChecking:
                self.delegate?.posDevicePanCheckingRequired(model: .DEVICE)
                break
            case ClientToPos.extendedError:
                errorCode = String(decoding: packetData.subdata(in: 1..<5), as: UTF8.self)
                errorDescription = String(decoding: packetData.subdata(in: 5..<packetData.count - 4), as: UTF8.self)
                break
            case ClientToPos.error:
                errorCode = String(decoding: packetData.subdata(in: 1..<5), as: UTF8.self)
                errorDescription = String(decoding: packetData.subdata(in: 5..<packetData.count - 4), as: UTF8.self)
                break
            default:
                break
            }
        }
        break
    }
}

希望对你有帮助

【问题讨论】:

  • 这一定是代码中的逻辑问题,默认不会发送两次。您需要与我们分享一些代码以便能够尝试提供帮助,您是否尝试过在发送此代码的代码中放置断点并检查堆栈跟踪?
  • @Scriptable:我添加了我的代码,如果您需要我可以添加 Xcode 控制台以更好地理解问题
  • 也许在generateBodyForPayment 中放置一个断点来检查它被调用了多少次并检查堆栈以查看调用它的内容?将断点放在其他函数中可能会更好,但将断点放在那里不会有坏处。
  • @Daniel:我试过了,但是方法generateBodyForPayment被调用了一次
  • 我强烈建议你重构你的代码,以便函数接收参数来告诉他们要做什么,并且你有特定操作的显式函数而不是依赖于外部状态。例如。有类似func generatePingPacket() 的东西,它调用generatePacket(type:.ping)。这段代码看起来像是一些 C 代码或其他东西的翻译,而不是正确的 Swift 实现。由于值缓存,使用外部状态可能会导致不一致的行为。至少确保您的状态属性标记为动态。

标签: ios swift sockets udp


【解决方案1】:

大家有兴趣,这个问题是网络造成的。在家里,我有一个 Vodafone 网络,还有他们的路由器(Vodafone Station Revolution)。 Vodafone Station Revolution 产生 Wi-Fi @ 2,4GHz 和 Wi-Fi @ 5GHz。当我尝试在此网络上发送 UDP 数据包时,此数据包将发布两次,一个用于 2.4GHz,另一个用于 5GHz 网络。因此,为了解决我的问题,我禁用了 5GHz 网络并且一切正常。这不是代码问题,实际上是网络问题。 我希望我的经验对其他需要在 Vodafone 网络上发送 UDP 数据包的人有用。 顺便谢谢你的帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-26
    • 2010-11-13
    • 1970-01-01
    • 2012-10-03
    • 2012-05-20
    • 1970-01-01
    • 2013-08-28
    相关资源
    最近更新 更多