之前都沒有做過相關(guān)socket的柬焕,閑著就找了些相關(guān)的socket荐吵,發(fā)現(xiàn)自己寫的還是比較繁瑣的混卵。默默的找了下第三方CocoaAsyncSocket拙泽。雖然網(wǎng)上有很多關(guān)于CocoaAsyncSocket的帖子,但是還是得自己實(shí)踐还蹲,自己寫一篇來(lái)的記憶深刻爹耗,理解更深入。下面開始干貨谜喊,demo源碼潭兽。
由于沒有服務(wù)器,只好自己默默的搭一下端對(duì)端的斗遏,其中一個(gè)當(dāng)做服務(wù)器山卦,另一個(gè)當(dāng)然就是用戶端了。
服務(wù)端
導(dǎo)入CocoaAsyncSocket诵次,就不說(shuō)了账蓉,本文講的是使用GCD版本的TCP即時(shí)通訊,非UDP逾一。
導(dǎo)入頭文件#import "GCDAsyncSocket.h"铸本,并遵循代理GCDAsyncSocketDelegate
創(chuàng)建對(duì)應(yīng)的控件
//手動(dòng)設(shè)置端口號(hào)輸入框
@property (weak, nonatomic) IBOutlet UITextField *portField;
@property (weak, nonatomic) IBOutlet UITextField *ipField;
//服務(wù)端給用戶端發(fā)送消息輸入框
@property (weak, nonatomic) IBOutlet UITextField *msgField;
//開始監(jiān)聽按鈕
@property (weak, nonatomic) IBOutlet UIButton *beginBtn;
//發(fā)送消息按鈕
@property (weak, nonatomic) IBOutlet UIButton *senderBtn;
//接收消息按鈕
@property (weak, nonatomic) IBOutlet UIButton *receiveBtn;
//接收到消息顯示文本
@property (weak, nonatomic) IBOutlet UITextView *receiveLabel;
//服務(wù)器socket(開放端口,監(jiān)聽客戶端socket的鏈接)
@property(nonatomic, strong) GCDAsyncSocket *serverSocket;
//保護(hù)客戶端socket
@property (strong, nonatomic) GCDAsyncSocket *clientSocket;
初始化服務(wù)端Socket
//初始化服務(wù)器socket,在主線程回調(diào)
self.serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
實(shí)現(xiàn)GCDAsyncSocketDelegate
代理 didAcceptNewSocket遵堵,接收對(duì)方發(fā)送過來(lái)的socket箱玷,獲取到對(duì)應(yīng)的消息
- (void)socket:(GCDAsyncSocket*)sock didAcceptNewSocket:(GCDAsyncSocket*)newSocket{
//sock為服務(wù)端的socket怨规,服務(wù)端的socket只負(fù)責(zé)客戶端的連接,不負(fù)責(zé)數(shù)據(jù)的讀取锡足。 newSocket為客戶端的socket
//保存客戶端的socket
_clientSocket = newSocket;
NSLog(@"服務(wù)端的socket %p 客戶端的socket %p",sock,newSocket);
[self showMessageWithStr:@"鏈接成功"];
[self showMessageWithStr:[NSString stringWithFormat:@"服務(wù)器地址:%@ -端口:%d", newSocket.connectedHost, newSocket.connectedPort]];
[newSocket readDataWithTimeout:-1 tag:0];//超時(shí)波丰,以及標(biāo)記tag
}
代理didWriteDataWithTag,服務(wù)器寫數(shù)據(jù)給客戶端
//服務(wù)器寫數(shù)據(jù)給客戶端
-(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
//接收到數(shù)據(jù)
NSString *receiverStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[self showMessageWithStr:receiverStr];
// // 把回車和換行字符去掉掰烟,接收到的字符串有時(shí)候包括這2個(gè),導(dǎo)致判斷quit指令的時(shí)候判斷不相等
// receiverStr = [receiverStr stringByReplacingOccurrencesOfString:@"\r" withString:@""];
// receiverStr = [receiverStr stringByReplacingOccurrencesOfString:@"\n" withString:@""];
/*
//判斷是登錄指令還是發(fā)送聊天數(shù)據(jù)的指令沐批。這些指令都是自定義的
//登錄指令
if([receiverStr hasPrefix:@"iam:"]){
// 獲取用戶名
NSString *user = [receiverStr componentsSeparatedByString:@":"][1];
// 響應(yīng)給客戶端的數(shù)據(jù)
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
開始監(jiān)聽按鈕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:服務(wù)器開啟成功"];
}
else [self showMessageWithStr:@"system:服務(wù)器開啟失敗"];
}
發(fā)送消息按鈕Action
- (void)senderBtnClick
{
NSData *data = [self.msgField.text dataUsingEncoding:NSUTF8StringEncoding];
//tag:消息標(biāo)記,withTimeout -1:無(wú)窮大九孩,一直等
[_clientSocket writeData:data withTimeout:-1 tag:0];
}
接收消息按鈕Action
- (void)receiveBtnClick
{
[self.clientSocket readDataWithTimeout:11 tag:0];
}
代碼寫完了惧磺,來(lái)看下界面:
客戶端
一樣導(dǎo)入頭文件#import "GCDAsyncSocket.h",并遵循代理GCDAsyncSocketDelegate
創(chuàng)建對(duì)應(yīng)的控件
//端口號(hào)輸入框捻撑,對(duì)應(yīng)服務(wù)端的端口號(hào)
@property (weak, nonatomic) IBOutlet UITextField *portField;
//ip地址輸入框磨隘,客戶端的主機(jī)ip地址(這個(gè)需要注意一下因?yàn)橛玫氖荰CP,不是UDP顾患,需要將兩個(gè)端安裝在同一個(gè)設(shè)備上番捂,不然沒法接收消息,因?yàn)閕p地址可以直接用回環(huán)地址: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()];//直接全局隊(duì)列
實(shí)現(xiàn)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江解,發(fā)送消息
//數(shù)據(jù)發(fā)送成功
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
NSLog(@"%s",__func__);
[self showMessageWithStr:@"system:發(fā)送成功"];
//發(fā)送完數(shù)據(jù)手動(dòng)讀取
[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,用于監(jiān)聽連接情況
-(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];
}
下面展示下界面效果犁河,連接對(duì)應(yīng)的客戶端鳖枕,以及用戶端效果
就這樣,簡(jiǎn)單的實(shí)現(xiàn)了桨螺,端對(duì)端的連接宾符,以及發(fā)送消息。
GCDAsyncSocketDelegate的代理方法還有好多灭翔,具體的后面還會(huì)研究并且更新文章魏烫,當(dāng)然有興趣的搬磚們可以自己去研究,本文只是一個(gè)簡(jiǎn)單的入門肝箱,如有問題可以給我留言哄褒。
總結(jié)一下,并強(qiáng)調(diào)一下本文用的是GCD版的TCP連接煌张,因?yàn)楸救耸菍⒖蛻舳四派摹⒎?wù)端分開寫在兩個(gè)App上(可以直接寫在同一個(gè)App上,用tabBarController來(lái)實(shí)現(xiàn)一頁(yè)客戶端骏融,一頁(yè)用戶端)链嘀,會(huì)存在一個(gè)問題井辜,那就是你如果安裝在兩個(gè)設(shè)備上你是無(wú)法連接成功的、無(wú)法連接成功管闷、無(wú)法連接成功,需要安裝在同一個(gè)設(shè)備上一開始我就被這個(gè)問題給坑了一下窃肠。當(dāng)然UDP不存在這個(gè)問題包个,以及你客戶端、服務(wù)端寫在同一個(gè)App上也是不存在的無(wú)法連接成功的問題冤留。好了碧囊,要說(shuō)的就這些,碰到的坑代碼里面也有說(shuō)了纤怒,總結(jié)也強(qiáng)調(diào)了糯而。