首先理一下网络通讯三要素
- IP地址(主机名)
- 网络设备标示
- 本地回环地址:127.0.0.1 主机名:localHost
- 端口号
- 用于标识进程的逻辑地址,不同进程的标示
- 有效端口:0 ~ 65535
- 其中 0 ~ 1024 由系统使用或者保留端口
- 传输协议(通讯的规则)
- TCP
- UDP
1.Socket到底是什么东西?以下这幅流程图表示运行过程
总结:socket其实就是:用来操作某个IP上的某个端口来达到点对点通讯效果,本身就是一个抽象的东东,通讯就是通过socket来交互,可以面向TCP和UDP两种连接
TCP 采用三次握手来创建连接,是可靠连接,是长连接
分手是四次分手,客户端向服务器发送分手,会经过两次分手;服务器端向客户端发送分手,也会经过两次分手UDP 是不可靠连接,发送的东西是不管到达的
2.Socket终端使用案例:
- 新建两个终端控制台
- 第一台输入命令: nc -l 6969 (开启nc命令,使用6969端口,充当服务器端)
- 第二台输入命令:nc 127.0.0.1 6969 (两台的端口保持一致,充当客户端)
- 最后两台终端就可以实现通讯了,可以相互发送消息
以上案列采用的 “nc”命令,可以用来检测本地GCP连接
3.Socket 代码实际运用
socket是成双成对的出现
- 导入框架
#import <sys/socket.h>
#import <arpa/inet.h>
#import <netinet/in.h>
- 定义端口号和ip
static const char *serve_ip = "127.0.0.1"; //ip
static const short serve_port = 6969; //port
3.定义socket
@property (nonatomic,assign) int clientSockt; //定义socket
/** */
@property (nonatomic,strong) UITextField *msgTextFiled; //创建消息文本
/** */
@property (nonatomic,strong) NSThread *thread;
- 创建socket , 连接服务器端
//创建socket
-(void)initSockt{
/**
参数
domain : 协议域 ,AF_INET ->IPV4
type: Socket 类型,SOCK_STREAM(流/TCP)/SOCK_DGRAM(报文/UDP)
protocol: IPPROTO_TCP(如果写0,那么就会自动选择)
返回值
socket
*/
_clientSockt = socket(AF_INET, SOCK_STREAM, 0);
//连接IP地址和服务端
struct sockaddr_in serverAddr;
//赋值长度
serverAddr.sin_len = sizeof(serverAddr);
serverAddr.sin_family = AF_INET;
//IP地址
inet_aton(serve_ip, &serverAddr.sin_addr);
//端口号
serverAddr.sin_port = htons(serve_port);
int connetResult = connect(_clientSockt,(const struct sockaddr *)&serverAddr, sizeof(serverAddr));
if (connetResult == 0) {
NSLog(@"连接成功");
//开启一个线程模拟
self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(receveAction) object:nil];
[self.thread start];
}
else{
NSLog(@"连接失败 %d",connetResult);
return;
}
}
- 接收消息
-(void)receveAction{
/**
参数
1.客户端socket
2.接受内容缓冲区地址
3.接受内容缓存区长度
4.接受方式,0表示阻塞,必须等待服务器返回数据
返回值
如果超过,则返回读入的字节数,失败则返回SOCKET_ERROR
*/
while (1) {
uint8_t buffer[1024];//空间,准备数据
size_t recvlen = recv(_clientSockt, buffer, sizeof(buffer), 0);//等待着获的数据
NSLog(@"接受到多少数据%ld",recvlen);
//解析数据(获取到的服务器数据)
NSData *date = [NSData dataWithBytes:buffer length:recvlen];
NSString *str = [[NSString alloc]initWithData:date encoding:NSUTF8StringEncoding];
NSLog(@"接收到的内容:%@",str);
}
}
- 接下来,我们来利用终端来模拟服务器,向我们的app发送消息,上面我们已经说过,怎么使用终端来建立一个服务器端口,建立连接。好了,这个时候,把我们的App跑起来吧,看看有什么神奇的效果
在终端输入:
nc -l 6969 //这是建立服务器端口,再把APP跑起来,这时应该就会发现控制台打印“连接成功”。这时,就可以任意发送消息了
123456 //这是发送的消息
1213131SAS //这是发送的消息
这时,在app控制台打印出效果
2018-07-12 11:26:12.459423+0800 Socket[1232:116451] 连接成功
2018-07-12 11:26:23.504420+0800 Socket[1232:116570] 接受到多少数据7
2018-07-12 11:26:23.504659+0800 Socket[1232:116570] 接收到的内容:123456
2018-07-12 11:26:26.944561+0800 Socket[1232:116570] 接受到多少数据11
2018-07-12 11:26:26.944768+0800 Socket[1232:116570] 接收到的内容:1213131SAS
ok,一切尽在不言中
- 接受消息,我们这边已经搞定,那么接下来我们再来搞定发送消息
首先创建一个按钮、一个输入文本框,点击按钮,发送文本框内的字符串,调用以下方法,实现发送
-(void)sendAction:(NSString *)msg{
//3.发送数据
/**
参数
1.客户端socket
2.发送内容地址(指针)
3.发送内容长度
4.发送内容标志,一般为0
返回值
如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
*/
//void * 表示万能指针(以下两种都可以)
// NSString *msg = @"hello";
// const char * msg = @"hello";
ssize_t sendLen = send(_clientSockt, msg.UTF8String, strlen(msg.UTF8String), 0);
NSLog(@"发送了 %ld字节",sendLen);
}
发送成功后,这时在终端上就显示出了,刚刚发送的消息
- 正所谓,有始就有终,我们还剩下最后一步,那就是断开连接,如果不用了,就得把链接断开,节约资源
-(void)disConnection{
//关闭数据连接
[self.thread cancel];
close(_clientSockt);
}
4. socket 实际运用
是实际开发过程中,对于一般的小型聊天,都采用“环信、融云、LeanClould”,但是对于主供消息聊天的就不可能采用这些三方公司的,因为价钱太高,所以就会引用一些三方框架:
- 基本socket 原生, CocoaAsyncSocket
- 就是BSD Socket,处理了很多协议
- 是基于传输层封装的一套框架
- 基于WebSocket , SocketRocket,SRWebSocket
- 用于浏览器的长连接协议
- 基于MQIT, MQTTKit 、MQTTClient
- 适用订阅的消息机制,列如:滴滴打车地图上的小车轨迹
- 适用于物联网,传输层更小,低带宽,性能更优
- 位于应用层的聊天协议
- 基于XMPP,XMPPFrameWork
- 缺点,是基于XML的数据来传输的
- 位于应用层的聊天协议