直播项目笔记(三)

IM编程

整合工具栏

  • UITextField设置leftViewrightView
let sendBtn = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 32))
sendBtn.setTitleColor(UIColor.black, for: .normal)
sendBtn.setTitle("删除", for: .normal)
inputTextField.rightView = sendBtn
// 如果不加这个属性按钮不会显示
inputTextField.rightViewMode = .always
  • 使用闭包传值
// 定义一个闭包
var textCallback: ((String) -> Void)?

// 给闭包复制
textCallback("hello world")

// 调用闭包
// weak self 防止循环引用
textCallback = { [weak self] text in 
    print(text) // "hello world"
}
  • 监听键盘的高度变化
// MARK: 注册通知
override func viewDidLoad() {
  super.viewDidLoad()
  
  setupUI()
  NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}

// 注销通知
deinit {
   NotificationCenter.default.removeObserver(self)
}

// 处理方法
@objc fileprivate func keyboardWillChangeFrame(_ note : Notification) {
   let duration = note.userInfo![UIKeyboardAnimationDurationUserInfoKey] as! Double
   let endFrame = (note.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
   let inputViewY = endFrame.origin.y - kChatToolsViewHeight
   
   UIView.animate(withDuration: duration, animations: {
       UIView.setAnimationCurve(UIViewAnimationCurve(rawValue: 7)!)
       let endY = inputViewY == (kScreenH - kChatToolsViewHeight) ? kScreenH : inputViewY
       self.chatToolsView.frame.origin.y = endY
   })
}

// 发送通知
inputTextField.becomeFirstResponder()

IM编程

Socket 在实际项目中的应用

大多应用在实时通讯

  • 直播 进出直播间 聊天 送礼物
  • 微信聊天
  • 游戏

TCP/UDP

  • TCP:传输控制协议 。是专门设计用于在不可靠的因特网上提供可靠的,端到端的字节流通信的协议。它是一种面向连接的协议。TCP连接是字节流而非报文流。(TCP类似于打电话, 双方直接通信)
  • 用户数据报协议 。不需要建立连接,不可靠(UDP类似于发短信, 双方发出消息后等待别人的回复)

iOS中Socket编程

  • BSD Socket 是UNIX系统中通用的网络接口,它不仅支持各种不同的网络类型,而且也是一种内部进程之间的通信机制。在我们iOS中也可以使用,但是它所有的函数都是基于C语言的,所有在实际的项目开发中,我们都是使用封装好的
  • CFSocket是苹果官方提供给我们进行Socket编程,里面还是有很多C语言的东西,使用起来稍微麻烦一点。
  • AsyncSocket是一个开源的库,用来进行iOS的Socket编程就非常方便, 但是目前只有OC版本, 并且长时间没有更新
  • ysocket目前使用Swift进行Socket编程时,常用的一个库

消息传输

TCP在传输数据时,传输的是字节流

在读取消息时,需要知道数据的长度, 否则就会出现读取不完整或者读取长度过多的情况, 因此读取方法要求我们传入本次读取的消息长度

如何解决该问题呢?

  • 方案一: 客户端发送两次消息,消息一是记录后续消息长度, 消息二是真正的消息
  • 方案二: 客户端发送一次消息,消息有一个Header,用于记录消息的长度, 后续为真实消息内容

消息类型 ProtocolBuffer

  • ProtocolBuffer(也称PB/GPB): google 的一种数据交换的格式, 可以实现跨平台, 方便的序列化&反序列化, 并且数据量相对json小
  • ProtoBuf支持多平台和语言, 包括C++/Java/Python等等
  • ProtoBuf支持直接将对象序列化成Data, 也支持直接将Data序列化为对象类型
  • 一条消息数据,用protobuf序列化后的大小是json的10分之一,xml格式的20分之一,是二进制序列化的10分之一

ysocket 的使用(以TCP为例)

  • 服务器端
fileprivate lazy var serverSocket: TCPServer = TCPServer(address: "0.0.0.0", port: 7878)
fileprivate var isServerRunning : Bool = false

func startRunning() {
   // 1.开始监听
   serverSocket.listen()
   isServerRunning = true
   
   // 2.开启接受客户端 异步 否则阻塞主线程
   DispatchQueue.global().async {
       while self.isServerRunning {
           if let client = self.serverSocket.accept() {
               if let lMsg = client.read(4) {
                   // 1.读取长度的data
                   let headData = Data(bytes: lMsg, count: 4)
                   var length: Int = 0
                   (headData as NSData).getBytes(&length, length: 4)
                   
                   // 2.根据长度, 读取真实消息
                   guard let msg = client.read(length) else {
                       return
                   }
                   let data = Data(bytes: msg, count: length)
                   let str = String.init(data: data, encoding: String.Encoding.utf8)!
                   print(str)
               } else {
                   // isClientConnected = false
                   print("客户端断开了连接")
                   client.close()
               }
           }
       }
   }
   
}
    
func stopRunning() {
   isServerRunning = false
}
  • 客户端
fileprivate lazy var clientSocket: TCPClient = TCPClient(address: "0.0.0.0", port: 7878)

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
   switch clientSocket.connect(timeout: 5) {
   case .success:
       let str = "Hello World"
       let data = str.data(using: String.Encoding.utf8)!
       // 1.将消息长度, 写入到data
       var length = data.count
       let headerData = Data(bytes: &length, count: 4)
       // 2.发送消息
       let totalData = headerData + data
       switch clientSocket.send(data: totalData) {
       case .success:
           print("发送成功")
       case .failure(let error):
           print(error)
       }
   case .failure(let error):
       print(error)
   }
}

ProtocolBuffer使用

  • 环境安装
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install automake
brew install libtool
brew install protobuf
brew install protobuf-swift
  • 客户端集成(通过cocoapods)
use_frameworks!
pod 'ProtocolBuffers-Swift'
  • 服务器集成
因为服务器使用Mac编写,不能直接使用cocoapods集成
因为需要将工程编译为静态库来集成
    到Git中下载整个库
    执行脚本: ./scripts/build.sh
    添加: ./src/ProtocolBuffers/  ProtocolBuffers.xcodeproj到项目中

ProtocolBuffer的使用

  • 创建.proto文件
在项目中, 创建一个(或多个).proto文件
之后会通过该文件, 自动帮我们生成需要的源文件(比如C++生成.cpp源文件, 比如java生成.java源文件, Swift就生成.swift源文件)
  • 源码规范
syntax = "proto2";

message Person {
    required int64 id = 1;
    required string name = 2;
    optional string email = 3;
}
  • 具体说明
syntax = "proto2"; 为定义使用的版本号, 目前常用版本proto2/proto3
message是消息定义的关键字,等同于C++/Swift中的struct/class,或是Java中的class
Person为消息的名字,等同于结构体名或类名
required前缀表示该字段为必要字段,既在序列化和反序列化之前该字段必须已经被赋值
optional前缀表示该字段为可选字段, 既在序列化和反序列化时可以没有被赋值
repeated通常被用在数组字段中
int64和string分别表示整型和字符串型的消息字段
id和name和email分别表示消息字段名,等同于Swift或是C++中的成员变量名
标签数字1和2则表示不同的字段在序列化后的二进制数据中的布局位置, 需要注意的是该值在同一message中不能重复
  • 定义有枚举类型Protocol Buffer消息
enum UserStatus {
    OFFLINE = 0;  //表示处于离线状态的用户
    ONLINE = 1;   //表示处于在线状态的用户
}

message UserInfo {
    required int64 acctID = 1;
    required string name = 2;
    required UserStatus status = 3;
}
  • 定义有类型嵌套
enum UserStatus {
    OFFLINE = 0;
    ONLINE = 1;
}
message UserInfo {
    required int64 acctID = 1;
    required string name = 2;
    required UserStatus status = 3;
}

message LogonRespMessage {
    required LoginResult logonResult = 1;
    required UserInfo userInfo = 2;
}
  • 代码编写完成后, 生成对应语言代码
protoc person.proto --swift_out="./"
  • 序列化
// 1.创建UserInfo类型
let user = UserInfo.Builder()
user.acctID = Int64(1)
user.name = "Bob"
user.status = .ONLINE
// 2.获取对应的data
let userData = (try! user.build()).data()
  • 反序列化
let user = try! UserInfo.parseFrom(data: data)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容