一、概念

  • Socket 字面意思又称“套接字”

  • 网络上的两个程序(如,客户端和服务器端)通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket。

  • 应用程序一般是先通过Socket来建立一个通信连接,再向网络发出请求或响应网络请求。

  

  说明:

    ☞ 客户端向服务器端发送网络请求前,必须要先在底层建立一个通信连接(通信管道),才能发送网络请求。

客户端向服务器端发送http请求,服务器返回数据,这个过程就是一个数据交换的过程。

客户端与服务器端进行数据交换,需要先建立一个双向的通信连接(即一条线、一个通道)

    ☞ 客户端和服务端 两端都有一个Socket,通过Socket建立一个连接(双向通信管道),有了管道就可以进行数据传输。

    ☞ Socket 就是通信管道的两个端口,可以理解为管道的入口/出口。

二、网络通信的要素

  网络上的请求就是通过Socket来建立连接然后互相通信

  1. IP地址(网络上主机设备的唯一标识)——>寻找服务器主机

  2. 端口号(定位程序) ——> 寻找程序

    • 用于标示进程的逻辑地址,不同进程的标示
    • 有效端口:0~65535,其中0~1024由系统使用或者保留端口,开发中建议使用1024以上的端口

  3. 传输协议(就是用什么样的方式进行交互)

    • 通讯的规则
    • 常见协议:TCP、UDP

三、传输协议 TCP/UDP

  TCP和UDP:数据传输的两种方式,即把数据从一端传到另一端的两种方式

  1. TCP(传输控制协议) —>要建立连接(如:发送HTTP请求,客户端向服务端发送网络请求)

☞ 建立连接,形成传输数据的通道

☞ 在连接中进行大数据传输(数据大小不受限制)

☞ 通过三次握手完成连接,是可靠协议,安全送达

        说明:在建立通信连接(打通管道)之前有三次握手,目的是为了数据的安全性和可靠性(让数据安全可靠的传输到对方)。

        举例:打电话 (理解三次握手)

第一次握手:拿起电话,进行拨号。这个拨号的过程称为第一次握手。【开始准备连接】

第二次握手:拨通了,对方"喂"了一声(响应了一声),我听到了,称为第二次握手。【说明我连接你 没问题】

第三次握手:我听到了对方"喂"了一声(响应了一声),我也习惯性的"喂"了一声,对方听到了。【说明你连接我 没问题】

如果这三个过程都没有问题,就可以确定通话连接建立成功。

    ☞ 必须建立连接,效率会稍低。(每次请求都要建立连接)

  2. UDP(用户数据报协议)—>不建立连接 (如:广播用这个,不断的发送数据包)

    ☞ 将 数据 及 源 和 目的 封装成数据包中,不需要建立连接

    ☞ 每个数据报的大小限制在64KB之内

    ☞ 因为无需连接,因此是不可靠协议

      举例:看老师广播讲课,网络卡主了,再看到的是最新的视频内容,不能接着看,可能错过了一些内容。

    ☞ 不需要建立连接,速度快 (省掉了三次握手操作)

四、Socket 通信流程图

☞ bind():绑定端口 (80、3306)

☞ listen():监听端口(服务器监听客户端有没有连接到这个端口来)

☞ accept():如果有连接到这个端口,就接收这个连接。(通信管道打通,接下来就可以传输数据了)

☞ write():发请求/写请求/发数据

☞ read():读请求/读数据

  • HTTP底层就是Socket通信,通过Socket建立连接(通信管道),实现数据传输,连接的方式(数据传输的方式)是TCP。
  • HTTP是一个TCP的传输协议(方式),它是一个可靠、安全的协议。

五、体验 Socket

  实现Socket服务端监听:

1)使用C语言实现。

2)使用 CocoaAsyncSocket 第三方框架(OC),内部是对C的封装。

    telnet命令:是连接服务器上的某个端口对应的服务。

    telnet命令:telnet host port

      如:telnet www.baidu.com 80  (IP地址和域名一样,都能找到主机。)

1. 【案例】写个10086服务,体验客户端与服务端的Socket通信

☞ 自己写一个服务端,用终端代替客户端来演示

☞ 掌握:通过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(, )];
}
return _serviceSocket;
} - (NSMutableArray *)clientSocketArr {
if(!_clientSocketArr) {
_clientSocketArr = [NSMutableArray array];
}
return _clientSocketArr;
} - (void)start {
//开启10086服务:5288
//2.绑定端口 + 开启监听
NSError *error = nil;
//框架里的这个方法做了两件事情:绑定端口和开启监听
[self.serviceSocket acceptOnPort: error:&error];
if (!error) {
NSLog(@"10086服务开启成功!");
} else {
//失败的原因是端口被其它程序占用
NSLog(@"10086服务开启失败:%@", error);
}
} #pragma mark -- 实现代理的方法 如果有客户端的Socket连接到服务器,就会调用这个方法。
- (void)socket:(GCDAsyncSocket *)serviceSocket didAcceptNewSocket:(GCDAsyncSocket *)clientSocket {
static NSInteger index = ;
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:- tag:]; //2.监听客户端有没有数据上传 (参数1:超时时间,-1代表不超时)
/**
* timeout: 超时时间,-1 代表不超时
* tag:标识作用,现在不用就写0
*/
[clientSocket readDataWithTimeout:- tag:];
} #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 + , str);
//把字符串转成数字
NSInteger num = [str integerValue];
NSString *responseStr = nil;
//服务器对应的处理的结果
switch (num) {
case :
responseStr = @"在线充值服务暂停中...\n";
break;
case :
responseStr = @"在线投诉服务暂停中...\n";
break;
case :
responseStr = @"优惠信息没有\n";
break;
case :
responseStr = @"没有特殊服务\n";
break;
case :
responseStr = @"恭喜你退出成功!\n";
break;
default:
break;
} //2.服务端处理请求,返回数据(data)给客户端
[clientSocket writeData:[responseStr dataUsingEncoding:NSUTF8StringEncoding] withTimeout:- tag:];
//写完数据后 判断
if (num == ) {
//移除客户端,就会关闭连接
[self.clientSockets removeObject:clientSocket];
} //由于框架内部的实现,每次读完数据后,都要调用一次监听数据的方法(保证能接收到客户端第二次上传的数据)
[clientSocket readDataWithTimeout:- tag:]; }
@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

☞ 体验Socket通信-服务端简单实现代码:

Demo下载地址:https://github.com/borenfocus/Socket10086ServerDemo

2. 【案例扩展】写个转发消息服务(群聊服务端)

  • 多个客户端连接到服务器。
  • 当一个客户端发送消息给服务器时,服务器转发给其它已经连接的客户端。
  • 相当于一个群聊的雏形。

  

/// 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: 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:- tag:];
} #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:- tag:];
}
}
//由于框架内部的实现,每次读完数据后,都要调用一次监听数据的方法(保证能接收到客户端第二次上传的数据)
[clientSocket readDataWithTimeout:- tag:];
} - (GCDAsyncSocket *)serviceSocket {
if (!_serviceSocket) {
// 1.创建一个Socket对象
// serviceSocket 服务端的Socket只监听 有没有客户端请求连接
// 队列:代理的方法在哪个队列里调用 (子线程的队列)
_serviceSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_global_queue(, )];
}
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 ;
}

☞ 体验Socket通信-群聊服务端实现代码:

Demo下载地址:https://github.com/borenfocus/SocketGroupServerDemo

3. 【案例】体验Socket通信-群聊客户端实现

  

  

///  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: error:&error];
if (error) {
NSLog(@"error:%@", error);
}
} #pragma mark - GCDAsyncSocketDelegate
- (void)socket:(GCDAsyncSocket *)clientSock didConnectToHost:(NSString *)host port:(uint16_t)port {
NSLog(@"与服务器连接成功!");
// 监听读取数据(在读数据的时候,要监听有没有数据可读,目的是保证数据读取到)
[clientSock readDataWithTimeout:- tag:];
} - (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:- tag:];
} #pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return ;
} - (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 == ) {
return;
}
// 发送数据
[self.clientSocket writeData:[senderStr dataUsingEncoding:NSUTF8StringEncoding] withTimeout:- tag:]; 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(, )];
}
return _clientSocket;
} - (NSMutableArray *)dataArr {
if (!_dataArr) {
_dataArr = [[NSMutableArray alloc]init];
}
return _dataArr;
} @end

☞ 体验Socket通信-群聊客户端实现:

Demo下载地址:https://github.com/borenfocus/SocketGroupClientDemo

六、长连接和短连接

  长连接和短连接:是连接的一个保存状态(保存时间),长连接就是长时间连接,短连接就是短时间连接。

  • http网络请求是短连接。
  • 长连接用在即时通信(实时聊天,要随时随地的发送信息,考虑到性能,用长连接)

七、Socket 层上的协议

  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)

  • 数据传输的方式:TCP/UDP —》相当于 EMS/顺丰/申通/中通
  • 数据传输的格式:HTTP/XMMP —》相当于 信的内容格式 (可以是中文/英文/…等)

【iOS干货】☞ Socket的更多相关文章

  1. iOS的socket开发基础

    目录[-] socket简介 tcp和udp的区别 TCP三次握手和四次挥手 TCP三次握手 tcp四次挥手 tcpsocket和udpsocket的具体实现 tcpsocket的具体实现 udpso ...

  2. iOS 使用 socket 即时通信(非第三方库)

    其实写这个socket一开始我是拒绝的. 因为大家学C 语言和linux基础时肯定都有接触,客户端和服务端的通信也都了解过,加上现在很多开放的第三方库都不需要我们来操作底层的通信. 但是来了!!! 但 ...

  3. iOS 处理socket粘包问题

    1.什么是粘包? 粘包通常出现在TCP的协议里面,对于UDP来说是不会出现粘包状况的,之所以出现这种状况的原因,涉及到一种名为Nagle的算法. Nagle算法通过减少必须发送的封包的数量,提高网络应 ...

  4. (六十五)iOS的socket实现(GCDAsyncSocket)

    本文介绍使用GCDAsyncSocket来实现iOS端的socket,有关简易服务端的代码已经在上一篇文章中提到,这里不再赘述,将直接介绍如何实现客户端. 首先下载CocoaAsyncSocket框架 ...

  5. (六十四)iOS的socket实现(C+OC混合实现)

    对于微博.微信朋友圈之类的网络通信,使用JSON完全可以满足需求,但是如果要制作网络游戏,就需要建立一个持久连接,这时候就要考虑使用socket. 在iOS上实现socket大体有两种方法,一是借助自 ...

  6. iOS开发— Socket编程

    Socket编程 一.了解网络各个协议:TCP/IP.SOCKET.HTTP等 网络七层由下往上分别为物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. 其中物理层.数据链路层和网络层通常被 ...

  7. IOS — 关于Socket传输文件需要自定义延时或者包大小的情况

    1. 首先导入头文件 #include <stdio.h> #include <errno.h> #include <string.h> #include < ...

  8. iOS 基于Socket 的 C/S 网络通信结构(下一个)

    以前实现简单 Server 程序,服务端通过 void WriteStreamClientCallBack(CFWriteStreamRef stream, CFStreamEventType eve ...

  9. iOS 之 socket 与 http

    http连接:短连接,发送一次请求,服务器响应后连接就断开. socket连接:长连接,连接后长期保持连接状态.

随机推荐

  1. Zepto源码分析-架构

    构造函数 Zepto.js 是专门为智能手机浏览器推出的javascript库, 拥有与和jQuery相似的语法. 它的优点是精简,压缩后5-10K. 不支持IE MIT开源协议 结构   http: ...

  2. 搭建Elasticsearch集群常见问题

    一.ES安装方法: Linux用户登录(bae),我们用的是5.3版本的包.从官网下载: curl -L -O https://artifacts.elastic.co/downloads/elast ...

  3. 一天搞定CSS:盒模型content、padding、border、margin--06

    1.盒模型 网页设计中常听的属性名:内容(content).填充(padding).边框(border).边界(margin), CSS盒子模式都具备这些属性. 这些属性我们可以用日常生活中的常见事物 ...

  4. Python爬知乎妹子都爱取啥名

    闲来无事上知乎,看到好多妹子,于是抓取一波. 有没有兴趣?? 目标网址https://www.zhihu.com/collection/78172986 抓取分析 爬取分析 使用pandas操作文件 ...

  5. 如何按内容筛选dom

    有时候我们需要按照dom的text内容去筛选,那么可以用jQuery的contains筛选 写法 $("div:contains(aaa)") 筛选出内容包含aaa的div 另外 ...

  6. 使用wget下载JDK8

    每次去官网下载JDK有点烦 但是直接使用wget 又得同意协议所以 使用如下的wget就好了(注意是64位的哦) 先去官网看一下地址变化 没有如下 :修改后面的下载地址即可 注意哦~ 2.然后使用下面 ...

  7. CRL快速开发框架4.4版发布,支持主从读写分离

    经过一些调整和优化,4.3已经运行在生产环境,对于不久将会遇到的查询性能,读写分离需求列上日程 读写分离需求 对于一个数据库作了主从发布/订阅,主库为DB1,从库为DB2 所有写入通过DB1,所有查询 ...

  8. Visual studio常用的code snippets

    作为全球第一的IDE,VS用起来自然相当的爽,当你在visual studio里敲出几个字母,能帮你生成一大段代码,省时省力又能装逼. 比如,你打一个 prop,然后按tab键,就能生成一个带get/ ...

  9. Spring学习(14)--- 基于Java类的配置Bean 之 @ImportResource & @Value 注解

    学习如何使用@ImportResource 和 @Value 注解进行资源文件读取 例子: 先创建一个MyDriverManager类(模拟读取数据库配置信息) package com.beanann ...

  10. 限制容器对CPU的使用 - 每天5分钟玩转 Docker 容器技术(28)

    上节学习了如何限制容器对内存的使用,本节我们来看CPU. 默认设置下,所有容器可以平等地使用 host CPU 资源并且没有限制. Docker 可以通过 -c 或 --cpu-shares 设置容器 ...