ysocket - 使用socket進行通信(附聊天室樣例)

(本文代碼已升級至Swift3)

在Swift開發(fā)中,如果我們需要保持客服端和服務器的長連接進行雙向的數(shù)據(jù)通信寄锐,使用socket是一種很好的解決方案。

下面通過一個聊天室的樣例來演示socket通信尖啡,這里我們使用了一個封裝好的socket庫(SwiftSocket)。

SwiftSocket配置:

將下載下來的ysocket文件夾拖進項目中即可剩膘。

功能如下:

1衅斩,程序包含服務端和客服端,這里為便于調(diào)試把服務端和客服端都做到一個應用中

2怠褐,程序啟動時畏梆,自動初始化啟動服務端,并在后臺開啟一個線程等待客服端連接

3奈懒,同時奠涌,客戶端初始化完畢后會與服務端進行連接,同時也在后臺開啟一個線程等待接收服務端發(fā)送的消息

4磷杏,連接成功后溜畅,自動生成一個隨機的用戶名(如“游客232”)并發(fā)送消息告訴服務器這個用戶信息

5,點擊界面的“發(fā)送消息”按鈕极祸,則會發(fā)送聊天消息到服務端慈格,服務端收到后會把聊天消息發(fā)給所有的客服端怠晴。客服端收到后顯示在對話列表中

注意1:消息傳遞過程使用的json格式字符串數(shù)據(jù)浴捆。

目前這個demo里消息種類有用戶登錄消息蒜田,聊天消息,后面還可以加上用戶退出消息等选泻。為了在接收到消息以后冲粤,能判斷是要執(zhí)行什么命令。我們創(chuàng)建消息體的時候使用字典NSDictionary(其中cmd表示命令類型页眯,content表示內(nèi)容體色解,nickname表示用戶名,等等)餐茵,發(fā)送消息時把字典轉(zhuǎn)成json格式的字符串傳遞科阎,當另一端接收的時候,又把json串還原成字典再執(zhí)行響應的動作忿族。

注意2:可變長度消息的發(fā)送

由于我們發(fā)送的消息長度是不固定的锣笨。所以看下面代碼可以發(fā)現(xiàn),為了讓接受方知道我們這條消息的長度道批。每次我們要發(fā)送一條消息错英。其實是發(fā)兩個消息包出去。

第1個包長度固定為4個字節(jié)隆豹,里邊記錄的是接下來的實際消息包的長度椭岩。

第2個包才是實際的消息包,接受方通過第一個包知道了數(shù)據(jù)長度璃赡,從而進行讀取判哥。

效果圖如下:

代碼如下:

--- 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 {

//用于讀取并解析服務端發(fā)來的消息

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: {

})

}

//發(fā)送用戶名給服務器(這里使用隨機生成的)

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

self.sendMessage(msgtosend: msgtosend)

//不斷接收服務器發(fā)來的消息

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: {

})

}

}

}

}

//“發(fā)送消息”按鈕點擊

@IBActionfuncsendMsg(_ sender:AnyObject) {

letcontent=textFiled.text!

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

self.sendMessage(msgtosend: message)

textFiled.text=nil

}

//發(fā)送消息

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

}

//循環(huán)接收消息

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)

}

//發(fā)送消息

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

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末塌计,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子侯谁,更是在濱河造成了極大的恐慌锌仅,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墙贱,死亡現(xiàn)場離奇詭異热芹,居然都是意外死亡,警方通過查閱死者的電腦和手機惨撇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門伊脓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人串纺,你說我怎么就攤上這事丽旅∫” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵榄笙,是天一觀的道長邪狞。 經(jīng)常有香客問我,道長茅撞,這世上最難降的妖魔是什么帆卓? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮米丘,結(jié)果婚禮上剑令,老公的妹妹穿的比我還像新娘。我一直安慰自己拄查,他們只是感情好吁津,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著堕扶,像睡著了一般碍脏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上稍算,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天典尾,我揣著相機與錄音,去河邊找鬼糊探。 笑死钾埂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的科平。 我是一名探鬼主播褥紫,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼匠抗!你這毒婦竟也來了故源?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤汞贸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后印机,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矢腻,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年射赛,在試婚紗的時候發(fā)現(xiàn)自己被綠了多柑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡楣责,死狀恐怖竣灌,靈堂內(nèi)的尸體忽然破棺而出聂沙,到底是詐尸還是另有隱情,我是刑警寧澤初嘹,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布及汉,位于F島的核電站,受9級特大地震影響屯烦,放射性物質(zhì)發(fā)生泄漏坷随。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一驻龟、第九天 我趴在偏房一處隱蔽的房頂上張望温眉。 院中可真熱鬧,春花似錦翁狐、人聲如沸类溢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闯冷。三九已至,卻和暖如春隐锭,著一層夾襖步出監(jiān)牢的瞬間窃躲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工钦睡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蒂窒,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓荞怒,卻偏偏與公主長得像洒琢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子褐桌,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • # 一度蜜v3.0協(xié)議 --- # 交互協(xié)議 [TOC] ## 協(xié)議說明 ### 請求參數(shù) 下表列出了v3.0版協(xié)...
    c5e350bc5b40閱讀 640評論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理衰抑,服務發(fā)現(xiàn),斷路器荧嵌,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 大綱 一.Socket簡介 二.BSD Socket編程準備 1.地址 2.端口 3.網(wǎng)絡字節(jié)序 4.半相關與全相...
    VD2012閱讀 2,276評論 0 5
  • //服務器端口 varport =6666 //客戶端管理類 classChatUser:NSObject{ va...
    changeL閱讀 365評論 0 0
  • /*初始化客戶端呛踊,并連接服務器*/ func connectServer(addr: String,port: I...
    changeL閱讀 1,345評論 1 0