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)