Swift中socket不定长消息发送接收的方法总结

使用 socket 可以很方便地实现客服端和服务器的长连接,并进行双向的数据通信。但有时我们发送的数据包长度并不是固定的(比如做一个聊天系统),通常的做法是在数据包前面添加个包头信息,让接收方知道整个发送包的长度。也就是说接收方先收这个固定长度的包头信息,然后再根据包头信息里面定义的实际长度来接收包数据。
下面通过一个聊天室程序演示 socket 发送/读取不定长消息包的几种方法。socket 通信这里我们使用了一个封装好的 socket 库(SwiftSocket)。

几种方法 (以聊天室为例)

方法1:每条消息发送两个包
第1个包长度固定为4个字节,里边记录的是后面发送的实际消息包的长度。
第2个包才是实际的消息包。
接收方先接收第一个固定长度的数据包,通过第一个包知道了数据长度,从而进行读取实际的消息包。这种方式我原来写过相关的文章,地址是:Swift - 使用socket进行通信(附聊天室样例)

(1)发送数据相关代码如下:

//发送消息
func sendMessage(msgtosend:NSDictionary){
    let msgdata=try? NSJSONSerialization.dataWithJSONObject(msgtosend,
                                                            options: .PrettyPrinted)
    var len:Int32=Int32(msgdata!.length)
    let data:NSMutableData=NSMutableData(bytes: &len, length: 4)
    self.socketClient!.send(data: data)
    self.socketClient!.send(data:msgdata!)
}

(2)接收数据相关代码如下:

//解析收到的消息
func readMsg()->NSDictionary?{
    //read 4 byte int as type
    if let data=self.tcpClient!.read(4){
        if data.count==4{
            let ndata=NSData(bytes: data, length: data.count)
            var len:Int32=0
            ndata.getBytes(&len, length: data.count)
            if let buff=self.tcpClient!.read(Int(len)){
                let msgd:NSData=NSData(bytes: buff, length: buff.count)
                let msgi:NSDictionary=(try!
                    NSJSONSerialization.JSONObjectWithData(msgd,
                        options: .MutableContainers)) as! NSDictionary
                return msgi
            }
        }
    }
    return nil
}

方法2:每条消息只发送1个包。不过这个包增加了包头信息。
这个其实是上面方式的整合。发送方将消息长度做为包头信息(比如固定是4个字节大小)添加到消息包前面。
接收方先读取前4个字节的内容,再根据获取到的长度信息读取剩余的消息。

(1)客户端这边(ViewController.swift)发送消息的时候将包头拼在消息包前面,这样每次只发一个数据包。(接收数据方式不变)
1

//发送消息
func sendMessage(msgtosend:NSDictionary){
    //消息数据包
    let msgdata = try? NSJSONSerialization.dataWithJSONObject(msgtosend,
                                                            options: .PrettyPrinted)
    //消息数据包大小
    var len:Int32=Int32(msgdata!.length)
    //消息包头数据
    let data:NSMutableData=NSMutableData(bytes: &len, length: 4)
    //包头后面添加正真的消息数据包
    data.appendData(msgdata!)
    //发送合并后的数据包
    self.socketClient!.send(data: data)
}

(2)服务端(MyTcpSocketServer.swift)同样也是发送一个包含包头信息的数据包。(接收数据方式不变)

//发送消息
func sendMsg(msg:NSDictionary){
    //消息数据包
    let jsondata=try? NSJSONSerialization.dataWithJSONObject(msg, options:
        NSJSONWritingOptions.PrettyPrinted)
    //消息数据包大小
    var len:Int32=Int32(jsondata!.length)
    //消息包头数据
    let data:NSMutableData=NSMutableData(bytes: &len, length: 4)
    //包头后面添加正真的消息数据包
    data.appendData(jsondata!)
    //发送合并后的数据包
    self.tcpClient!.send(data: data)
}

 

方法3:每条消息只发送1个包。同时不增加包头信息。
这种方式发送方直接将消息包发送出去,不再额外添加任何包头信息。
接受方由于不知道消息的实际长度,便需要进行循环读取。每次读固定字节数(比如1024个字节),如果某次读取到的字节数小于1024个字节,则表示读取完毕。

(1)首先修改 ytcpsocket.swift,给 TCPClient 类添加 readAll() 方法,用来实现自动循环读取全部的消息,而不用指定预期的数据长度。

/*
 * 不指定长度来读取数据
 */
public func readAll(timeout:Int = -1)->[UInt8]?{
    //内容缓冲区
    var data:[UInt8] = [UInt8]()
    repeat{
        if let tempData = read(1024, timeout: timeout) {
            //将每次读取到的数据添加到缓冲区
            data = data + tempData
            //读取结束
            if tempData.count < 1024 {
                break
            }
        }else{
            break
        }
    }while true
    return data
}

(2)客户端管理类 ChatUser(MyTcpSocketServer.swift)修改
接收数据的方式修改了,同时发送数据前不再需要提供消息长度,直接发消息包即可。

//客户端管理类(便于服务端管理所有连接的客户端)
class ChatUser:NSObject{
    var tcpClient:TCPClient?
    var username:String=""
    var socketServer:MyTcpSocketServer?
    
    //解析收到的消息
    func readMsg()->NSDictionary?{
        if let buff=self.tcpClient!.readAll(){
            let msgd:NSData=NSData(bytes: buff, length: buff.count)
            let msgi:NSDictionary=(try!
                NSJSONSerialization.JSONObjectWithData(msgd,
                options: .MutableContainers)) as! NSDictionary
            return msgi
        }
        return nil
    }
    
    //循环接收消息
    func messageloop(){
        while true{
            if let msg=self.readMsg(){
                self.processMsg(msg)
            }else{
                self.removeme()
                break
            }
        }
    }
    
    //处理收到的消息
    func processMsg(msg:NSDictionary){
        if msg["cmd"] as! String=="nickname"{
            self.username=msg["nickname"] as! String
        }
        self.socketServer!.processUserMsg(user: self, msg: msg)
    }
    
    //发送消息
    func sendMsg(msg:NSDictionary){
        let jsondata=try? NSJSONSerialization.dataWithJSONObject(msg, options:
            NSJSONWritingOptions.PrettyPrinted)
        self.tcpClient!.send(data: jsondata!)
    }
    
    //移除该客户端
    func removeme(){
        self.socketServer!.removeUser(self)
    }
    
    //关闭连接
    func kill(){
        self.tcpClient!.close()
    }
}

(3)同样的,客户端代码(ViewController.swift)这边发送与接收数据也可以简化。

import UIKit
 
class ViewController: UIViewController {
    
    //消息输入框
    @IBOutlet weak var textFiled: UITextField!
    //消息输出列表
    @IBOutlet weak var textView: UITextView!
    
    //socket服务端封装类对象
    var socketServer:MyTcpSocketServer?
    //socket客户端类对象
    var socketClient:TCPClient?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //启动服务器
        socketServer = MyTcpSocketServer()
        socketServer?.start()
        
        //初始化客户端,并连接服务器
        processClientSocket()
    }
    
    //初始化客户端,并连接服务器
    func processClientSocket(){
        socketClient=TCPClient(addr: "localhost", port: 8080)
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
            () -> Void in
            
            //用于读取并解析服务端发来的消息
            func readmsg()->NSDictionary?{
                if let buff=self.socketClient!.readAll(){
                    let msgd:NSData=NSData(bytes: buff, length: buff.count)
                    let msgi:NSDictionary =
                        (try! NSJSONSerialization.JSONObjectWithData(msgd,
                        options: .MutableContainers)) as! NSDictionary
                    return msgi
                }
                return nil
            }
            
            //连接服务器
            let (success,msg)=self.socketClient!.connect(timeout: 5)
            if success{
                dispatch_async(dispatch_get_main_queue(), {
                    self.alert("connect success", after: {
                    })
                })
                
                //发送用户名给服务器(这里使用随机生成的)
                let msgtosend=["cmd":"nickname","nickname":"游客\(Int(arc4random()%1000))"]
                self.sendMessage(msgtosend)
                
                //不断接收服务器发来的消息
                while true{
                    if let msg=readmsg(){
                        dispatch_async(dispatch_get_main_queue(), {
                            self.processMessage(msg)
                        })
                    }else{
                        dispatch_async(dispatch_get_main_queue(), {
                            //self.disconnect()
                        })
                        break
                    }
                }
            }else{
                dispatch_async(dispatch_get_main_queue(), {
                    self.alert(msg,after: {
                    })
                })
            }
        })
    }
    
    //“发送消息”按钮点击
    @IBAction func sendMsg(sender: AnyObject) {
        let content=textFiled.text!
        let message=["cmd":"msg","content":content]
        self.sendMessage(message)
        textFiled.text=nil
    }
    
    //发送消息
    func sendMessage(msgtosend:NSDictionary){
        let msgdata=try? NSJSONSerialization.dataWithJSONObject(msgtosend,
                                                                options: .PrettyPrinted)
        self.socketClient!.send(data:msgdata!)
    }
    
    //处理服务器返回的消息
    func processMessage(msg:NSDictionary){
        let cmd:String=msg["cmd"] as! String
        switch(cmd){
        case "msg":
            self.textView.text = self.textView.text +
                (msg["from"] as! String) + ": " + (msg["content"] as! String) + "\n"
        default:
            print(msg)
        }
    }
    
    //弹出消息框
    func alert(msg:String,after:()->(Void)){
        let alertController = UIAlertController(title: "",
                                                message: msg,
                                                preferredStyle: .Alert)
        self.presentViewController(alertController, animated: true, completion: nil)
 
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 7), dispatch_get_main_queue(),{
           alertController.dismissViewControllerAnimated(false, completion: nil)
        })
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

时间: 2024-09-20 00:47:48

Swift中socket不定长消息发送接收的方法总结的相关文章

socket-VBS中Socket通过TCP协议发送数据

问题描述 VBS中Socket通过TCP协议发送数据 问题是一个接着一个: 在使用MSWINsock.Winsock通过TCP发送数据到Server的时候,提示如下错误: 代码如下: Dim sock,socket '赋值sock变量为WINSOCK的对象 Set sock = Wscript.createobject("MSWINsock.Winsock") sock.Protocol = 0 sock.RemoteHost = "10.185.59.51" so

Android中使用socket通信实现消息推送的方法详解_Android

原理最近用socket写了一个消息推送的demo,在这里和大家分享一下. 主要实现了:一台手机向另外一台手机发送消息,这两台手机可以随时自由发送文本消息进行通信,类似我们常用的QQ. 效果图: 原理:手机通过socket发送消息到服务器,服务器每接收到一条消息之后,都会把这条消息放进一个messageList里面,服务器会不停地检测messageList是否含有消息,如果有的话就会根据messageList里面item的数据,推送到相应的另一端手机上面. 下面简单画了一个图来说明这个原理: 演示

Android中使用socket通信实现消息推送的方法详解

原理 最近用socket写了一个消息推送的demo,在这里和大家分享一下. 主要实现了:一台手机向另外一台手机发送消息,这两台手机可以随时自由发送文本消息进行通信,类似我们常用的QQ. 效果图: 原理:手机通过socket发送消息到服务器,服务器每接收到一条消息之后,都会把这条消息放进一个messageList里面,服务器会不停地检测messageList是否含有消息,如果有的话就会根据messageList里面item的数据,推送到相应的另一端手机上面. 下面简单画了一个图来说明这个原理: 演

php中GET和POST请求发送几种方法总结

无论是畅言还是多说,我都需要从远程抓取文章的评论数,然后存入本地数据库.对于多说,请求的格式如下:  代码如下 复制代码 // 获取评论次数,参数是文章ID function getCommCount($postid) {  $jsondata = file_get_contents("http://api.duoshuo.com/threads/counts.json?short_name=i94web&threads=$postid");  // 设置true返回数组,不设

关于socket通信,多线程发送消息

问题描述 关于socket通信,多线程发送消息 最近在多线程中使用socket通信遇到的问题,场景是有多个线程需要用到同一个socket连接进行消息的发送,因为没有订具体的协议,所以对于发送(文本)消息,每次发送端发送1K字节,接收端每次接收1K字节,无效数据用0填充,正常情况下来说都没什么问题,但当接收方接收不过来时,由于发送方设置了发送超时(30ms),send会发出EWOULDBLOCK,这下问题来了,推消息设计时设置超时就是希望对方不收就扔掉, 那么如果收到EWOULDBLOCK 就返回

socket-Java中Socket如何发送心跳告诉服务器还活着

问题描述 Java中Socket如何发送心跳告诉服务器还活着 客户端发送的是Object(A类的实例a),如果还要发送心跳,那么心跳用什么发(字符串,对象)? 服务器端怎么分辨是有用的对象还是心跳? 解决方案 心跳检测就是消息测试用的,可以约定某个字符串为心跳检测专用字符串啊. 协商好,再处理请求数据时区分下就可以了. 解决方案二: 这个就是一个空包就可以了的,和服务器端协商好,用什么样的包头,什么样的命令码,固定就可以了

byte-C#中socket通信需要将数据按包头和定长分割,如何做

问题描述 C#中socket通信需要将数据按包头和定长分割,如何做 包头是0x55,每个数据加上包头为11个byte,但是数据中也可能出现0x55,该如何分割 解决方案 一般的解决方法是两种: 1)定义转义符,对内容中的 0x55 进行转义.如将 0x55 转义成 0x54 01;则 0x54 是 0x54 00:用两个字节表示: 2)定义包头后带数据的长度,再加上包尾,最好再加上一个校验位.这样在接收到一包数据后,先按上述定义进行有效性验证. 解决方案二: 包头后面带上长度信息,按长度去截取啊

微信企业号验证/发送/接收消息_java

1. 内网映射 由于微信企业号回调模式的URL尽支持域名方式访问,估需要注册花生壳,做一个内网穿透(需要花16块钱,购买一个免费版,购买之后,第二天才能添加上域名) 2. 微信企业号 注册微信企业号:https://qy.weixin.qq.com/ (选择团队,团队不需要认证) 通讯录:新建组织 - > 关注成员 企业号 -> 应用中心 -> 新建应用 -> 消息型应用 -> 模式选择(回调模式) -> 开启微信消息转发, 回调模式说明:http://qydev.we

atagram ocket-java udp DatagramSocket 发送接收消息

问题描述 java udp DatagramSocket 发送接收消息 使用如下代码发送并接收消息: SocketClient client = new SocketClient(); String serverHost = ""127.0.0.1""; int serverPort = 3333; client.send(serverHost serverPort (""你好,阿蜜果!"").getBytes()); Str