Socket:網絡上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱為一個socket。
socket.png
網絡通信要素
- IP地址:網絡上主機設備的唯一標識
- 端口號:服務器上有不同的應用程序,用于標示進程的邏輯地址,不同進程的標示
傳輸協議
- TCP:需要建立連接,傳輸數據大小不受限制,可靠協議、安全送達,效率低
a、HTTP底層就是通過socket建立連接通信管道,實現數據傳輸
b、HTTP是一個TCP的傳輸協議(方式),它是一個可靠,安全的協議 - UDP:不需要建立連接,數據大小有限制,不可靠協議,傳輸速度快
TCP/UDP是數據傳輸的方式,而HTTP/XMPP等一種數據傳輸的格式,為了方便接收和讀取,你可以自己定義協議格式
網上看了小碼哥的視頻實現一個簡單的聊天室,列下核心代碼搞清楚中間發生了哪些事
// 服務端代碼
#import "XMGServiceListener.h"
#import "GCDAsyncSocket.h" // 基于Scoket原生的框架 CocoaAsyncSocket github上有下
@interface XMGServiceListener()<GCDAsyncSocketDelegate>
@property (nonatomic, strong) GCDAsyncSocket *serverSocket; /** 服務端的socket */
@property (nonatomic, strong) NSMutableArray *clientSockets; //客戶端的所有socket對象
@end
@implementation XMGServiceListener
-(void)start{ // 開啟服務端
// 1.創建一個socket對象
// serverSocket 服務端的socket只監聽 有沒有客戶端請求連接
GCDAsyncSocket *serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
// 2.綁定端口,并開啟監聽,代表服務端已經開啟 端口號使用1024以上的 0-1024為系統端口
NSError *error = nil;
[serverSocket acceptOnPort:5288 error:&error];
if (!error) {
NSLog(@"服務開啟成功");
}else{
//失敗原因是端口被其它程序占用
NSLog(@"服務開啟失敗 %@",error);
}
self.serverSocket = serverSocket;
}
#pragma mark 有客戶端的socket連接到服務器
-(void)socket:(GCDAsyncSocket *)serverSocket didAcceptNewSocket:(GCDAsyncSocket *)clientSocket{
NSLog(@"serverSocket %@ ",serverSocket);
NSLog(@"clientSocket %@ host:%@ port:%d",clientSocket,clientSocket.connectedHost,clientSocket.connectedPort);
//1.保存客戶端的socket
[self.clientSockets addObject:clientSocket];
// 2.監聽客戶端有沒有數據上傳
//timeout -1 代表不超時
//tag 標識作用,現在不用,就寫0
[clientSocket readDataWithTimeout:-1 tag:0];
NSLog(@"當前有%ld 客戶已經連接到服務器",self.clientSockets.count);
}
#pragma mark 讀取客戶端請求的數據
-(void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag{
// 1.把NSData轉NSString
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// 2.把當前客戶端的數據 轉發給 其它的客戶端
NSLog(@"接收到客戶端上傳的數據:%@",str);
for (GCDAsyncSocket *socket in self.clientSockets) {
if (socket != clientSocket) { // 不發送給發消息的客戶端
[socket writeData:data withTimeout:-1 tag:0];
}
#warning 每次讀完數據后,都要調用一次監聽數據的方法
[clientSocket readDataWithTimeout:-1 tag:0];
}
@end
// 客戶端代碼
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 實現聊天室
// 1.連接到群聊服務器
// 1.1.創建一個客戶端的socket對象
GCDAsyncSocket *clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
self.clientSocket = clientSocket;
// 1.2 發送連接請求
NSError *error = nil;
[clientSocket connectToHost:@"192.168.0.108" onPort:5288 error:&error];
if (!error) {
NSLog(@"%@",error);
}
// 2.發送聊天消息和接收聊天消息
}
-(void)socket:(GCDAsyncSocket *)clientSocket didConnectToHost:(NSString *)host port:(uint16_t)port{
NSLog(@"與服務器連接成功");
// 監聽讀取數據
[clientSocket readDataWithTimeout:-1 tag:0];
}
// Disconnect 斷開連接
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
NSLog(@"與服務器斷開連接 %@",err);
}
#pragma mark 讀取消息
-(void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag{
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",str);
NSLog(@"%@",[NSThread currentThread]);
// 把消息添加到數據源
if (str) {
[self.dataSources addObject:str];
// 刷新表格
#warning 要在主線程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.tableView reloadData];
}];
}
// 監聽讀取數據
[clientSocket readDataWithTimeout:-1 tag:0];
}
- (IBAction)sendAction:(id)sender {
// 發數據
[self.clientSocket writeData:[@"hello world" dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
}
上面的聊天室是基于TCP的長鏈接,將客戶端socket從數組中移除就可以退出
XMPPFramework框架也是一種類似GCDAsyncSocket的框架,提供了一系列的方法去實現與服務端的連接和收發