美文网首页
ysocket - 使用socket进行通信(附聊天室样例)

ysocket - 使用socket进行通信(附聊天室样例)

作者: F麦子 | 来源:发表于2017-06-26 16:22 被阅读595次

    (本文代码已升级至Swift3)

    在Swift开发中,如果我们需要保持客服端和服务器的长连接进行双向的数据通信,使用socket是一种很好的解决方案。

    下面通过一个聊天室的样例来演示socket通信,这里我们使用了一个封装好的socket库(SwiftSocket)。

    SwiftSocket配置:

    将下载下来的ysocket文件夹拖进项目中即可。

    功能如下:

    1,程序包含服务端和客服端,这里为便于调试把服务端和客服端都做到一个应用中

    2,程序启动时,自动初始化启动服务端,并在后台开启一个线程等待客服端连接

    3,同时,客户端初始化完毕后会与服务端进行连接,同时也在后台开启一个线程等待接收服务端发送的消息

    4,连接成功后,自动生成一个随机的用户名(如“游客232”)并发送消息告诉服务器这个用户信息

    5,点击界面的“发送消息”按钮,则会发送聊天消息到服务端,服务端收到后会把聊天消息发给所有的客服端。客服端收到后显示在对话列表中

    注意1:消息传递过程使用的json格式字符串数据。

    目前这个demo里消息种类有用户登录消息,聊天消息,后面还可以加上用户退出消息等。为了在接收到消息以后,能判断是要执行什么命令。我们创建消息体的时候使用字典NSDictionary(其中cmd表示命令类型,content表示内容体,nickname表示用户名,等等),发送消息时把字典转成json格式的字符串传递,当另一端接收的时候,又把json串还原成字典再执行响应的动作。

    注意2:可变长度消息的发送

    由于我们发送的消息长度是不固定的。所以看下面代码可以发现,为了让接受方知道我们这条消息的长度。每次我们要发送一条消息。其实是发两个消息包出去。

    第1个包长度固定为4个字节,里边记录的是接下来的实际消息包的长度。

    第2个包才是实际的消息包,接受方通过第一个包知道了数据长度,从而进行读取。

    效果图如下:

    代码如下:

    --- ViewController.swift 主页面 ---

    importUIKit

    classViewController:UIViewController{

    //消息输入框

    @IBOutletweakvartextFiled:UITextField!

    //消息输出列表

    @IBOutletweakvartextView:UITextView!

    //socket服务端封装类对象

    varsocketServer:MyTcpSocketServer?

    //socket客户端类对象

    varsocketClient:TCPClient?

    overridefuncviewDidLoad() {

    super.viewDidLoad()

    //启动服务器

    socketServer =MyTcpSocketServer()

    socketServer?.start()

    //初始化客户端,并连接服务器

    processClientSocket()

    }

    //初始化客户端,并连接服务器

    funcprocessClientSocket(){

    socketClient=TCPClient(addr:"localhost", port: 8080)

    DispatchQueue.global(qos: .background).async {

    //用于读取并解析服务端发来的消息

    funcreadmsg()->[String:Any]?{

    //read 4 byte int as type

    ifletdata=self.socketClient!.read(4){

    ifdata.count==4{

    letndata=NSData(bytes: data, length: data.count)

    varlen:Int32=0

    ndata.getBytes(&len, length: data.count)

    ifletbuff=self.socketClient!.read(Int(len)){

    letmsgd =Data(bytes: buff, count: buff.count)

    letmsgi = (try!JSONSerialization.jsonObject(with: msgd,

    options: .mutableContainers))as! [String:Any]

    returnmsgi

    }

    }

    }

    returnnil

    }

    //连接服务器

    let(success,msg)=self.socketClient!.connect(timeout: 5)

    ifsuccess{

    DispatchQueue.main.async {

    self.alert(msg:"connect success", after: {

    })

    }

    //发送用户名给服务器(这里使用随机生成的)

    letmsgtosend=["cmd":"nickname","nickname":"游客\(Int(arc4random()%1000))"]

    self.sendMessage(msgtosend: msgtosend)

    //不断接收服务器发来的消息

    whiletrue{

    ifletmsg=readmsg(){

    DispatchQueue.main.async {

    self.processMessage(msg: msg)

    }

    }else{

    DispatchQueue.main.async {

    //self.disconnect()

    }

    break

    }

    }

    }else{

    DispatchQueue.main.async {

    self.alert(msg: msg,after: {

    })

    }

    }

    }

    }

    //“发送消息”按钮点击

    @IBActionfuncsendMsg(_ sender:AnyObject) {

    letcontent=textFiled.text!

    letmessage=["cmd":"msg","content":content]

    self.sendMessage(msgtosend: message)

    textFiled.text=nil

    }

    //发送消息

    funcsendMessage(msgtosend:[String:String]){

    letmsgdata=try?JSONSerialization.data(withJSONObject: msgtosend,

    options: .prettyPrinted)

    varlen:Int32=Int32(msgdata!.count)

    letdata =Data(bytes: &len, count: 4)

    _ =self.socketClient!.send(data: data)

    _ =self.socketClient!.send(data:msgdata!)

    }

    //处理服务器返回的消息

    funcprocessMessage(msg:[String:Any]){

    letcmd: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)

    }

    }

    //弹出消息框

    funcalert(msg:String,after:()->(Void)){

    letalertController =UIAlertController(title:"",

    message: msg,

    preferredStyle: .alert)

    self.present(alertController, animated:true, completion:nil)

    //1.5秒后自动消失

    DispatchQueue.main.asyncAfter(deadline:DispatchTime.now() + 1.5) {

    alertController.dismiss(animated:false, completion:nil)

    }

    }

    overridefuncdidReceiveMemoryWarning() {

    super.didReceiveMemoryWarning()

    }

    }

    --- MyTcpSocketServer.swift 服务端 ---

    importUIKit

    //服务器端口

    varserverport = 8080

    //客户端管理类(便于服务端管理所有连接的客户端)

    classChatUser:NSObject{

    vartcpClient:TCPClient?

    varusername:String=""

    varsocketServer:MyTcpSocketServer?

    //解析收到的消息

    funcreadMsg()->[String:Any]?{

    //read 4 byte int as type

    ifletdata=self.tcpClient!.read(4){

    ifdata.count==4{

    letndata=NSData(bytes: data, length: data.count)

    varlen:Int32=0

    ndata.getBytes(&len, length: data.count)

    ifletbuff=self.tcpClient!.read(Int(len)){

    letmsgd =Data(bytes: buff, count: buff.count)

    letmsgi = (try!JSONSerialization.jsonObject(with: msgd,

    options: .mutableContainers))as! [String:Any]

    returnmsgi

    }

    }

    }

    returnnil

    }

    //循环接收消息

    funcmessageloop(){

    whiletrue{

    ifletmsg=self.readMsg(){

    self.processMsg(msg: msg)

    }else{

    self.removeme()

    break

    }

    }

    }

    //处理收到的消息

    funcprocessMsg(msg:[String:Any]){

    ifmsg["cmd"]as!String=="nickname"{

    self.username=msg["nickname"]as!String

    }

    self.socketServer!.processUserMsg(user:self, msg: msg)

    }

    //发送消息

    funcsendMsg(msg:[String:Any]){

    letjsondata=try?JSONSerialization.data(withJSONObject: msg, options:

    JSONSerialization.WritingOptions.prettyPrinted)

    varlen:Int32=Int32(jsondata!.count)

    letdata =Data(bytes: &len, count: 4)

    _ =self.tcpClient!.send(data: data)

    _ =self.tcpClient!.send(data: jsondata!)

    }

    //移除该客户端

    funcremoveme(){

    self.socketServer!.removeUser(u:self)

    }

    //关闭连接

    funckill(){

    _ =self.tcpClient!.close()

    }

    }

    //服务端类

    classMyTcpSocketServer:NSObject{

    varclients:[ChatUser]=[]

    varserver:TCPServer=TCPServer(addr:"127.0.0.1", port: serverport)

    varserverRuning:Bool=false

    //启动服务

    funcstart() {

    _ = server.listen()

    self.serverRuning=true

    DispatchQueue.global(qos: .background).async {

    whileself.serverRuning{

    letclient=self.server.accept()

    ifletc=client{

    DispatchQueue.global(qos: .background).async {

    self.handleClient(c: c)

    }

    }

    }

    }

    self.log(msg:"server started...")

    }

    //停止服务

    funcstop() {

    self.serverRuning=false

    _ =self.server.close()

    //forth close all client socket

    forc:ChatUserinself.clients{

    c.kill()

    }

    self.log(msg:"server stoped...")

    }

    //处理连接的客户端

    funchandleClient(c:TCPClient){

    self.log(msg:"new client from:"+c.addr)

    letu=ChatUser()

    u.tcpClient=c

    clients.append(u)

    u.socketServer=self

    u.messageloop()

    }

    //处理各消息命令

    funcprocessUserMsg(user:ChatUser, msg:[String:Any]){

    self.log(msg:"\(user.username)[\(user.tcpClient!.addr)]cmd:"+(msg["cmd"]as!String))

    //boardcast message

    varmsgtosend=[String:String]()

    letcmd = msg["cmd"]as!String

    ifcmd=="nickname"{

    msgtosend["cmd"]="join"

    msgtosend["nickname"]=user.username

    msgtosend["addr"]=user.tcpClient!.addr

    }elseif(cmd=="msg"){

    msgtosend["cmd"]="msg"

    msgtosend["from"]=user.username

    msgtosend["content"]=(msg["content"]as!String)

    }elseif(cmd=="leave"){

    msgtosend["cmd"]="leave"

    msgtosend["nickname"]=user.username

    msgtosend["addr"]=user.tcpClient!.addr

    }

    foruser:ChatUserinself.clients{

    //if u~=user{

    user.sendMsg(msg: msgtosend)

    //}

    }

    }

    //移除用户

    funcremoveUser(u:ChatUser){

    self.log(msg:"remove user\(u.tcpClient!.addr)")

    ifletpossibleIndex=self.clients.index(of: u){

    self.clients.remove(at: possibleIndex)

    self.processUserMsg(user: u, msg: ["cmd":"leave"])

    }

    }

    //日志打印

    funclog(msg:String){

    print(msg)

    }

    }

    源码下载:

    hangge_756.zip

    相关文章

      网友评论

          本文标题:ysocket - 使用socket进行通信(附聊天室样例)

          本文链接:https://www.haomeiwen.com/subject/tpdjcxtx.html