TCP 看我就够了

TCP的初识

TCP 是一种面向连接的,可靠的,基于字节流的传输层通信协议.TCP工作在网络OSI七层模型中的第四层-传输层,下面一张图展示OSI七层模型及每一层的作用和对应的协议.

图.png

TCP是传输层协议,在进行数据传输之前使用三次握手协议建立连接,大体的过程是客户端发出SYN连接请求后,服务端接收请求后应答SYN+ACK,客户端收到服务端应答后应答ACK,这种建立连接的方法可以防止产生错误的连接,防止已失效的连接请求报文段突然又传送到了服务端。TCP三次握手过程图示如下:

图片.png

TCP三次握手过程描述如下:

1.客户端发送SYN标志位为1,Sequence Number为x的连接请求报文段,然后客户端进入SYN_SEND状态,等待服务器的确认响应; 2.服务器收到客户端的连接请求,对这个SYN报文段进行确认,然后发送Acknowledgment Number为x+1(Sequence Number+1),SYN标志位和ACK标志位均为1,Sequence Number为y的报文段(即SYN+ACK报文段)给客户端,此时服务器进入SYN_RECV状态; 3.客户端收到服务器的SYN+ACK报文段,确认ACK后,发送Acknowledgment Number为y+1,SYN标志位为0,ACK标志位为1的报文段,发送完成后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手,客户端和服务器端成功地建立连接,可以开始传输数据了。

当数据传送完成后,为了正确完整的完成数据传输,需要经过四次挥手断开连接。TCP四次挥过程图示如下:

图.png

TCP四次挥手过程描述如下:

1.客户端发送Sequence Number为x+2,Acknowledgment Number为y+1的FIN报文段,客户端进入FIN_WAIT_1状态,即告诉服务端没有数据需要传输了,请求关闭连接; 2.服务端收到客户端的FIN报文段后,向客户端应答一个Acknowledgment Number为Sequence Number+1的ACK报文段,即应答客户端你的请求我收到了,但是我还没准备好,请等待我的关闭请求。客户端收到后进入FIN_WAIT_2状态; 3.服务端完成数据传输后向客户端发送Sequence Number为y+1的FIN报文段,请求关闭连接,服务器进入LAST_ACK状态; 4.客户端收到服务端的FIN报文段后,向服务端应答一个Acknowledgment Number为Sequence Number+1的ACK报文段,然后客户端进入TIME_WAIT状态;服务端收到客户端的ACK报文段后关闭连接进入CLOSED状态,客户端等待2MSL后依然没有收到回复,则证明服务端已正常关闭,客户端此时关闭连接进入CLOSED状态。

TCP的使用

上面的那些都是理论的知识,在我们实际应用中不必过分钻研(当然除了你本来就是研究这个的或者你很感兴趣),我们要做的,要学习的就是怎么在项目中使用它,下面我就先讲一下我在项目中的使用以及遇到的问题. * 我们的需求:在我们的项目中有一个微课模块,我们的需求就是要做到当老师或者管理员进入微课的时候能够通知到所有人,针对这个问题,我跟总监经过讨论,决定使用TCP.(至于为什么不走IM自定义消息就不在累述) * 我们的实现:我们使用Socket来完成的TCP链接 ,服务端是用MINA2搭建,IOS 使用CocoaAsyncSocket,安卓也是用的MINA2 其实在这里有些人还搞不清楚什么的TCP 什么是UDP 什么是HTTP 什么是Socket,那我就大概说下我的理解: # socket是对TCP/IP协议的封装和应用(程序员层面上)。也可以说,TPC/IP协议是传输层协议,主要解决数据 如何在网络中传输,HTTP是应用层协议,主要解决如何包装数据。socket是让我们更简单的使用TCP/IP协议

我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如 果没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用到应用层协议,应用层协议有很多,比如HTTP、FTP、TELNET等,也 可以自己定义应用层协议。WEB使用HTTP协议作应用层协议,以封装HTTP文本信息,然后使用TCP/IP做传输层协议将它发到网络上。实际上socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。 实际上,Socket跟TCP/IP协议没有必然的联系。Socket编程接口在设计的时候,就希望也能适应其他的网络协议。所以说,Socket的出现 只是使得程序员更方便地使用TCP/IP协议栈而已,是对TCP/IP协议的抽象,从而形成了我们知道的一些最基本的函数接口,比如create、 listen、connect、accept、send、read和write等等。

在这里我就着重讲下IOS端的使用和问题

使用到的是CocoaAsyncSocket 中的GCDAsyncSocket (当然CocoaAsyncSocket里也有创建UDP的就不累述)

  • 创建链接 以及对应的回调

//建立链接 TcpClient *tcp = [TcpClient sharedInstance]; [tcp setDelegate_ITcpClient:self]; if(tcp.asyncSocket.isConnected) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:@"网络已经连接好啦!" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil]; [alert show]; }else { [tcp openTcpConnection:HOST port:[PORT intValue]]; } 这里的TcpClient 是拥有GCDAsyncSocket属性的单例 从中可以看到连接的时候只是需要HOST 和 port 就是地址和端口

   - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
    {
     DLog(@"链接成功啦socket:%p didConnectToHost:%@ port:%hu", sock, host, port);
    [[NSNotificationCenter defaultCenter] postNotificationName:@"didConnectToHost" object:nil userInfo:nil];
    if ([itcpClient respondsToSelector:@selector(didConnectToHost)]) {
        [itcpClient didConnectToHost];
    }
    [self read];
    } 


   - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
     {

     if (err) {
    DLog(@"连接失败");
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([itcpClient respondsToSelector:@selector(OnConnectionError:)]) {
            [itcpClient OnConnectionError:err];
        }
        
    });
     }else{
    DLog(@"正常断开");
   }
   } 
  • 发送消息 // 进入微课 NSDictionary *params = @{@"requestCode":@"10001",@"token":[LoginDataHelper shareInstance].userInfo.token,@"cId":self.model.cId}; NSString *json = [params JSONString]; NSString *strn = [NSString stringWithFormat:@"%@\n",json]; [tcp writeString:strn]; // TcpClient 中的方法 -(void)writeString:(NSString*)datastr; { NSString *requestStr = [NSString stringWithFormat:@"%@",datastr]; NSData *requestData = [requestStr dataUsingEncoding:NSUTF8StringEncoding]; [self writeData:requestData]; } -(void)writeData:(NSData*)data; { TAG_SEND++; [asyncSocket writeData:data withTimeout:-1. tag:TAG_SEND]; } 当然发送消息也有对应的 回调 - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag { DLog(@"发送消息socket:%p didWriteDataWithTag:%ld", sock, tag); [[NSNotificationCenter defaultCenter] postNotificationName:@"didWriteDataWithTag" object:nil userInfo:nil]; dispatch_async(dispatch_get_main_queue(), ^{ if ([itcpClient respondsToSelector:@selector(OnSendDataSuccess:)]) { [itcpClient OnSendDataSuccess:[NSString stringWithFormat:@"tag:%li",tag]]; } }); }
  • 收到服务器的消息
 - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
    {
DLog(@"收到消息啦socket:%p didReadData:withTag:%ld", sock, tag);
     NSString *httpResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    TAG_RECIVED = tag;
     NSDictionary *dic = [NSDictionary dictionaryWithJsonString:httpResponse];
     if (dic) {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"didReadData" object:nil userInfo:dic];
     if(![httpResponse isEqualToString:@""])
    [recivedArray addObject:httpResponse];
    dispatch_async(dispatch_get_main_queue(), ^{
    if ([itcpClient respondsToSelector:@selector(OnReciveData:)]) {
       [itcpClient OnReciveData:dic];
      }       
      });
    }
   [self read];
    } 

当然这里你们发送的消息和接收的消息,前后端要先针对其格式做好对接,定好格式,按照这个格式去发送和解析

  • 关于保活问题 TCP长时间处于非活动状态可能会被杀死,所以做好保活是很有必要的 这里我做的处理是创建心跳机制 发送心跳包 //心跳 { _heartTime = [NSTimer timerWithTimeInterval:50 target:self selector:@selector(reconnectTP) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:self.heartTime forMode:NSDefaultRunLoopMode]; [_heartTime fire]; } - (void)reconnectTP{ TcpClient *tcp = [TcpClient sharedInstance]; [tcp reconnect]; { TcpClient *tcp = [TcpClient sharedInstance]; if(tcp.asyncSocket.isDisconnected) { DLog(@"网络不通"); }else if(tcp.asyncSocket.isConnected) { NSDictionary *params = @{@"requestCode":@"10001",@"token":[LoginDataHelper shareInstance].userInfo.token,@"cId":self.posterModel.cId}; NSString *json = [params JSONString]; NSString *strn = [NSString stringWithFormat:@"%@\n",json]; [tcp writeString:strn]; }else{ DLog(@"TCP没有建立链接"); } } }

这里就是定时检测TCP是否在连线状态,如果不在就重连,如果在就发送心跳包给后台。从而保证TCP的活性

  • 中间出现过的问题 开始我们的TCP一直都很正常,但是在服务器集群之后就出现问题了,IOS怎么也接收不到服务器发送的消息,链接很正常就是收不到消息,但是安卓却没有任何问题,当初这个问题困扰我们了很久,大家都把责任推到IOS 这边,当时我也是倍感压力,很不解,为啥之前就行,集群之后就出现问题了呢,后来经过我不断地努力和测试才发现问题是: 服务端在发送消息之后并没有用\r\n 或者\n 作为结束标志,这在之前是没问题的,但是集群之后在Ruby语言里面就出现问题,没有结束标志,IOS这边就一直收不到消息。因为他一直认为在传送数据没有结束。 # 所以一定要在发送消息之后以\r\n或者\n 作为结束符,避免不必要的麻烦。

目前只想起来这些,至于其他问题,可以留言给我,我们公共探讨,也可以加我的Q:719967870,下面我贴出 基于GCDAsyncSocket封装的单例大家可以直接使用

    //  TcpClient.h
   //  ConnectTest
   //
  //  Created by  yuchen on 2016.

   #import <Foundation/Foundation.h>
   #import "GCDAsyncSocket.h"
   #import "ITcpClient.h"
          
@interface TcpClient : NSObject
  {
              long TAG_SEND;
              long TAG_RECIVED;
              id<ITcpClient> itcpClient;
              NSMutableArray *recivedArray;
  }

  @property (nonatomic,retain) GCDAsyncSocket *asyncSocket;
 + (TcpClient *)sharedInstance;
  -(void)setDelegate_ITcpClient:(id<ITcpClient>)_itcpClient;
  // 链接
  -(void)openTcpConnection:(NSString*)host port:(NSInteger)port;
  -(void)reconnect ;
  -(void)read;
  //发消息
  -(void)writeString:(NSString*)datastr;
  -(void)writeData:(NSData*)data;
  -(long)GetSendTag;
  -(long)GetRecivedTag;
  //断开
  -(void)disconnect;
   @end

//.m

 //  TcpClient.m
  //  ConnectTest
   //
   //  Created by  yuchen on 2016.
   //
  //
        
#import "TcpClient.h"
#import "GCDAsyncSocket.h"
#import "LZ_DevKit.h"
#import "NSDictionary+JSON.h"
@implementation TcpClient
@synthesize asyncSocket;

+ (TcpClient *)sharedInstance;
{
    static TcpClient *_sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[TcpClient alloc] init];
    });

    return _sharedInstance;
}


-(id)init;
{
    self = [super init];
    recivedArray = [NSMutableArray arrayWithCapacity:10];
    return self;
}

-(void)setDelegate_ITcpClient:(id<ITcpClient>)_itcpClient;
{
itcpClient = _itcpClient;
}

-(void)openTcpConnection:(NSString*)host port:(NSInteger)port;
{



//  dispatch_queue_create("bin.queue", DISPATCH_QUEUE_SERIAL);
//  dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_queue_t mainQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:mainQueue];

    [asyncSocket setAutoDisconnectOnClosedReadStream:NO];

      NSError *error = nil;
        if (![asyncSocket connectToHost:host onPort:port error:&error])
        {
        DLog(@"Error connecting: %@", error);

    }



}
-(void)disconnect{

    itcpClient = nil;
     [asyncSocket setDelegate:nil delegateQueue:NULL];
    [asyncSocket disconnect];




}

//  重新连接
-(void)reconnect {
    NSError* err;
    if([asyncSocket isDisconnected]) {
    
       BOOL  result = [asyncSocket connectToHost:HOST onPort:[PORT integerValue]  error:&err];
    
        if(result)
        {
            DLog(@"重新连接--主机%@-Port%@",HOST,PORT);
        
 
        }
        else {
            DLog(@"连接失败ERROR %@",[err description]);
        }
    
    }else{
        DLog(@"已经连接");
    }
}

-(void)writeString:(NSString*)datastr;
{
    NSString *requestStr = [NSString stringWithFormat:@"%@",datastr];

    NSData *requestData = [requestStr dataUsingEncoding:NSUTF8StringEncoding];
    [self writeData:requestData];
}

-(void)writeData:(NSData*)data;
{
    TAG_SEND++;
    [asyncSocket writeData:data withTimeout:-1. tag:TAG_SEND];
}

-(void)read;
{
    [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];

}

    -(long)GetSendTag;
{
    return TAG_SEND;
}

-(long)GetRecivedTag;
{
return TAG_RECIVED;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Socket Delegate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////?哈哈

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
    DLog(@"链接成功啦socket:%p didConnectToHost:%@ port:%hu", sock, host, port);
    [[NSNotificationCenter defaultCenter] postNotificationName:@"didConnectToHost" object:nil userInfo:nil];
    if ([itcpClient respondsToSelector:@selector(didConnectToHost)]) {
        [itcpClient didConnectToHost];
    }
    [self read];

    }
//是否加密
- (void)socketDidSecure:(GCDAsyncSocket *)sock
{
DLog(@"socketDidSecure:%p", sock);


NSString *requestStr = [NSString stringWithFormat:@"GET / HTTP/1.1\r\nHost: %@\r\n\r\n", HOST];
NSData *requestData = [requestStr dataUsingEncoding:NSUTF8StringEncoding];

    [sock writeData:requestData withTimeout:-1 tag:0];
    [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
}

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
DLog(@"发送消息socket:%p didWriteDataWithTag:%ld", sock, tag);
 [[NSNotificationCenter defaultCenter] postNotificationName:@"didWriteDataWithTag" object:nil userInfo:nil];
dispatch_async(dispatch_get_main_queue(), ^{
    if ([itcpClient respondsToSelector:@selector(OnSendDataSuccess:)]) {
    [itcpClient OnSendDataSuccess:[NSString stringWithFormat:@"tag:%li",tag]];
    }
  
});
}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    DLog(@"收到消息啦socket:%p didReadData:withTag:%ld", sock, tag);
 
    NSString *httpResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    TAG_RECIVED = tag;

    NSDictionary *dic = [NSDictionary dictionaryWithJsonString:httpResponse];
if (dic) {

    [[NSNotificationCenter defaultCenter] postNotificationName:@"didReadData" object:nil userInfo:dic];

    if(![httpResponse isEqualToString:@""])
    [recivedArray addObject:httpResponse];

    dispatch_async(dispatch_get_main_queue(), ^{
    if ([itcpClient respondsToSelector:@selector(OnReciveData:)]) {
       [itcpClient OnReciveData:dic];
        }
   
    });

    }
    [self read];


}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{

    if (err) {
    DLog(@"连接失败");
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([itcpClient respondsToSelector:@selector(OnConnectionError:)]) {
            [itcpClient OnConnectionError:err];
        }
        
    });
}else{
    DLog(@"正常断开");
    }

}

- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock
{

}
@end

CocoaAsyncSocket :https://github.com/robbiehanson/CocoaAsyncSocket

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏俗人笔记

PHP中检测IP是否是内网IP的三个方法

2623
来自专栏向治洪

android优化之省电

Android程序中耗电最多的地方在以下几个方面 : 1、 大数据量的传输。 2、 不停的在网络间切换。 3、 解析大量的文本数据。 那么我们怎么样来改...

19510
来自专栏CDN及云技术分享

Openssl状态机的实现

Openssl是通过“握手“建立加密信道,在该信道双方的身份都是合法的,并且传输数据都是密文传输。Openssl握手通过客户端和服务端互相交换信息计算出secr...

3703
来自专栏Golang语言社区

Golang:使用 httprouter 构建 API 服务器

我 10 个月前开始成为一名 Gopher,没有回头。像许多其他 gopher 一样,我很快发现简单的语言特性对于快速构建快速、可扩展的软件非常有用。当我刚开始...

76514
来自专栏C++

Windows核心编程:第7章 线程调度、优先级和关联性

1053
来自专栏喵了个咪的博客空间

EMQ百万级MQTT消息服务(TLS Docker Golang)

4563
来自专栏蜉蝣禅修之道

iOS开发之CFHttpMessageRef的那些坑

3176
来自专栏Pulsar-V

Ubuntu Linux下通过c++获取屏幕大小

操作1,从驱动读取屏幕大小 #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #i...

3266
来自专栏猿天地

Spring Cloud Gateway 限流操作

API网关作为所有请求的入口,请求量大,我们可以通过对并发访问的请求进行限速来保护系统的可用性。

2003
来自专栏iOSDevLog

Plugin with id 'com.android.application' not found.

50114

扫码关注云+社区

领取腾讯云代金券