【iOS】Socket通信

Socket是网络传输层的一种技术,跟http有本质的区别,http是应用层的一个网络协议。
Socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 Socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。
Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。

http和Socket的区别:

  • Socket是长连接。http是短连接
  • Socket是双向通信,http是单向的,只能客户端向服务器发送数据
  • Socket的数据完全由自己组织,http必须按照http协议来发送

下载CocoaAsyncSocket

点击前往github下载地址

GCDAsyncSocket 网络请求使用

我们使用Socket时,可以选择tcp或者udp,所以下面以tcp为例

ViewController.h

#import <UIKit/UIKit.h>

enum
{
    SOCKET_CONNECT_USER, //用户断开连接
    SOCKET_CONNECT_SERVER, //服务器连接失败
};

@interface ViewController : UIViewController

@end

ViewController.m

#import "ViewController.h"
/*导入头文件*/
#import "GCDAsyncSocket.h"

#define BaseHost @"wangys.com"       //host地址
#define BasePort 80                  //端口
#define BaseHeartStr @"wys"          //心跳数据
#define BaseSocktConnectStr @"request_data" //请求链接数据

/* 记住要设置代理 */
@interface ViewController ()<GCDAsyncSocketDelegate>
@property(nonatomic,strong)GCDAsyncSocket * mysocket;
@property(nonatomic,strong)NSTimer * myTimer;
@property (weak, nonatomic) IBOutlet UIButton * sendDataBtn;
@property (weak, nonatomic) IBOutlet UITextField * DataStrText;
@property (weak, nonatomic) IBOutlet UITextField * receiveText;
@end

@implementation ViewController

-(void)viewDidLoad
{
    [super viewDidLoad];
}

#pragma mark - creat连接
//创建连接
-(void)createSocket
{
    if (self.mysocket != nil) return;
    
    self.mysocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    
    [self tcpConnectWithData:BaseSocktConnectStr];
}

-(void)tcpConnectWithData:(NSString *)userData
{
    [self.mysocket setUserData:userData];
    
    NSError * error = nil;
    
    if (![self.mysocket connectToHost:BaseHost onPort:BasePort withTimeout:2.0f error:&error])
    {
        NSLog(@"error:%@",error);
    }
}

#pragma mark - 发送心跳包
//定时发送心跳包
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
    self.myTimer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:self selector:@selector(sendHeartData) userInfo:nil repeats:YES];
    
    [self.myTimer fire]; //先执行一次
}

-(void)sendHeartData
{
    NSString * heartStr = BaseHeartStr;
    
    NSData * hData = [heartStr dataUsingEncoding:NSUTF8StringEncoding];
    
    [self.mysocket writeData:hData withTimeout:10.0f tag:0];
}

#pragma mark - 向服务器发送数据
//发送数据
-(IBAction)sendData:(UIButton *)sender
{
    NSString * dataStr = self.DataStrText.text;
    
    NSData * dataForTesxt = [dataStr dataUsingEncoding:NSUTF8StringEncoding];
    
    [self.mysocket writeData:dataForTesxt withTimeout:10.0f tag:1];
}

//向服务器发送完数据之后回调
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
    if (tag == 1)
    {
        NSLog(@"发送成功");
    }
}

#pragma mark - 服务器向客户端发送数据
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    [self.mysocket readDataWithTimeout:10.0f tag:tag];
    
    [self receiveData:data];
}

-(void)receiveData:(NSData *)data
{
    //显示
    self.receiveText.text = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
}

#pragma mark - 断网处理
//如果不是手动切断,是其他原因,所以要重新连接,断开连接的时候会调用这个函数
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
    if ([sock.userData isEqualToString:[NSString stringWithFormat:@"%d",SOCKET_CONNECT_SERVER]])
    {
        //服务器掉线 重新连接
        [self tcpConnectWithData:BaseSocktConnectStr];
    }
    else
    {
        return;
    }
}

//用户手动断开网络(这个方法我没写调用)
-(void)disconnectSocket
{
    self.mysocket.userData = SOCKET_CONNECT_USER;
    
    [self.myTimer timeInterval];
    
    [self.mysocket disconnect];
}

@end

Cocoa Streams使用详解

Cocoa Streams实际上是Objective-C对CFNetwork的简单封装,主要包含了三个类:NSStream, NSInputStream, and NSOutputStream。
它使用名为 NSStreamDelegate 的协议来实现 CFNetwork 中的回调函数的作用,同样,runloop 也与 NSStream 结合的很好。

#import "ViewController.h"

#define BaseHost @"wangys.com"
#define BasePort 12345

@interface ViewController ()<NSStreamDelegate>
{
    NSInputStream  * inputStream;
    NSOutputStream * outputStream;
}
@end

@implementation ViewController

-(void)viewDidLoad
{
    [super viewDidLoad];
}

//建立连接
-(void)connectToHost:(UIButton *)button
{
    NSString * host = BaseHost;
    int port = BasePort;
    
    // 1.定义C语言输入输出流
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;
    CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(host), port, &readStream, &writeStream);
    
    // 2.把C语言的输入输出流转化成OC对象
    inputStream = (__bridge NSInputStream *)(readStream);
    outputStream = (__bridge NSOutputStream *)(writeStream);
    
    // 3.设置代理
    inputStream.delegate = self;
    outputStream.delegate = self;
    
    // 4.输入输出流添加到runloop循环中
    [inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    
    
    // 5.打开输入输出流
    [inputStream open];
    [outputStream open];
}

//登录
-(void)loginClick:(UIButton *)button
{
    /**
     * 如果要登录,发送的数据格式为 "iam:wys";
     * 如果要发送聊天消息,数据格式为 "msg:send a message";
     */
    
    //登录指令
    NSString * loginString = @"iam:wys";
    
    NSData * data = [loginString dataUsingEncoding:NSUTF8StringEncoding];
    
    [outputStream write:data.bytes maxLength:data.length];
}

//处理流事件
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    NSLog(@"current thread : %@",[NSThread currentThread]);
    
    /**
     * NSStreamEventOpenCompleted = 1UL << 0      //输入输出流打开完成
     * NSStreamEventHasBytesAvailable = 1UL << 1  //有字节可读
     * NSStreamEventHasSpaceAvailable = 1UL << 2  //可以发放字节
     * NSStreamEventErrorOccurred = 1UL << 3      //连接出现错误
     * NSStreamEventEndEncountered = 1UL << 4     //连接结束
     */
    
    switch (eventCode) {
        case NSStreamEventOpenCompleted:
            break; 

        case NSStreamEventHasBytesAvailable:
            [self havaBytesToRead];
            break;

        case NSStreamEventHasSpaceAvailable:
            break;  

        case NSStreamEventErrorOccurred:
            break;
            
        case NSStreamEventEndEncountered:
            [self endConnect];
            break;
            
        default:
            break;
    }
}

//有字节可读
-(void)havaBytesToRead
{
    // 1.建立一个缓冲区 可以放1024字节
    uint8_t buf[1024];
    
    // 2.返回实际装的字节数
    NSInteger length = [inputStream read:buf maxLength:sizeof(buf)];
    
    // 3.把字节数组转化成字符串
    NSData * data = [NSData dataWithBytes:buf length:length];
    NSString * readStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
    
    [self showMessage:readStr];
}

//关闭连接
-(void)endConnect
{
    // 关闭输出输入流
    [inputStream close];
    [outputStream close];
    
    //从主运行循环中移除
    [inputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}

//显示信息
-(void)showMessage:(NSString *)string
{
    NSLog(@"showMessage:%@",string);
}
@end

2016-12-23更新

TCPUDP的区别

基于连接与无连接   
对系统资源的要求(TCP较多,UDP少)   
UDP程序结构较简单   
流模式与数据报模式   
TCP保证数据正确性,UDP可能丢包   
TCP保证数据顺序,UDP不保证   
UDP Server不需要调用listen和accept   
UDP收发数据用sendto/recvfrom函数   
TCP:地址信息在connect/accept时确定   
UDP:在sendto/recvfrom函数中每次均 需指定地址信息   
UDP:shutdown函数无效

参考文献
IOS开发之SOCKET长连接的使用
Socket简介
iOS中socket使用
GCDAsyncSocket 网络请求
iOS学习之Socket使用简明教程- AsyncSocket

推荐阅读更多精彩内容

  • 一、概念 首先,理清一些概念 TCP/IP和UDP,HTTP协议,Socket 1.TCP/IP和UDP,是网络中...
    _AJH阅读 1,692评论 0 16
  • 一、网络各个协议:TCP/IP、SOCKET、HTTP等 网络七层由下往上分别为物理层、数据链路层、网络层、传输层...
    杯水救车薪阅读 971评论 0 16
  • 对TCP/IP、UDP、Socket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。那么我想...
    yuantao123434阅读 4,964评论 1 97
  • 用socket从服务器上获取数据,有以下三种方法:第一种是使用封装好的第三方GCDAsyncSocket,下载地址...
    CMD独白阅读 571评论 3 5
  • 人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟。 ____...
    璐帆阅读 30评论 0 0