之前都没有做过相关socket的,闲着就找了些相关的socket,发现自己写的还是比较繁琐的。默默的找了下第三方CocoaAsyncSocket。虽然网上有很多关于CocoaAsyncSocket的帖子,但是还是得自己实践,自己写一篇来的记忆深刻,理解更深入。下面开始干货,demo源码。
由于没有服务器,只好自己默默的搭一下端对端的,其中一个当做服务器,另一个当然就是用户端了。
服务端
导入CocoaAsyncSocket,就不说了,本文讲的是使用GCD版本的TCP即时通讯,非UDP。
导入头文件#import "GCDAsyncSocket.h",并遵循代理GCDAsyncSocketDelegate
创建对应的控件
//手动设置端口号输入框
@property (weak, nonatomic) IBOutlet UITextField *portField;
@property (weak, nonatomic) IBOutlet UITextField *ipField;
//服务端给用户端发送消息输入框
@property (weak, nonatomic) IBOutlet UITextField *msgField;
//开始监听按钮
@property (weak, nonatomic) IBOutlet UIButton *beginBtn;
//发送消息按钮
@property (weak, nonatomic) IBOutlet UIButton *senderBtn;
//接收消息按钮
@property (weak, nonatomic) IBOutlet UIButton *receiveBtn;
//接收到消息显示文本
@property (weak, nonatomic) IBOutlet UITextView *receiveLabel;
//服务器socket(开放端口,监听客户端socket的链接)
@property(nonatomic, strong) GCDAsyncSocket *serverSocket;
//保护客户端socket
@property (strong, nonatomic) GCDAsyncSocket *clientSocket;
初始化服务端Socket
//初始化服务器socket,在主线程回调
self.serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
实现GCDAsyncSocketDelegate
代理 didAcceptNewSocket,接收对方发送过来的socket,获取到对应的消息
- (void)socket:(GCDAsyncSocket*)sock didAcceptNewSocket:(GCDAsyncSocket*)newSocket{
//sock为服务端的socket,服务端的socket只负责客户端的连接,不负责数据的读取。 newSocket为客户端的socket
//保存客户端的socket
_clientSocket = newSocket;
NSLog(@"服务端的socket %p 客户端的socket %p",sock,newSocket);
[self showMessageWithStr:@"链接成功"];
[self showMessageWithStr:[NSString stringWithFormat:@"服务器地址:%@ -端口:%d", newSocket.connectedHost, newSocket.connectedPort]];
[newSocket readDataWithTimeout:-1 tag:0];//超时,以及标记tag
}
代理didWriteDataWithTag,服务器写数据给客户端
//服务器写数据给客户端
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
NSLog(@"%s",__func__);
[sock readDataWithTimeout:-1 tag:100];
}
代理didReadData,收到消息
//收到消息
- (void)socket:(GCDAsyncSocket*)sock didReadData:(NSData*)data withTag:(long)tag{
//sock为客户端的socket
//接收到数据
NSString *receiverStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[self showMessageWithStr:receiverStr];
// // 把回车和换行字符去掉,接收到的字符串有时候包括这2个,导致判断quit指令的时候判断不相等
// receiverStr = [receiverStr stringByReplacingOccurrencesOfString:@"\r" withString:@""];
// receiverStr = [receiverStr stringByReplacingOccurrencesOfString:@"\n" withString:@""];
/*
//判断是登录指令还是发送聊天数据的指令。这些指令都是自定义的
//登录指令
if([receiverStr hasPrefix:@"iam:"]){
// 获取用户名
NSString *user = [receiverStr componentsSeparatedByString:@":"][1];
// 响应给客户端的数据
NSString *respStr = [user stringByAppendingString:@"has joined"];
[sock writeData:[respStr dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
}
//聊天指令
if ([receiverStr hasPrefix:@"msg:"]) {
//截取聊天消息
NSString *msg = [receiverStr componentsSeparatedByString:@":"][1];
[sock writeData:[msg dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
}
//quit指令
if ([receiverStr isEqualToString:@"quit"]) {
//断开连接
[sock disconnect];
//移除socket
_clientSocket = nil;
}
*/
NSLog(@"%s",__func__);
}
显示消息
- (void)showMessageWithStr:(NSString *)str
{
NSString * tmpStr = _receiveLabel.text;
tmpStr = [tmpStr stringByAppendingString:[NSString stringWithFormat:@"\n%@",str]];
[_receiveLabel setText:tmpStr];
}
按钮Action
开始监听按钮Action
- (void)beginBtnClick
{
//2、开放端口
NSError*error =nil;
BOOL result = [self.serverSocket acceptOnPort:self.portField.text.integerValue error:&error];
if(result && error ==nil) {
[self showMessageWithStr:@"system:服务器开启成功"];
}
else [self showMessageWithStr:@"system:服务器开启失败"];
}
发送消息按钮Action
- (void)senderBtnClick
{
NSData *data = [self.msgField.text dataUsingEncoding:NSUTF8StringEncoding];
//tag:消息标记,withTimeout -1:无穷大,一直等
[_clientSocket writeData:data withTimeout:-1 tag:0];
}
接收消息按钮Action
- (void)receiveBtnClick
{
[self.clientSocket readDataWithTimeout:11 tag:0];
}
代码写完了,来看下界面:
客户端
一样导入头文件#import "GCDAsyncSocket.h",并遵循代理GCDAsyncSocketDelegate
创建对应的控件
//端口号输入框,对应服务端的端口号
@property (weak, nonatomic) IBOutlet UITextField *portField;
//ip地址输入框,客户端的主机ip地址(这个需要注意一下因为用的是TCP,不是UDP,需要将两个端安装在同一个设备上,不然没法接收消息,因为ip地址可以直接用回环地址:127.0.0.1)
@property (weak, nonatomic) IBOutlet UITextField *ipField;
//消息输入送框
@property (weak, nonatomic) IBOutlet UITextField *msgField;
//接收消息文本
@property (weak, nonatomic) IBOutlet UILabel *receiveLabel;
@property(nonatomic, strong) GCDAsyncSocket *clientSocket;
初始化
self.clientSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];//直接全局队列
实现GCDAsyncSocketDelegate
代理didConnectToHost
//连接成功
- (void)socket:(GCDAsyncSocket*)sock didConnectToHost:(NSString*)host port:(uint16_t)port{
[self showMessageWithStr:@"system:连接成功"];
NSLog(@"system:连接成功");
[self.clientSocket readDataWithTimeout:-1 tag:0];
}
代理didWriteDataWithTag,发送消息
//数据发送成功
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
NSLog(@"%s",__func__);
[self showMessageWithStr:@"system:发送成功"];
//发送完数据手动读取
[sock readDataWithTimeout:-1 tag:tag];
}
代理didReadData,接收消息
//收到消息
- (void)socket:(GCDAsyncSocket*)sock didReadData:(NSData*)data withTag:(long)tag{
NSString*text = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
[self showMessageWithStr:text];
[self.clientSocket readDataWithTimeout:-1 tag:0];
}
代理socketDidDisconnect,用于监听连接情况
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
if (err) {
[self showMessageWithStr:@"system:连接失败"];
NSLog(@"system:连接失败");
}else{
[self showMessageWithStr:@"system:正常断开"];
NSLog(@"system:正常断开");
}
}
按钮Action
开始连接
- (IBAction)beginBtnClickEvent:(id)sender {
// [self.clientSocket connectToHost:self.ipField.text onPort:self.portField.text.intValue withTimeout:-1 error:&error];
[self.clientSocket connectToHost:self.ipField.text onPort:self.portField.text.integerValue viaInterface:nil withTimeout:-1 error:nil];
NSLog(@"ip:%@,端口:%@",self.ipField.text,self.portField.text);
//
// NSString *loginStr = @"iam:I am login!";
//
// NSData *loginData = [loginStr dataUsingEncoding: NSUTF8StringEncoding];
//
// [_clientSocket writeData:loginData withTimeout:-1 tag:0];
}
展示消息
- (void)showMessageWithStr:(NSString *)str
{
NSLog(@"%@",str);
self.receiveLabel.text = [self.receiveLabel.text stringByAppendingFormat:@"%@\n", str];
}
下面展示下界面效果,连接对应的客户端,以及用户端效果
就这样,简单的实现了,端对端的连接,以及发送消息。
GCDAsyncSocketDelegate的代理方法还有好多,具体的后面还会研究并且更新文章,当然有兴趣的搬砖们可以自己去研究,本文只是一个简单的入门,如有问题可以给我留言。
总结一下,并强调一下本文用的是GCD版的TCP连接,因为本人是将客户端、服务端分开写在两个App上(可以直接写在同一个App上,用tabBarController来实现一页客户端,一页用户端),会存在一个问题,那就是你如果安装在两个设备上你是无法连接成功的、无法连接成功、无法连接成功,需要安装在同一个设备上一开始我就被这个问题给坑了一下。当然UDP不存在这个问题,以及你客户端、服务端写在同一个App上也是不存在的无法连接成功的问题。好了,要说的就这些,碰到的坑代码里面也有说了,总结也强调了。