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

(本文代码已升级至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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • # 一度蜜v3.0协议 --- # 交互协议 [TOC] ## 协议说明 ### 请求参数 下表列出了v3.0版协...
    c5e350bc5b40阅读 641评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 大纲 一.Socket简介 二.BSD Socket编程准备 1.地址 2.端口 3.网络字节序 4.半相关与全相...
    VD2012阅读 2,299评论 0 5
  • //服务器端口 varport =6666 //客户端管理类 classChatUser:NSObject{ va...
    changeL阅读 368评论 0 0
  • /*初始化客户端,并连接服务器*/ func connectServer(addr: String,port: I...
    changeL阅读 1,356评论 1 0