iOS开发系列iOS Instant Messaging

iOS即时通讯(1)--Socket

2015-09-20  本文已影响1094人  月夜胜邪

Socket简介


Socket是应用层和TCP/IP协议族通信的中间软件抽象层,它是一组接口。

TCP和UDP的区别

TCP:面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立连接需要开销较多(时间,系统资源)。

UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快。

TCP的三次握手和四次握手

TCP三次握手

所谓三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。
三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时。将触发三次握手。



首先了解一下几个标志,SYN(synchronous),同步标志,ACK (Acknowledgement),即确认标志,seq应该是Sequence Number,序列号的意思,另外还有四次握手的fin,应该是final,表示结束标志。
第一次握手:客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,以及初始序号X,保存在包头的序列号(Sequence Number)字段里。
第二次握手:服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的序列号加1以,即X+1。
第三次握手:客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1。并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写序列号的+1。

TCP四次握手

TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。



其实有个问题,为什么连接的时候是三次握手,关闭的时候却是四次挥手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

TCPSocket和UDPSocket的具体实现

基本TCP客户—服务器程序设计基本框架基本TCP客户—服务器程序设计基本框架
基本UDP客户—服务器程序设计基本框架流程图基本UDP客户—服务器程序设计基本框架流程图

Socket常用函数:
创建套接字Socket(af, type, protocol)
建立地址和套接字的联系bind(sockid, local addr, addrlen)
服务器端侦听客户端的请求listen(sockid, quenlen)

建立服务器/客户端的连接(面向连接TCP)
客户端请求连接Connect(sockid, destaddr, addrlen)
服务器端等待从编号为Sockid的Socket上接收客户连接请求newsockid=accept(Sockid,Clientaddr, paddrlen)

发送/接收数据
面向连接:

send(sockid, buff, bufflen)
recv()

面向无连接:

sendto(sockid, buff, …, addrlen)
recvfrom()

释放套接字close(sockid)

CocoaAsyncSocket的学习

CocoaAsyncSocket GitHub

初始化环境

RunLoop文件夹下加入工程,添加CFNetwork.framework,再导入以下文件头:

#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <unistd.h>
详细使用

一个iOS项目一般只有一个正在连接的Socket,所以需要使用单例或者AppDelegate数据共享,在使用结束后,要将Socket关闭,减轻服务器负荷。
单例类:

#import <Foundation/Foundation.h>
#import "AsyncSocket.h"

#define DEFINE_SHARED_INSTANCE_USING_BLOCK(block) \
static dispatch_once_t onceToken = 0; \
__strong static id sharedInstance = nil; \
dispatch_once(&onceToken, ^{ \
sharedInstance = block(); \
}); \
return sharedInstance; \

enum{
    SocketOfflineByServer, //服务器掉线,默认为0
    SocketOfflineByUser, //用户主动cut
};

@interface Singleton : NSObject <AsyncSocketDelegate>

@property (nonatomic, strong) AsyncSocket *socket; //socket
@property (nonatomic, copy) NSString *socketHost; //socket的Host
@property (nonatomic, assign) UInt16 socketPort; //socket的prot
@property (nonatomic, retain) NSTimer *connectTimer; //计时器

+ (Singleton *)sharedInstance;
-(void)socketConnectHost;// socket连接
-(void)cutOffSocket;// 断开socket连接

@end
#import "Singleton.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <unistd.h>

@implementation Singleton
+ (Singleton *)sharedInstance{
    static Singleton *sharedInstace = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstace = [[self alloc] init];
    });
    return sharedInstace;
}

// socket连接
- (void)socketConnectHost{
    self.socket    = [[AsyncSocket alloc] initWithDelegate:self];
    NSError *error = nil;
    [self.socket connectToHost:self.socketHost onPort:self.socketPort withTimeout:3 error:&error];
}

// 连接成功回调
#pragma mark  - 连接成功回调
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port{
    NSLog(@"socket连接成功");
    self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(longConnectToSocket) userInfo:nil repeats:YES];
    [self.connectTimer fire];
}

// 心跳连接
- (void)longConnectToSocket{
    // 根据服务器要求发送固定格式的数据,假设为指令@"longConnect",但是一般不会是这么简单的指令
    NSString *longConnect = @"longConnect";
    NSData *dataStream = [longConnect dataUsingEncoding:NSUTF8StringEncoding];
    [self.socket writeData:dataStream withTimeout:1 tag:1];
}

// 切断socket
- (void)cutOffSocket{
    self.socket.userData = SocketOfflineByUser;
    [self.connectTimer invalidate];
    [self.socket disconnect];
}

- (void)onSocketDidDisconnect:(AsyncSocket *)sock{
    NSLog(@"sorry the connect is failure %ld",sock.userData);
    if (sock.userData == SocketOfflineByServer) {
        // 服务器掉线,重连
        [self socketConnectHost];
    }
    else if (sock.userData == SocketOfflineByUser) {
        // 如果由用户断开,不进行重连
        return;
    }
}

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    // 对得到的data值进行解析与转换即可
    [self.socket readDataWithTimeout:30 tag:0];
}

@end
简单的使用
    [Singleton sharedInstance].socketHost = @"192.186.100.21";// host设定
    [Singleton sharedInstance].socketPort = 10045;// port设定

    // 在连接前先进行手动断开
    [Singleton sharedInstance].socket.userData = SocketOfflineByUser;
    [[Singleton sharedInstance] cutOffSocket];

    // 确保断开后再连,如果对一个正处于连接状态的socket进行连接,会出现崩溃
    [Singleton sharedInstance].socket.userData = SocketOfflineByServer;
    [[Singleton sharedInstance] socketConnectHost];
上一篇下一篇

猜你喜欢

热点阅读