最近在学习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说明文档
参考作者提供的例子,在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一样,不再复述
网友评论