Socket 字面意思又稱「套接字」git
網絡上的兩個程序(如,客戶端和服務器端)經過一個雙向的通訊鏈接實現數據的交換,這個鏈接的一端稱爲一個Socket。github
說明:json
☞ 客戶端向服務器端發送網絡請求前,必需要先在底層創建一個通訊鏈接(通訊管道),才能發送網絡請求。數組
客戶端向服務器端發送http請求,服務器返回數據,這個過程就是一個數據交換的過程。安全
客戶端與服務器端進行數據交換,須要先創建一個雙向的通訊鏈接(即一條線、一個通道)服務器
☞ 客戶端和服務端 兩端都有一個Socket,經過Socket創建一個鏈接(雙向通訊管道),有了管道就能夠進行數據傳輸。網絡
☞ Socket 就是通訊管道的兩個端口,能夠理解爲管道的入口/出口。app
網絡上的請求就是經過Socket來創建鏈接而後互相通訊框架
1. IP地址(網絡上主機設備的惟一標識)——>尋找服務器主機socket
2. 端口號(定位程序) ——> 尋找程序
3. 傳輸協議(就是用什麼樣的方式進行交互)
TCP和UDP:數據傳輸的兩種方式,即把數據從一端傳到另外一端的兩種方式
1. TCP(傳輸控制協議) —>要創建鏈接(如:發送HTTP請求,客戶端向服務端發送網絡請求)
☞ 創建鏈接,造成傳輸數據的通道
☞ 在鏈接中進行大數據傳輸(數據大小不受限制)
☞ 經過三次握手完成鏈接,是可靠協議,安全送達
說明:在創建通訊鏈接(打通管道)以前有三次握手,目的是爲了數據的安全性和可靠性(讓數據安全可靠的傳輸到對方)。
舉例:打電話 (理解三次握手)
第一次握手:拿起電話,進行撥號。這個撥號的過程稱爲第一次握手。【開始準備鏈接】
第二次握手:撥通了,對方"喂"了一聲(響應了一聲),我聽到了,稱爲第二次握手。【說明我鏈接你 沒問題】
第三次握手:我聽到了對方"喂"了一聲(響應了一聲),我也習慣性的"喂"了一聲,對方聽到了。【說明你鏈接我 沒問題】
若是這三個過程都沒有問題,就能夠肯定通話鏈接創建成功。
☞ 必須創建鏈接,效率會稍低。(每次請求都要創建鏈接)
2. UDP(用戶數據報協議)—>不創建鏈接 (如:廣播用這個,不斷的發送數據包)
☞ 將 數據 及 源 和 目的 封裝成數據包中,不須要創建鏈接
☞ 每一個數據報的大小限制在64KB以內
☞ 由於無需鏈接,所以是不可靠協議
舉例:看老師廣播講課,網絡卡主了,再看到的是最新的視頻內容,不能接着看,可能錯過了一些內容。
☞ 不須要創建鏈接,速度快 (省掉了三次握手操做)
☞ bind():綁定端口 (80、3306)
☞ listen():監聽端口(服務器監聽客戶端有沒有鏈接到這個端口來)
☞ accept():若是有鏈接到這個端口,就接收這個鏈接。(通訊管道打通,接下來就能夠傳輸數據了)
☞ write():發請求/寫請求/發數據
☞ read():讀請求/讀數據
實現Socket服務端監聽:
1)使用C語言實現。
2)使用 CocoaAsyncSocket 第三方框架(OC),內部是對C的封裝。
telnet命令:是鏈接服務器上的某個端口對應的服務。
telnet命令:telnet host port
如:telnet www.baidu.com 80 (IP地址和域名同樣,都能找到主機。)
☞ 本身寫一個服務端,用終端代替客戶端來演示
☞ 掌握:經過Socket對象在服務器裏怎麼去接收數據和返回數據。
/// ----- MyServiceListener.h ----- @interface MyServiceListener : NSObject //開啓服務 - (void)start; @end /// ----- MyServiceListener.m ----- #import "MyServiceListener.h" #import "GCDAsyncSocket.h" /** * 服務的監聽者(服務端監聽客戶端鏈接) */ @interface MyServiceListener()<GCDAsyncSocketDelegate> /** 保存服務端的Socket對象 */ @property (nonatomic, strong) GCDAsyncSocket *serviceSocket; /** 保存客戶端的全部Socket對象 */ @property (nonatomic, strong) NSMutableArray *clientSocketArr; @end @implementation MyServiceListener - (GCDAsyncSocket *)serviceSocket { if (!_serviceSocket) { //1.建立一個Socket對象 //serviceSocket 服務端的Socket只監聽 有沒有客戶端請求鏈接 //隊列:代理的方法在哪一個隊列裏調用 (子線程的隊列) _serviceSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)]; } return _serviceSocket; } - (NSMutableArray *)clientSocketArr { if(!_clientSocketArr) { _clientSocketArr = [NSMutableArray array]; } return _clientSocketArr; } - (void)start { //開啓10086服務:5288 //2.綁定端口 + 開啓監聽 NSError *error = nil; //框架裏的這個方法作了兩件事情:綁定端口和開啓監聽 [self.serviceSocket acceptOnPort:5288 error:&error]; if (!error) { NSLog(@"10086服務開啓成功!"); } else { //失敗的緣由是端口被其它程序佔用 NSLog(@"10086服務開啓失敗:%@", error); } } #pragma mark -- 實現代理的方法 若是有客戶端的Socket鏈接到服務器,就會調用這個方法。 - (void)socket:(GCDAsyncSocket *)serviceSocket didAcceptNewSocket:(GCDAsyncSocket *)clientSocket { static NSInteger index = 1; NSLog(@"客戶端【%ld】已鏈接到服務器!", index++); //1.保存客戶端的Socket(客戶端的Socket被釋放了,鏈接就會關閉) [self.clientSockets addObject:clientSocket]; //提供服務(客戶端一鏈接到服務器,就打印下面的內容) NSMutableString *serviceStr = [[NSMutableString alloc]init]; [serviceStr appendString:@"========歡迎來到10086在線服務========\n"]; [serviceStr appendString:@"請輸入下面的數字選擇服務...\n"]; [serviceStr appendString:@" [0] 在線充值\n"]; [serviceStr appendString:@" [1] 在線投訴\n"]; [serviceStr appendString:@" [2] 優惠信息\n"]; [serviceStr appendString:@" [3] special services\n"]; [serviceStr appendString:@" [4] 退出\n"]; [serviceStr appendString:@"=====================================\n"]; // 服務端給客戶端發送數據 [clientSocket writeData:[serviceStr dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0]; //2.監聽客戶端有沒有數據上傳 (參數1:超時時間,-1表明不超時) /** * timeout: 超時時間,-1 表明不超時 * tag:標識做用,如今不用就寫0 */ [clientSocket readDataWithTimeout:-1 tag:0]; } #pragma mark -- 服務器端 讀取 客戶端請求(發送)的數據。在服務端接收客戶端數據,這個方法會被調用 - (void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag { //1.獲取客戶端發送的數據 NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSInteger index = [self.clientSocketArr indexOfObject:clientSocket]; NSLog(@"接收到客戶端【%ld】發送的數據:%@", index + 1, str); //把字符串轉成數字 NSInteger num = [str integerValue]; NSString *responseStr = nil; //服務器對應的處理的結果 switch (num) { case 0: responseStr = @"在線充值服務暫停中...\n"; break; case 1: responseStr = @"在線投訴服務暫停中...\n"; break; case 2: responseStr = @"優惠信息沒有\n"; break; case 3: responseStr = @"沒有特殊服務\n"; break; case 4: responseStr = @"恭喜你退出成功!\n"; break; default: break; } //2.服務端處理請求,返回數據(data)給客戶端 [clientSocket writeData:[responseStr dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0]; //寫完數據後 判斷 if (num == 4) { //移除客戶端,就會關閉鏈接 [self.clientSockets removeObject:clientSocket]; } //因爲框架內部的實現,每次讀完數據後,都要調用一次監聽數據的方法(保證能接收到客戶端第二次上傳的數據) [clientSocket readDataWithTimeout:-1 tag:0]; } @end /// ----- ViewController.m ----- #import "ViewController.h" #import "MyServiceListener.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //1.建立一個服務監聽對象 MyServiceListener *listener = [[MyServiceListener alloc]init]; //2.開始監聽 [listener start]; //3.開啓主運行循環,讓服務不能停(服務器通常要永久開啓) [[NSRunLoop mainRunLoop] run]; } @end
Demo下載地址:https://github.com/borenfocus/Socket10086ServerDemo
/// MyService.h #import <Foundation/Foundation.h> @interface MyService : NSObject /** 開啓服務 */ - (void)startService; @end /// MyService.m #import "MyService.h" #import "GCDAsyncSocket.h" @interface MyService ()<GCDAsyncSocketDelegate> /** 保存服務端的Socket對象 */ @property (nonatomic, strong) GCDAsyncSocket *serviceSocket; /** 保存客戶端的全部Socket對象 */ @property (nonatomic, strong) NSMutableArray *clientSocketArr; @end @implementation MyService //開啓10086服務:5288 - (void)startService { NSError *error = nil; // 綁定端口 + 開啓監聽 [self.serviceSocket acceptOnPort:5288 error:&error]; if (!error) { NSLog(@"服務開啓成功!"); } else { NSLog(@"服務開啓失敗!"); } } #pragma mark -- 實現代理的方法 若是有客戶端的Socket鏈接到服務器,就會調用這個方法。 - (void)socket:(GCDAsyncSocket *)serviceSocket didAcceptNewSocket:(GCDAsyncSocket *)clientSocket { // 客戶端的端口號是系統分配的,服務端的端口號是咱們本身分配的 NSLog(@"客戶端【Host:%@, Port:%d】已鏈接到服務器!", clientSocket.connectedHost, clientSocket.connectedPort); //1.保存客戶端的Socket(客戶端的Socket被釋放了,鏈接就會關閉) [self.clientSocketArr addObject:clientSocket]; //2.監聽客戶端有沒有數據上傳 (參數1:超時時間,-1表明不超時;參數2:標識做用,如今不用就寫0) [clientSocket readDataWithTimeout:-1 tag:0]; } #pragma mark -- 服務器端 讀取 客戶端請求(發送)的數據。在服務端接收客戶端數據,這個方法會被調用 - (void)socket:(GCDAsyncSocket *)clientSocket didReadData:(NSData *)data withTag:(long)tag { //1.獲取客戶端發送的數據 NSString *messageStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"接收到客戶端【Host:%@, Port:%d】發送的數據:%@", clientSocket.connectedHost, clientSocket.connectedPort, messageStr); // 遍歷客戶端數組 for (GCDAsyncSocket *socket in self.clientSocketArr) { if (socket != clientSocket) { // 不轉發給本身 //2.服務端把收到的消息轉發給其它客戶端 [socket writeData:data withTimeout:-1 tag:0]; } } //因爲框架內部的實現,每次讀完數據後,都要調用一次監聽數據的方法(保證能接收到客戶端第二次上傳的數據) [clientSocket readDataWithTimeout:-1 tag:0]; } - (GCDAsyncSocket *)serviceSocket { if (!_serviceSocket) { // 1.建立一個Socket對象 // serviceSocket 服務端的Socket只監聽 有沒有客戶端請求鏈接 // 隊列:代理的方法在哪一個隊列裏調用 (子線程的隊列) _serviceSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)]; } return _serviceSocket; } - (NSMutableArray *)clientSocketArr { if (!_clientSocketArr) { _clientSocketArr = [[NSMutableArray alloc]init]; } return _clientSocketArr; } @end /// main.m #import <Foundation/Foundation.h> #import "MyService.h" int main(int argc, const char * argv[]) { @autoreleasepool { //1.建立一個服務監聽對象 MyService *service = [[MyService alloc]init]; //2.開始監聽 [service startService]; //3.開啓主運行循環,讓服務不能停(服務器通常要永久開啓) [[NSRunLoop mainRunLoop] run]; } return 0; }
Demo下載地址:https://github.com/borenfocus/SocketGroupServerDemo
/// ViewController.m #import "ViewController.h" #import "GCDAsyncSocket.h" @interface ViewController ()<UITableViewDataSource, GCDAsyncSocketDelegate> @property (weak, nonatomic) IBOutlet UITableView *tableView; @property (weak, nonatomic) IBOutlet UITextField *textField; @property (nonatomic, strong) GCDAsyncSocket *clientSocket; @property (nonatomic, strong) NSMutableArray *dataArr; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 實現聊天室 // 1. 鏈接到服務器 NSError *error = nil; [self.clientSocket connectToHost:@"192.168.1.95" onPort:5288 error:&error]; if (error) { NSLog(@"error:%@", error); } } #pragma mark - GCDAsyncSocketDelegate - (void)socket:(GCDAsyncSocket *)clientSock didConnectToHost:(NSString *)host port:(uint16_t)port { NSLog(@"與服務器鏈接成功!"); // 監聽讀取數據(在讀數據的時候,要監聽有沒有數據可讀,目的是保證數據讀取到) [clientSock readDataWithTimeout:-1 tag:0]; } - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err { NSLog(@"與服務器斷開鏈接:%@", err); } // 讀取數據(接收消息) - (void)socket:(GCDAsyncSocket *)clientSock didReadData:(NSData *)data withTag:(long)tag { NSString *messageStr = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"接收到消息:%@", messageStr); messageStr = [NSString stringWithFormat:@"【匿名】:%@", messageStr]; [self.dataArr addObject:messageStr]; // 刷新UI要在主線程 dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView reloadData]; }); // 監聽讀取數據(讀完數據後,繼續監聽有沒有數據可讀,目的是保證下一次數據能夠讀取到) [clientSock readDataWithTimeout:-1 tag:0]; } #pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.dataArr.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; cell.textLabel.text = self.dataArr[indexPath.row]; return cell; } - (IBAction)clickSenderBtn:(UIButton *)sender { NSLog(@"發送消息"); [self.view endEditing:YES]; NSString *senderStr = self.textField.text; if (senderStr.length == 0) { return; } // 發送數據 [self.clientSocket writeData:[senderStr dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0]; senderStr = [NSString stringWithFormat:@"【我】:%@", senderStr]; [self.dataArr addObject:senderStr]; [self.tableView reloadData]; } - (GCDAsyncSocket *)clientSocket { if (!_clientSocket) { _clientSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)]; } return _clientSocket; } - (NSMutableArray *)dataArr { if (!_dataArr) { _dataArr = [[NSMutableArray alloc]init]; } return _dataArr; } @end
Demo下載地址:https://github.com/borenfocus/SocketGroupClientDemo
長鏈接和短鏈接:是鏈接的一個保存狀態(保存時間),長鏈接就是長時間鏈接,短鏈接就是短期鏈接。
Socket層上的協議:指的數據傳輸的格式。
1. HTTP協議:定義在網絡上數據傳輸的一種格式。
傳輸格式:假設:這是假設,實際http的格式不是這樣的。
http1.1,content-type:multipart/form-data,content-length:188,body:username=zhangsan&password=123456
2. XMPP協議:是一款即時通信協議 (別人定義好的協議,咱們常常拿來用)
是基於可擴展標記語言(XML)的協議,它用於即時消息(IM)以及在線現場探測。
傳輸格式:
<from>zhangsan<from>
<to>lisi<to>
<body>一塊兒吃晚上</body>
3. 自定義即時通信協議,json格式。
{
"from": "zhangsan",
"to": "lisi",
"body": "中午一塊兒吃飯",
}
你作什麼操做,必需要有一個固定的格式,這樣服務器才知道你要作什麼。
舉例:寫一封信給北京好友(區別 TCP/UDP 與 HTTP/XMMP)