前言
在終端中模擬群聊功能,連接 QQ服務(wù)器的終端命令: telnet命令telnet host(IP地址) port(端口號(hào)) 比如 : telnet 192.168.10.1 1688(端口號(hào)是隨便寫的,只要在規(guī)定的范圍中即可,但是也有可能連接失敗,原因是端口號(hào)已經(jīng)被使用了).
1.telnet命令是連接服務(wù)器上的某個(gè)端口對(duì)應(yīng)的服務(wù)首先定義一個(gè)方法用于開啟QQ群聊的服務(wù)器
#import <Foundation/Foundation.h>
@interface WGServicerListener : NSObject
/** 定義一個(gè)對(duì)象方法,用于QQ開啟服務(wù)器 */
- (void)startServeSocket;
@end
- 在main.m函數(shù)開啟群聊服務(wù)器,并且讓服務(wù)器永不停止(除非程序退出)
#import <Foundation/Foundation.h>
#import "GCDAsyncSocket.h"
#import "WGServicerListener.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 創(chuàng)建一個(gè)服務(wù)器的監(jiān)聽對(duì)象
WGServicerListener *serveListener = [[WGServicerListener alloc] init];
// 監(jiān)聽開啟服務(wù)器
[serveListener startServeSocket];
// 保證服務(wù)器一直開啟
[[NSRunLoop mainRunLoop] run];
}
return 0;
}
- 監(jiān)聽客戶端和服務(wù)器的連接, 以及監(jiān)聽客戶端是否上傳可數(shù)據(jù)
#import "WGServicerListener.h"
#import "GCDAsyncSocket.h"
@interface WGServicerListener () <GCDAsyncSocketDelegate>
/** 服務(wù)器的Socket對(duì)象 */
@property(nonatomic, strong) GCDAsyncSocket *serverSocket;
/** 保存所有客戶端對(duì)象 */
@property(nonatomic, strong) NSMutableArray *clientSockets;
@end
@implementation WGServicerListener
#pragma mark - 懶加載
- (NSMutableArray *)clientSockets
{
if (!_clientSockets) {
_clientSockets = [NSMutableArray array];
}
return _clientSockets;
}
- (void)startServeSocket {
// 創(chuàng)建一個(gè)服務(wù)器的Socket對(duì)象
GCDAsyncSocket *serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
// 綁定并監(jiān)聽serverSocket對(duì)象
NSError *error = nil;
[serverSocket acceptOnPort:1886 error:&error];
// 判斷是否開啟QQ服務(wù)器
if (!error) {
NSLog(@"QQ服務(wù)器已經(jīng)開啟");
} else
{
NSLog(@"QQ服務(wù)器開啟失敗");
}
// 保存創(chuàng)建的服務(wù)器Socket對(duì)象
self.serverSocket = serverSocket;
}
/**
* 只要有客戶端連接服務(wù)器就會(huì)調(diào)用該代理方法. 第一個(gè)Socket表示服務(wù)器的Socket對(duì)象,第二個(gè)是客戶端的Socket對(duì)象
* 在該方法中的參數(shù)中:serverSocket就是服務(wù)器端的Socket,所以需要定義一個(gè)屬性強(qiáng)引用著它,第二個(gè)參數(shù)是服務(wù)器
* 端的clientSocket,用于讀取客戶端上傳的數(shù)據(jù).所以需要定義一個(gè)數(shù)組保存它.
*/
- (void)socket:(GCDAsyncSocket *)serverSocket didAcceptNewSocket:(GCDAsyncSocket *)clientSocket
{
// NSLog(@"%@",serverSocket);
// NSLog(@"%@",clientSocket);
// 保存和QQ服務(wù)器連接的客戶端
[self.clientSockets addObject:clientSocket];
// 監(jiān)聽客戶端有沒有上傳數(shù)據(jù)
/**
* -1 表示不要超時(shí)
*/
[clientSocket readDataWithTimeout:-1 tag:0];
NSLog(@"客戶端%ld已經(jīng)連接到服務(wù)器了",self.clientSockets.count);
}
/**
* 監(jiān)聽客戶端有沒有上傳數(shù)據(jù),只要和服務(wù)器連接的客戶端發(fā)送了消息,那么就一定會(huì)調(diào)用該方法
* 第一個(gè)參數(shù)是客戶端(因?yàn)楸O(jiān)聽的是客戶端是否發(fā)送消息)
*/
- (void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag
{
// 傳進(jìn)來(lái)的是一個(gè)NSData類型,需要將它轉(zhuǎn)為字符串類型
NSString *responeStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",responeStr);
// 發(fā)送消息,在發(fā)送消息之前需要判斷當(dāng)前監(jiān)聽的對(duì)象(客戶端)是不是自己,如果是自己那么就不要發(fā)送消息給自己了
for (GCDAsyncSocket *socket in self.clientSockets) {
if (socket != clientSocket) {
[socket writeData:data withTimeout:-1 tag:0];
}
}
// 每次發(fā)送完畢消息,都需要監(jiān)聽客戶端是否上傳了信息,如果不監(jiān)聽,永遠(yuǎn)發(fā)送不了下一條消息
[clientSocket readDataWithTimeout:-1 tag:0];
}
@end
-
下面是模擬QQ群聊,一個(gè)終端代表一個(gè)客戶端.
消息發(fā)送的過程 : 客戶端--->服務(wù)器--->另一個(gè)客戶端
Snip20160309_3.png 知識(shí)拓展
端口號(hào)不一樣的作用是什么: 比如說(shuō)我們?cè)陔娔X上同時(shí)登陸兩個(gè)QQ,當(dāng)QQ1發(fā)送消息給QQ3,首先是QQ1將消息發(fā)送給QQ的服務(wù)器,服務(wù)器再將數(shù)據(jù)轉(zhuǎn)發(fā)給QQ3,但是同時(shí)QQ2也發(fā)送了消息給QQ3,也是首先將消息發(fā)送到QQ服務(wù)器,最后才是轉(zhuǎn)發(fā)給QQ3,但是,如果QQ3想要回復(fù)信息,也同樣是將信息先發(fā)送給服務(wù)器,服務(wù)器就是通過這個(gè)端口號(hào)來(lái)判定信息該發(fā)給誰(shuí).端口號(hào)是系統(tǒng)自己分配的.
// 箭頭表示發(fā)送消息的方向
QQ1(端口號(hào)01)—> QQ服務(wù)器 —> QQ3
QQ2(端口號(hào)02)—> QQ服務(wù)器 —> QQ3
—>QQ服務(wù)器—> 根據(jù)端口號(hào)01 —> QQ1
|
QQ3 恢復(fù)消息
|
—>QQ服務(wù)器—> 根據(jù)端口號(hào)02—>QQ2
發(fā)送消息的原理 : 客戶端的數(shù)據(jù)—>服務(wù)器 —> 發(fā)給好友(轉(zhuǎn)發(fā)的過程)