iOS中socket的实战训练

前言

我是培训班出身的,我至今还记得老师关于socket的一句话:http是短连接,socket是长连接。我估计是老师对我们这群菜鸟不报什么希望,所以才这么说的,而我直到前一阵子还一直当真理相信着。。。

最近工作上接触了socket,看了很多文档,渐渐的对socket有了一个清晰的了解,下面附上2个比较好的连接:

在这个充斥着互联网的世界,单机的APP已经渐渐销声匿迹,网络编程成为了一个程序员的基本素养。在此,我推荐2本我准备要看的书给和我一样非科班出身的程序猿:《计算机网络-自顶向下方法》,还有就是《TCP-IP详解》的3卷。这2本有先后顺序,先读第一本,在理解第二本会好很多。书单链接:

与君共勉

socket框架

我用的是GCDAsyncSocket,毕竟对c的api一脸懵逼的,所以找一个成名的、封装好、面向对象的socket框架,GCDAsyncSocket的用法我就不多说了,自己可以百度。

image.png

大家可以去https://github.com/robbiehanson/CocoaAsyncSocket下载,当然,也可以下我的demo直接拿。注意到图片里的udp了么,我们用的是tcp协议的,后面可以带大家看3次握手和4次分手的过程。

socket客户端

image.png

首先在storyboard里拖个小界面,在ViewController里关联下,接着声明一个GCDAsyncSocket的对象,如果不持有属性的话,对象释放的时候会自动断开连接。

@property (nonatomic, strong) GCDAsyncSocket *socket;

创建socket,这边GCDAsyncSocket用法详解就不说了,自己百度。

_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

给button加个点击事件,里面有连接服务器,和发送数据事件的切换。值得注意的是,每个发送的消息必须要有特定字符分隔开,不然后台无法识别数据是否已经发送完成,常用的是换行符。[GCDAsyncSocket CRLFData]框架里已经封装给我们了。所以每次发送的数据都要拼接上[GCDAsyncSocket CRLFData]

 if (self.button.tag == SendType_Connent) {
        NSArray *arr = [self.textField.text componentsSeparatedByString:@":"];
        NSError *error;
        [self.socket connectToHost:arr.firstObject onPort:[arr.lastObject intValue] withTimeout:15 error:&error];
        if (error) {
            NSLog(@"%@, %d", error, __LINE__);
        }
    }else {
        
        NSMutableData *data = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding].mutableCopy;
        [data appendData: [GCDAsyncSocket CRLFData]];
      
        [self.socket writeData:data withTimeout:30 tag:0];
    }

接着你可以把GCDAsyncSocketDelegate中的所有代理都拷贝过来,方便自己学习,你可以在所有代理方法中加上这句,这样就很方便就看到那些代理方法调用了。

NSLog(@"%s,%d", __func__, __LINE__);

下面是代理方法的书写

首先是连上服务器的回调,连上的时候把button的事件改成发送,然后监听服务器的数据。

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
    NSLog(@"%s,%d", __func__, __LINE__);
    self.button.tag = SendType_SendMessage;
    [self.button setTitle:@"发送" forState:UIControlStateNormal];  
    [self.socket readDataWithTimeout:-1 tag:0];
}

然后是数据监听的回调,将NSData转成NSString,并在控制台打印

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    NSLog(@"%s,%d", __func__, __LINE__);
    NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    [self.socket readDataWithTimeout:-1 tag:0];
}

最后是断开连接的回调,把button调回连接状态。

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
    NSLog(@"%s,%d", __func__, __LINE__);
    self.button.tag = SendType_Connent;
    [self.button setTitle:@"连接" forState:UIControlStateNormal];
}

一个简单demo就完成了,写完了当然要测试,在终端用netcat工具实现简单的服务器聊天功能,命令是nc -lk 端口

image.png

当光标移到下面去的时候,表示服务器已经开始监听啦。运行demo,如果是模拟器,那么ip写上127.0.0.1的回环地址,如果是真机的话,写上电脑的ip地址就行。端口的话就和服务器监听的一致就行。输入ip后,点击连接,如果成功的话,控制台会打印成功的回调。

image.png

接着在输入框里可以输入内容聊天了,比如我输入一个hello
image.png

控制台便会跳出来一个hello,控制台也会打印didWriteDataWithTag的回调。如果你在终端输入内容(回车键发送),那么你也会收到信息
image.png

你关掉终端或者按ctrl+c便能关掉服务端,会收到socketDidDisconnect的回调。

客户端的小demo就完成啦。

socket服务端

手机当服务器,有没有觉得很有成就感?

服务端的demo很多内容和上面一样,具体可以看demo中的内容,着重说下不同点。

首先是socket的创建,这个socket对象只负责端口的监听,并不负责data的传送。一旦这个socket断开了连接,其他客户端就再也连不上这台服务器了。

self.serverSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    //监听某个端口 等待被链接 0-25535  1000以内是系统预留端口
 [self.serverSocket acceptOnPort:5555 error:nil];

一旦有客户端有连接的话,便会回调这个方法:

- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{
    NSLog(@"%s,%d", __func__, __LINE__);
    //把链接进来的socket对象 持有住不被自动释放 释放掉的话 链接会自动断开
    [self.sockets addObject:newSocket];
    [newSocket readDataWithTimeout:-1 tag:0];
}

这里会有一个newSocket的参数,这个newSocket对象表示是与当前客户端的连接,作用是和当前客户端相互传送data的,self.serverSocket的代理对象会赋值给这个newSocket,所以newSocket也会走你写的代理方法。当然,可能会有好几个客户端接进来,所以你需要用一个数组来管理,创建数组我就不展示了。

所以走didReadData回调的是你sockets数组中的某一个,并不是一开始创建的self.serverSocket。我为了识别是哪一个客户端给我发的消失,创建了一个对象指向它:

@property (nonatomic, weak)GCDAsyncSocket *currentSocket;

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    NSLog(@"%s,%d", __func__, __LINE__);
    NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    self.currentSocket = sock;
    [sock readDataWithTimeout:-1 tag:0];
}

当有消息进来的时候,我指向那个客户端,方便给他回消息(暂时不考虑并发的情况,只是demo嘛)

客户端断开连接的时候,socket记得从数组中移除。

接下来就是测试啦,先运行demo,然后用终端当客户端,命令是nc 127.0.0.1 5555,host和port可以根据自己的实际情况自己改,你可以多开几个终端,同时连上服务器。

image.png

image.png

我连了3个,就有3次回调,数组也有3个。data传送你们自己试吧。

TCP的3次握手和4次分手

我们平时用的抓包工具是Charles,但这个一般用来抓http协议请求的,他帮我们做了很多处理,所以很多细节都看不到,对于socket来说,这工具就不够看了。推荐一个新工具--Wireshark。


image.png

工具的使用自己百度搜吧,我也用的很生疏。

接下来我模拟器运行服务端,真机运行客户端,模拟器ip是10.10.2.47,真机ip是10.10.2.50


image.png

当我点击连接的时候出现了4条数据,前面3条是不是很熟悉,就是一直念在口中的3次握手,第4条数据是滑动窗口的概念。


image.png

然后我客户端发送了2条后,服务器也发送了1条。由图可以得到每条消息要2个数据包,来回各一次。


image.png

最后断开连接,是不是很完美的4次分手?

结语

附上代码地址:https://github.com/harryphone/SocketDemo

用Wireshark可以分解出一次完整的http请求,下次有机会用socket封装出一个http请求,甚至是https请求。

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

推荐阅读更多精彩内容

  • 前言 本文会用实例的方式,将iOS各种IM的方案都简单的实现一遍。并且提供一些选型、实现细节以及优化的建议。 注:...
    maTianHong阅读 2,368评论 4 12
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 15,979评论 3 119
  • 转载:http://www.cocoachina.com/ios/20170615/19529.html 参考:h...
    F麦子阅读 4,007评论 3 2
  • 工具使用用素描本和彩铅、碳素笔。中间有一幅画使用勾线笔勾线时在本上晕开。 如果不逼自己一下,永远也不会下笔去画。
    西柚hy阅读 301评论 1 1
  • 早起《大学》一遍,艾灸。抡胳膊。 早课感受:今天听的早课是节气养生课。 受到的启发:1养生养在根本。养生可以说很多...
    悦2017137阅读 225评论 0 3