美文网首页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