美文网首页iOS开发
iOS实现简单的socket通信

iOS实现简单的socket通信

作者: 落叶兮兮 | 来源:发表于2020-11-27 16:27 被阅读0次

    最近在学习socket通信原理,写篇文章将其记录一下,tcp和udp的传值的实现

    这次是实现在客户端发送数据,分别使用udp和tcp传输数据,之后服务器上显示客户端传输的数据,使用的第三方框架是CocoaAsyncSocket

    因为需要有服务器充当角色,没有相应的服务器,使用mac自带的apache服务器并没有配置成功,之后参考网上的写法

    iOS上的app充当客户端,mac上的app充当服务器(xcode新建project时选择macOs中的app选项)

    GitHub的相关地址为:
    socketClient
    socketServer

    最终的效果图为:
    先在mac上运行socketServer项目,开启服务器
    控制台输出结果:


    socketServer最初运行结果

    之后在iphone上运行socketClient项目
    tcp测试时的效果图:


    tcp传输.gif

    点击发送后
    socketSercer项目的控制台输出为:


    服务器接受相应的结果

    udp的效果图和tcp一样,不再复述

    tcp实现

    tcpServer的实现

    对应的实现文件是:
    socketClient项目中的tcpViewController,
    socketServer项目中的TcpServer

    首先,先新建一个project,选择macOs的app,在mac上运行充当服务器

    之后新建一个文件,继承自NSObject,命名为TcpServer
    使用的第三方框架CocoaAsyncSocket中,作者有英文介绍服务器方面代码的写法,相应的连接如下:
    CocoaAsyncSocket说明文档

    Server的大致写法

    参考作者提供的例子,在TcpServer中,我们在.h文件中,声明一个对外的方法,

    - (void)startTcpServer;
    

    另外,声明两个属性

    @property (nonatomic, strong) GCDAsyncSocket *listenSocket;//监听socket
    @property (nonatomic, strong) NSMutableArray *clientSocketArray;//这里用来保存socket,如果不保存的话,socket会被直接dealloc,然后就会出现服务器断开连接的错误
    

    在.m文件中,实现clientSocketArray懒加载

    
    - (NSMutableArray *)clientSocketArray {
        if (_clientSocketArray) {
            return _clientSocketArray;
        }
        _clientSocketArray = [NSMutableArray array];
        return _clientSocketArray;
    }
    

    实现startTcpServer方法

    self.listenSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
        NSError *error = nil;
    //这里我使用的port端口为5556,也可以使用其他的端口,重要的是要和客户端写的保持一致
        if (![self.listenSocket acceptOnPort:5556 error:&error]) {
            NSLog(@"tcp服务开启失败,失败的原因为%@",error);
        } else {
            NSLog(@"tcp服务开启成功");
        }
    

    让该类遵守协议GCDAsyncSocketDelegate,实现相应的协议方法

    //当socket连接成功后,执行这个方法,然后会产生一个新的socket来处理连接,如果想要处理连接,必须retain这个socket(使其不被释放),这就是我们前面定义clientSocketArray数组的原因
    - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket {
       //为了对连接进行处理,必须保证newSocket retain,不被释放,我们使用数组将其加入
        [self.clientSocketArray addObject:newSocket];
        NSLog(@"执行了这个方法");
       //开始读取数据,timeOut为负值表示没有时间限制,读取数据完毕,执行socket:didReadData:withTag: 这个协议方法
        [newSocket readDataWithTimeout:-1 tag:self.clientSocketArray.count];
    }
    
    //读取数据完成后,执行这个方法
    - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
        NSLog(@"当前的data为%@",data);
        NSLog(@"当前的tag为%ld",tag);
    //将NSData数据类型转变为NSString
        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"客户端传来的字符为:%@",result);
    }
    
    - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
        NSLog(@"当前服务器的IP地址为%@",host);
    }
    
    - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
        NSLog(@"服务器断开了连接,错误为%@",err);
    }
    

    如果我们不使用clientSocketArray数组保存newSocket的话,在客户端开始传值时,server的控制台会输出以下信息


    image.png

    意思就是newSocket被回收,导致连接中断
    所以,clientSocketArray的作用很大

    之后,在main.m文件中,

    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // Setup code that might create autoreleased objects goes here.
            TcpServer *tcpServer = [[TcpServer alloc] init];
            [tcpServer startTcpServer];
            
            UdpServer *udpServer = [[UdpServer alloc] init];
            [udpServer startUdpServer];
            //开启主运行循环
            [[NSRunLoop mainRunLoop] run];
        }
        return NSApplicationMain(argc, argv);
    }
    

    运行之后的输出结果为:


    serverSocket中最初的运行结果

    tcpClient的实现

    新建一个projiect,选择iOS中的app,运行在iphone手机上,命名为clientForSocketCommunication,新建文件tcpViewController,继承于UIViewController,

    在.h文件中,声明相关的属性

    @property (nonatomic, strong) UITextField *textField;
    @property (nonatomic, strong) GCDAsyncSocket *clientSocket;
    @property (nonatomic, strong) UIButton *sendButton;
    @property (nonatomic, strong) NSMutableArray *textArr;
    

    在.m中实现相应的懒加载

    - (UITextField *)textField {
        if (_textField) {
            return _textField;
        }
        _textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width - 60, 60)];
        _textField.backgroundColor = [UIColor grayColor];
        _textField.textColor = [UIColor blackColor];
        _textField.delegate = self;
        return _textField;
    }
    
    - (NSMutableArray *)textArr {
        if (_textArr) {
            return _textArr;
        }
        _textArr = [NSMutableArray array];
        return _textArr;
    }
    
    - (UIButton *)sendButton {
        if (_sendButton) {
            return _sendButton;
        }
        _sendButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];
        [_sendButton setTitle:@"发送" forState:UIControlStateNormal];
        [_sendButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [_sendButton addTarget:self action:@selector(sendMessage) forControlEvents:UIControlEventTouchUpInside];
        return _sendButton;
    }
    

    在viewDidload中,实现相应的布局,并且调用开始连接的方法startConnection

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        self.view.backgroundColor = [UIColor whiteColor];
        self.navigationController.navigationBar.translucent = NO;
        self.title = @"socket通信中tcp测试客户端";
        
        [self.view addSubview:self.textField];
        [self.textField mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.equalTo(self.view);
            make.left.equalTo(self.view).offset(30);
            make.top.equalTo(self.view).offset(60);
            make.height.equalTo(@60);
        }];
        [self.view addSubview:self.sendButton];
        [self.sendButton mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.equalTo(self.view);
            make.top.equalTo(self.textField.mas_bottom).offset(30);
            make.width.greaterThanOrEqualTo(@0);
            make.height.greaterThanOrEqualTo(@0);
        }];
        
        [self startConnection];
    }
    
    - (void)startConnection {
        self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
        NSError *error = nil;
        //电脑上的网络名称和手机上的网络ip地址必须一致才行,不然的话协议方法一直无法执行,电脑和手机在同一个局域网内,我的mac电脑的局域网地址是192.168.31.61,使用的端口是5556,必须要和Server里面设置的监听端口保持一致,我用的是5556,也可以使用其他的
        if (![self.clientSocket connectToHost:@"192.168.31.61" onPort:5556 error:&error]) {
            NSLog(@"连接失败,失败的原因是%@",error);
        }
    }
    

    实现按钮的点击方法,给server发送消息

    - (void)sendMessage {
        NSLog(@"点击了该方法");
        if (!self.textField.text || [self.textField.text length] == 0) {
            NSLog(@"发送的信息不能为空");
            return;
        }
        [self.textArr addObject:self.textField.text];
      //将NSString的数据类型转换为NSData的数据类型
        NSData *data = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding];
       //将数据写入套接字,并在完成时调用委托。
        [self.clientSocket writeData:data withTimeout:-1 tag:self.textArr.count];
    }
    

    接下来实现相应的协议方法

    #pragma mark - UITextFieldDelegate
    - (BOOL)textFieldShouldReturn:(UITextField *)textField {
        [self.textField resignFirstResponder];
        return YES;
    }
    
    #pragma mark - GCDAsyncSocketDelegate
    - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
        NSLog(@"连接成功,连接的ip地址为%@",host);
    }
    
    - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
        NSLog(@"断开了连接,错误的原因是%@",err);
    }
    
    - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
        NSLog(@"调用了这个方法");
    }
    
    - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
        NSLog(@"当前发送的是第%ld个请求",tag);
    }
    

    完成之后,运行socketServer项目,然后运行socketClient,在文本框中输入消息,点击发送,在socketServer项目中的控制台,即可得到相应的传输信息

    udp实现

    udpServer的实现

    在socketServer项目中,新建一个文件UdpSever,继承于NSObject
    在.h文件中声明一个对外的方法

    - (void)startUdpServer;
    

    并且声明相应的属性

    @property (nonatomic, strong) GCDAsyncUdpSocket *listenSocket;
    

    在.m中实现声明的方法

    - (void)startUdpServer {
        self.listenSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];//队列传递nil,会自动创建一个队列,这里如果自定义队列,一定不能是并发队列
        NSError *error = nil;的端口,但是必须和客户端的保持一致,不然的话会接收不到数据
    //设置socket对应的port,这里我使用的是5557这个端口,也可以使用其他
        [self.listenSocket bindToPort:5557 error:&error];
        if (error) {
            NSLog(@"开启udp服务失败,失败的原因是%@",error);
        } else {
            NSLog(@"开启udp服务成功");
            NSError *err = nil;
    //        [self.listenSocket receiveOnce:&err];//该方法表示只接受一次数据,接收客户端发过来的数据一次之后,就不再接收
            [self.listenSocket beginReceiving:&err];//该方法表示多次接收数据
            if (err) {
                NSLog(@"接收数据失败");
            }
        }
    }
    
    

    实现相应的协议方法

    #pragma mark - GCDAsyncUdpSocketDelegate
    - (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address {
        NSLog(@"执行了这个方法,address结果为%@",address);
    }
    
    - (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
                                                 fromAddress:(NSData *)address
    withFilterContext:(nullable id)filterContext {
        NSLog(@"接受数据的ip地址为%@",address);
        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"从客户端传来的数据为%@",result);
    }
    

    之后,同样的在main.m文件中添加以下代码

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // Setup code that might create autoreleased objects goes here.
            TcpServer *tcpServer = [[TcpServer alloc] init];
            [tcpServer startTcpServer];
            
            UdpServer *udpServer = [[UdpServer alloc] init];
            [udpServer startUdpServer];
            //开启主运行循环
            [[NSRunLoop mainRunLoop] run];
        }
        return NSApplicationMain(argc, argv);
    }
    

    运行程序,控制台的输出结果为:


    socketServer运行结果

    udpClient的实现

    在socketClient中,新建文件UdpViewController,继承于UIViewController,
    在.h文件中,声明相应的属性

    @property (nonatomic, strong) UITextField *textField;
    @property (nonatomic, strong) GCDAsyncUdpSocket *clientSocket;
    @property (nonatomic, strong) UIButton *sendButton;
    @property (nonatomic, strong) NSMutableArray *textArr;
    

    在.m文件中,实现相应的懒加载

    - (UITextField *)textField {
        if (_textField) {
            return _textField;
        }
        _textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width - 60, 60)];
        _textField.backgroundColor = [UIColor grayColor];
        _textField.textColor = [UIColor blackColor];
        _textField.delegate = self;
        return _textField;
    }
    
    - (NSMutableArray *)textArr {
        if (_textArr) {
            return _textArr;
        }
        _textArr = [NSMutableArray array];
        return _textArr;
    }
    
    - (UIButton *)sendButton {
        if (_sendButton) {
            return _sendButton;
        }
        _sendButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];
        [_sendButton setTitle:@"发送" forState:UIControlStateNormal];
        [_sendButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [_sendButton addTarget:self action:@selector(sendMessage) forControlEvents:UIControlEventTouchUpInside];
        return _sendButton;
    }
    
    

    在viewDidload中实现相应的布局,并且开始调用startConnection方法

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        self.view.backgroundColor = [UIColor whiteColor];
        self.navigationController.navigationBar.translucent = NO;
        self.title = @"socket通信中udp测试客户端";
        
        [self.view addSubview:self.textField];
        [self.textField mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.equalTo(self.view);
            make.left.equalTo(self.view).offset(30);
            make.top.equalTo(self.view).offset(60);
            make.height.equalTo(@60);
        }];
        
        [self.view addSubview:self.sendButton];
        [self.sendButton mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.equalTo(self.view);
            make.top.equalTo(self.textField.mas_bottom).offset(30);
            make.width.greaterThanOrEqualTo(@0);
            make.height.greaterThanOrEqualTo(@0);
        }];
        
        [self startConnection];
    }
    
    - (void)startConnection {
        self.clientSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
        NSError *error = nil;
      //端口这里使用的是5557,也可以使用5558,都行,这里无所谓,重要的是下面发送数据时host和port一定要和服务器保持一致,下面的这部分代码其实也可以不写
        [self.clientSocket bindToPort:5557 error:&error];
        if (error) {
            NSLog(@"绑定端口失败,失败的原因是%@",error);
        }
    }
    

    实现点击按钮发送消息的方法

    - (void)sendMessage {
        if (!self.textField.text || [self.textField.text length] == 0) {
            return;
        }
        NSLog(@"开始发送数据");
        [self.textArr addObject:self.textField.text];
        NSData *data = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding];
       //192.168.31.61是我mac电脑连接网路的地址,5557是服务器监听的port,一定要和服务器保持一致
        [self.clientSocket sendData:data toHost:@"192.168.31.61" port:5557 withTimeout:-1 tag:self.textArr.count];
    }
    

    实现相应的协议方法

    #pragma mark - delegate
    - (BOOL)textFieldShouldReturn:(UITextField *)textField {
        [self.textField resignFirstResponder];
        return YES;
    }
    
    #pragma mark - GCDAsyncUdpSocketDelegate
    - (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag {
        NSLog(@"调用了该方法");
        NSLog(@"这是发出的第%ld个请求",tag);
    }
    
    - (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error {
        NSLog(@"tag为%ld的数据发送错误",tag);
        NSLog(@"错误的原因为%@",error);
    }
    
    - (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError  * _Nullable)error {
        NSLog(@"socket被关闭");
    }
    
    - (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error {
        NSLog(@"并没有连接,连接失败");
    }
    
    

    先运行socketServer,再运行socketClient,点击发送相应的消息,在socketServer控制台中就会输出相应的接收到的数据

    总结

    GitHub的相关地址为:
    socketClient
    socketServer

    最终的效果图为:
    先在mac上运行socketServer项目,开启服务器
    控制台输出结果:


    socketServer最初运行结果

    之后在iphone上运行socketClient项目
    tcp测试时的效果图:


    tcp传输.gif

    点击发送后
    socketSercer项目的控制台输出为:


    服务器接受相应的结果

    udp的效果图和tcp一样,不再复述

    相关文章

      网友评论

        本文标题:iOS实现简单的socket通信

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