iOS端TCP连接消息发送成功但服务端未接收问题排查
iOS NWConnection TCP发送消息本地成功但服务端无接收的排查与修复
问题概述
通过NWConnection建立TCP连接,连接和消息接收功能正常,但发送消息时本地日志提示发送成功,服务端无接收记录。此前功能正常,迭代后失效且无法回退。
预期行为
在ViewController中输入IP地址和端口,点击「连接」按钮,通过UIAlertController接收JSON数据,点击「确定」按钮后发送字符串。
实际行为
连接与消息接收功能正常,点击「确定」按钮后本地提示消息发送成功,但服务端仅显示连接记录,未收到消息。
相关代码
class ViewController: UIViewController { @IBOutlet weak var ipAddress: UITextField! @IBOutlet weak var portNumber: UITextField! @IBOutlet weak var statusLabel: UILabel! var connection: NWConnection? var message: String? override func viewDidLoad() { super.viewDidLoad() let backgroundImageView = UIImageView() backgroundImageView.image = UIImage(named: "bg") backgroundImageView.contentMode = .scaleAspectFill backgroundImageView.frame = view.bounds view.insertSubview(backgroundImageView, at: 0) let hideKeyboard = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard)) view.addGestureRecognizer(hideKeyboard) } @IBAction func connectTapped(_ sender: Any) { guard let ipAddress = ipAddress.text, !ipAddress.isEmpty else { makeAlert(titleInput: "Error!", messageInput: "Please enter IP Address") return } guard let portText = portNumber.text, let port = UInt16(portText), !portText.isEmpty else { makeAlert(titleInput: "Error!", messageInput: "Please enter port number") return } self.connectToServer(ipAddress: ipAddress, port: port) } //**Connection to Server** func connectToServer(ipAddress: String, port: UInt16) { let host = NWEndpoint.Host(ipAddress) let port = NWEndpoint.Port(rawValue: port)! connection = NWConnection(host: host, port: port, using: .tcp) connection?.stateUpdateHandler = { [weak self] newState in switch newState { case .ready: DispatchQueue.main.async { self?.statusLabel.text = "Connected to server." self?.receiveMessage() } case .failed(let error): DispatchQueue.main.async { self?.statusLabel.text = "Failed to connect: \(error.localizedDescription)" } self?.connection?.cancel() self?.connection = nil default: break } } connection?.start(queue: .global(qos: .background)) } func makeAlert(titleInput: String, messageInput: String, completion: (() -> Void)? = nil) { let alert = UIAlertController(title: titleInput, message: messageInput, preferredStyle: .alert) let okButton = UIAlertAction(title: "Okay", style: .default) { _ in completion?() } alert.addAction(okButton) self.present(alert, animated: true, completion: nil) } // **receive Message** func receiveMessage() { connection?.receive(minimumIncompleteLength: 1, maximumLength: 1024) { [weak self] data, context, isComplete, error in if let data = data, !data.isEmpty { self?.message = String(data: data, encoding: .utf8) ?? "Received data, but cannot decode." DispatchQueue.main.async { self?.makeAlert(titleInput: "Message received", messageInput: "\(self?.message ?? "no message")") { if let messageToSend = self?.message { self?.sendMessage(messageToSend) } } } } if isComplete { self?.connection?.cancel() self?.connection = nil } else if let error = error { DispatchQueue.main.async { self?.statusLabel.text = "Receive error: \(error.localizedDescription)" } self?.connection?.cancel() self?.connection = nil } else { self?.receiveMessage() } } } // **send Message** func sendMessage(_ message: String) { guard let connection = connection else { print("Connection is not established") DispatchQueue.main.async { self.statusLabel.text = "Connection is not established" } return } guard let data = message.data(using: .utf8) else { print("Failed to encode message to data") DispatchQueue.main.async { self.statusLabel.text = "Failed to encode message to data" } return } print("Sending message: \(message)") connection.send(content: data, completion: .contentProcessed({ [weak self] error in if let error = error { print("Failed to send data: \(error.localizedDescription)") DispatchQueue.main.async { self?.statusLabel.text = "Send error: \(error.localizedDescription)" } return } print("Data sent successfully") DispatchQueue.main.async { self?.makeAlert(titleInput: "Sent successfully", messageInput: "Sending message") { } self?.statusLabel.text = "Data sent successfully" } })) } @objc func hideKeyboard(gestureRecognizer: UITapGestureRecognizer) { view.endEditing(true) } }
排查与修复方案
1. 补充TCP消息分隔符(核心修复点)
TCP是流式协议,服务端通常依赖特定分隔符(如换行符\n、固定长度前缀)识别完整消息。当前代码发送原始字符串,无分隔符时服务端可能无法判定消息结束,导致一直等待后续数据。
修改sendMessage方法,添加服务端预期的分隔符:
func sendMessage(_ message: String) { guard let connection = connection else { print("Connection is not established") DispatchQueue.main.async { self.statusLabel.text = "Connection is not established" } return } // 按服务端要求添加消息分隔符,示例为换行符 let messageWithDelimiter = message + "\n" guard let data = messageWithDelimiter.data(using: .utf8) else { print("Failed to encode message to data") DispatchQueue.main.async { self.statusLabel.text = "Failed to encode message to data" } return } print("Sending message: \(messageWithDelimiter)") connection.send(content: data, completion: .contentProcessed({ [weak self] error in if let error = error { print("Failed to send data: \(error.localizedDescription)") DispatchQueue.main.async { self?.statusLabel.text = "Send error: \(error.localizedDescription)" } return } print("Data sent successfully") DispatchQueue.main.async { self?.makeAlert(titleInput: "Sent successfully", messageInput: "Sending message") { } self?.statusLabel.text = "Data sent successfully" } })) }
2. 增加连接状态校验
确保发送消息时连接处于.ready状态,避免因连接状态异常导致的发送无效:
// 在sendMessage开头添加状态检查 guard connection.state == .ready else { print("Connection is not ready") DispatchQueue.main.async { self.statusLabel.text = "Connection is not ready" } return }
3. 抓包验证数据包是否发出
使用Wireshark等工具抓取iOS设备与服务端之间的TCP数据包,确认消息是否已实际发送到服务端:
- 若数据包已发出,问题出在服务端接收逻辑;
- 若数据包未发出,检查iOS端网络权限、防火墙或代理设置。
4. 检查服务端接收逻辑
确认服务端是否正确处理TCP流式数据:
- 服务端是否按约定的分隔符解析消息;
- 服务端接收缓冲区是否足够,是否存在数据堆积;
- 服务端是否正确处理异步接收事件。
内容的提问来源于stack exchange,提问作者Gurpreet Singh
相关产品推荐
相关产品推荐

