Jacky Walker

用于记录工作和学习的博客


  • 首页

  • Objective-C

  • Cocoa/UIKit

  • net

  • OpenGL

  • 杂项

  • 设计模式

  • 分类

  • 标签

  • 归档

TCP的三次握手与四次挥手

发表于 2019-09-02 | 分类于 net | | 阅读次数:

引言

前段时间一直在准备面试,本以为准备的挺好,然而被腾讯面试官问道网络问题的时候,发现自己对TCP协议的理解真的是停留在表面,不够深入。于是本着提高自己的想法,去查了些资料,这里主要是总结我对TCP建立与断开连接过程的理解。

常见题目

在面试中网络问题是一定会考察的,而TCP协议则是考察网络知识的重点。经常会被问道的问题如下:

  1. 请讲一下TCP协议建立连接的过程
  2. 请介绍TCP协议中的三次握手和四次挥手是怎么样的
  3. 为什么TCP协议要三次握手来确立连接,而不是两次,也不是4次
  4. TCP连接发起是的syn序号为什么不能从同一个序号开始,比如说1

接下来,我将介绍我理解的TCP三次握手和4次挥手的过程,如果错误还请指正,谢谢。

TCP协议

三次握手过程

首先需要服务器监听特定的端口,等待客户端来请求连接。当客户端需要建立连接时,客户端会先向服务器发送syn报文,将报文中syn置为随机生成的序号n(这里假设序号为1000)。服务器收到同步报文后,会回复一个ack报文,把ACK位置位n+1(这里的序号应该为1001),同时设置syn为y(这里假设为2000)。客户端收到服务器发送的ack报文后,会回复一个ACK报文给服务器,其中ACK位置为y+1(这里即为2001)。当服务器收到ACK消息后,即认为连接进入稳定状态。状态机与流程图如下:

四次挥手过程

当client从app接收到关闭指令后,client会给server发送FIN消息(表明client不会再给server发送数据),client进入finish-wait-1状态。server收到finish消息后,回复确认消息ack给client,自身进入close-wait状态。client接收到ack消息后,进入到FIN-WAIT-2状态。并且在此状态等待服务器发送finish消息。当server接收到app的关闭指令后,server给client发送FIN消息。服务器进入到LAST-ACK状态。客户端收到FIN消息后,会回复ACK消息,同时进入到TIME-WAIT状态,来等待server收到ack消息,客户端会在接下来的2MSL(maximum segment lifetime)的时间内保持TIME-WAIT状态。为什么是2MSL时间呢,一是为了server有足够的时间收到ACK消息,并在消息丢失时重发。二是为了在此连接结束后的后续连接提供缓冲期。如果不是2倍MSL的话,就可能混合来自不同连接的数据包,造成消息混乱。状态机与流程图如下:

完整过程

以下为TCP从建立连接到断开的完整流程图

开始答题

有了上面的介绍,基本能够回答前两个问题。

请讲一下TCP协议建立连接的过程

看上面

请介绍TCP协议中的三次握手和四次挥手是怎么样的

看上面

为什么TCP协议要三次握手来确立连接,而不是两次,也不是4次

首先呢,根本不存在可靠的连接,tcp只是提供相对可靠的连接。三次握手的主要目的是交换通信需要的参数,主要是server与client的syn序号,这个序号是用于收发数据的。如果只有两次握手的话,当服务器发送ack+syn消息后,就会认为建立了稳定连接,这个时候如果ack+syn丢失了,client并没有收到这个消息,那么客户端就会认为连接建立不成功,而直接进入close状态。这样就会造成,server一直在哪傻等,永远不会有client来发送数据,这就会造成服务器资源的浪费。至于为什么不是四次握手,是因为握手三次成功以后,就可以认定当前连接是可靠的了,不然的话还需要client与server互相之间发送ack消息,这样就无休无止了。

TCP连接发起是的syn序号为什么不能从同一个序号开始,比如说1

因为现实中的网络状况不可预知,比如说客户端在第一次连接时,使用序号为1为初始序号进行数据发送,发送了1到30的数据片段,这个时候因为网络问题断开了连接。然后客户端是syn为1重新建立了新的连接,这个时候服务器收到了之前发送的30个字节的数据,服务器就会以为这30个字节的数据是新发的,这就会导致数据混乱。

参考资料

说明,本文所有图片均来自The TCP/IP Guide。

参考资料如下:

The TCP/IP Guide

TCP为什么需要3次握手与4次挥手

TCP三次握手四次挥手详解

C++中调用OC代码

发表于 2019-08-20 | 分类于 sundry | | 阅读次数:

前言

  最近项目中为了方便维护,底层统一使用C++编写。由于是项目是做屏幕共享sdk,所以只能底层的压缩、编解码使用C++,屏幕捕获部分Mac和win就自己实现了。那么问题就来了,因为是面向接口编程,所以项目的入口都是c++来写的,而屏幕捕获是需要oc部分的代码,就需要C++调用oc代码了。

准备

  之前只做过OC调动C++,于是Google了一下,在Stack Overflow上找到了这个回答。要看具体描述的可以去链接看看,实现思路一共有两种,我在这里大概描述一下。第一种,由于C++是不能直接调用OC的,所以需要通过C语言作为中间层,即C++调用C,C调用OC,这样就达到了C++调用OC的目的。第二种OC是可以调用C++的,通过在外部声名C++类,然后类具体实现放在OC类中,这样C++类就能够调用OC类了,其他需要调用OC的类,只需要调用外部声名的类即可。

实现

具体的实现方式有两种,第一种是C语言方法接收oc对象指针和参数,然后把指针桥接为具体的oc对象。第二种是用C++进行包装,先声名一个C++类,这里称为A。然后在OC类中,这里称为B,对A进行实现,因为这个实现实在OC语言里的,所以在这里是可以直接调用OC代码的。接下来声名一个C++类C。类C通过持有类A来调用OC类B,即A(C++)->C(C++)->B(OC类)

  ## 实现方式一 by C

MyObject-C-Interface.h
  

1
  int MyObjectDoSomethingWith (void *myObjectInstance, void *parameter);

  
MyObject.h
  

1
2
3
4
5
6
7
8
  @interface MyObject : NSObject
  {
  int someVar;
  }
  

  - (int)doSomethingWith:(void *)aParameter;
  @end

  MyObject.mm
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  @implementation MyObject
  
  int MyObjectDoSomethingWith (void *self, void *aParameter)
  {
  // 通过将self指针桥接为oc 对象来调用oc方法
  return [(__bridge id)self doSomethingWith:aParameter];
  }
  

  - (int) doSomethingWith:(void *) aParameter
  {
  //将void *指针强转为对应的类型
  int* param = (int *)aParameter;
  return *param / 2 ;
  }

  - (void)dealloc
  {
  NSLog(@"%s", __func__);
  }

  @end

  MyCPPClass.h
  

1
2
3
4
5
6
7
8
9
10
11
  class MyCPPClass {
  
  
  public:
  MyCPPClass();
  ~MyCPPClass();
  int someMethod (void *objectiveCObject, void *aParameter);
  void *self;
  
  void setSelf(void *self);
  };

  
  MyCPPClass.cpp
  
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

  #include "MyObject-C-Interface.h"

  MyCPPClass::MyCPPClass()
  {
  
  }
  
  MyCPPClass::~MyCPPClass()
  {
  
  }
  
  
  int MyCPPClass::someMethod (void *objectiveCObject, void *aParameter)
  {
  // To invoke an Objective-C method from C++, use
  // the C trampoline function
  return MyObjectDoSomethingWith (objectiveCObject, aParameter);
  }
  
  void MyCPPClass::setSelf(void *aSelf)
  {
  self = aSelf;
  }

  main.mm
  
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

  #include "MyCPPClass.hpp"
  #import "MyObject.h"

  int main(int argc, const char * argv[]) {
  @autoreleasepool {
  // insert code here...
  NSLog(@"Hello, World!");
  MyObject *object = [[MyObject alloc] init];
  MyCPPClass *c = new MyCPPClass();
  c->setSelf((__bridge void *)object);
  int a = 12;
  int result = c->someMethod((__bridge void *)object, &a);
  NSLog(@"%d", result);
  }
  return 0;
  }

  运行结果如下:
  运行结果
  
  存在的问题
  
  
  在每次C++调用时都需要传递OC对象桥接为==void ==的指针,使用起来很不方便。
  
  ## 方式二 by C++ IMPL
  *
MyObject-C-Interface.h**
  
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  #ifndef MyObject_C_Interface_h__h
  #define MyObject_C_Interface_h__h

  class MyClassImpl
  {
  public:
  MyClassImpl ( void );
  ~MyClassImpl( void );
  
  void init( void );
  int doSomethingWith( void * aParameter );
  void logMyMessage( char * aCStr );
  
  private:
  void * self;
  };
  
  #endif /* MyObject_C_Interface_h__h */

  需要注意的是,==MyClassImpl==的实现是放在OC中的
  MyObject.h
  
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  NS_ASSUME_NONNULL_BEGIN
  
  @interface MyObject : NSObject
  {
  int someVar;
  }
  
  - (int) doSomethingWith:(void *) aParameter;
  - (void) logMyMessage:(char *) aCStr;


  @end
  
  NS_ASSUME_NONNULL_END

  
  MyObject.mm
  
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
  #include "MyObject-C-Interface.h"

  typedef void(^RetainSelfBlock)(void);
  
  @implementation MyObject
  {
  RetainSelfBlock _retainBlock;//通过这个block持有对象,造成循环引用,避免被释放
  }
  MyClassImpl::MyClassImpl( void )
  : self( NULL )
  {
  
  }
  
  MyClassImpl::~MyClassImpl( void )
  {
  [(__bridge id) self breakRetainCycly];
  }
  
  void MyClassImpl::init( void )
  {
  MyObject *object = [[MyObject alloc] init];
  object->_retainBlock = ^{//循环引用
  [object class];
  };
  
  self = (__bridge void *)object;
  
  NSLog(@"%p", self);
  }
  
  int MyClassImpl::doSomethingWith( void *aParameter )
  {
  NSLog(@"%p", self);
  return [(__bridge id)self doSomethingWith:aParameter];
  }
  
  void MyClassImpl::logMyMessage( char *aCStr )
  {
  [(__bridge id)self logMyMessage:aCStr];
  }
  
  - (int) doSomethingWith:(void *) aParameter
  {
  int result = 0;

  // ... some code to calculate the result
  
  return result;
  }
  
  - (void) logMyMessage:(char *) aCStr
  {
  NSLog( @"%s", aCStr );
  }

  //打破循环引用,释放对象
  - (void) breakRetainCycly
  {
  _retainBlock = nil;
  }

  - (void)dealloc
  {
  NSLog(@"%s", __func__);
  }
  @end

  在==MyObject.mm==中需要注意的是,由于OC是使用ARC来进行内存管理的,C++不能够管理OC对象的生命周期。在默认的情况下,临时变量会在autorelease pool每一次pop后被释放,所以在oc实现中要想对象不被释放,那就需要循环引用来帮忙了。

  具体代码如下,在MyClassImpl初始化时,利用循环引用保证object不被释放,在MyClassImpl调用析构函数时,将block置空,打破循环引用,以此来释放oc对象
  
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  void MyClassImpl::init( void )
  {
  MyObject *object = [[MyObject alloc] init];
  object->_retainBlock = ^{//循环引用
  [object class];
  };
  
  self = (__bridge void *)object;
  
  NSLog(@"%p", self);
  }
  
  MyClassImpl::~MyClassImpl( void )
  {
  [(__bridge id) self breakRetainCycly];
  }
  
  //打破循环引用,释放对象
  - (void) breakRetainCycly
  {
  _retainBlock = nil;
  }

  
  MyCPPClass.hpp
  
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  #ifndef MyCPPClass_hpp
  #define MyCPPClass_hpp

  #include <stdio.h>

  class MyClassImpl;
  
  class MyCPPClass
  {
  enum { cANSWER_TO_LIFE_THE_UNIVERSE_AND_EVERYTHING = 42 };
  public:
  MyCPPClass ( void );
  ~MyCPPClass( void );
  
  void init( void );
  void doSomethingWithMyClass( void );
  
  private:
  MyClassImpl * _impl;
  int _myValue;
  };
  
  
  #endif /* MyCPPClass_hpp */

  
  MyCPPClass.cpp
  
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  #include "MyCPPClass.hpp"
  #include "MyObject-C-Interface.h"

  MyCPPClass::MyCPPClass( void )
  : _impl ( NULL )
  { }
  
  void MyCPPClass::init( void )
  {
  _impl = new MyClassImpl();
  _impl->init();
  }
  
  MyCPPClass::~MyCPPClass( void )
  {
  if ( _impl ) { delete _impl; _impl = NULL; }
  }
  
  void MyCPPClass::doSomethingWithMyClass( void )
  {
  int result = _impl->doSomethingWith(&_myValue);
  if ( result == cANSWER_TO_LIFE_THE_UNIVERSE_AND_EVERYTHING )
  {
  _impl->logMyMessage("Hello, Arthur!");
  }
  else
  {
  _impl->logMyMessage("Don't worry.");
  }
  }

  
  main.mm
  
  

1
2
3
4
5
6
7
8
9
10
11
  #include "MyCPPClass.hpp"

  int main(int argc, const char * argv[]) {
  @autoreleasepool {
  MyCPPClass *temp = new MyCPPClass();
  temp->init();
  temp->doSomethingWithMyClass();
  delete temp;
  }
  return 0;
  }

  运行结果
  by c++ IMPL
  
  # 总结
  第一种通过C语言的方式来调用,使用起来更复杂,所以建议使用C++的方式来实现。需要注意的问题是C++不能管理OC对象的释放,所以需要利用循环引用。
  
  你可以在这里下载demo
  
  参考:https://stackoverflow.com/questions/1061005/calling-objective-c-method-from-c-member-function

Mac OpenGL环境搭建

发表于 2019-08-20 | 分类于 OpenGL | | 阅读次数:

MAC OpenGL 环境搭建

基础库介绍

先要安装两个库一个是GLEW(OpenGL Extension Wrangler Library),另外一个是GLFW(Graphics Library Framework)

GLEW

譯自英文-OpenGL擴展Wrangler庫是一個跨平台的C / C ++庫,可幫助查詢和加載OpenGL擴展。 GLEW提供了高效的運行時機制,用於確定目標平台上支持哪些OpenGL擴展。所有OpenGL擴展都在一個頭文件中公開,該文件是從官方擴展名列表機器生成的

GLFW

GLFW 是配合 OpenGL 使用的轻量级工具程序库,缩写自 Graphics Library Framework(图形库框架)。GLFW 的主要功能是创建并管理窗口和 OpenGL 上下文,同时还提供了处理手柄、键盘、鼠标输入的功能。

进入安装步骤

准备工作

要进行安装的话需要先安装CMake,可以选择使用GUI 方式 或者命令行的方式。GUI可以在这里下载,命令行的方式的话,可以用brew来安装,方式如下

1
brew install cmake

安装GLEW

首先在这里下载源码,下载以后,解压文件,然后进入到文件所在路径,打开终端进入到此路径,执行命令

1
sudo make install

安装GLFW

首先在这里下载源码,我们可以选择直接下载编译好的库,也可以下载源文件然后通过camke来自己编译,区别是如果用cmake库的话,在执行成功以后,会直接将生成的库与头文件分别添加到系统路径,也就是/usr/local/include和/usr/local/lib。这里就自己选择方式了。我在这里介绍利用make工具,自己编译的方式

打开cmake 的gui工具,在source code选择下载的glfw路径,这一步需要在这个路径创建一个build目录,用于存放生成的文件。然后在browse build路径选择我们刚才创建的路径,点击configure。

生成成功会出现如下效果

接下来从终端进入到build目录,没错就是刚才我们创建的build,输入如下指令

1
2
sudo make
sudo make install

执行成功后,剩成的.a文件与头文件就都放到/usr/local/include和/usr/local/lib了。

环境安装好以后,应该是这个样子的

至此,环境安装完毕,接下来就是加入到工程了

加入工程

配置搜索路径

首先我们创建一个command line的工程,然后在build setting里这只user search path,在user search path中设置为:/usr/local/include,这里一定要注意不能选择递归查找,否则会出错。然后设置library search path为:/usr/local/lib。设置这个的作用是工程中明确工程中查找头文件和.a文件的路径。

添加要链接的framework

添加以下库文件:

  • Cocoa.framework
  • OpenGL.framwork
  • GLUT.framework
  • CoreVideo.framework
  • IOKit.framework
  • libglfw3.a
  • libGLEW.a

库文件

其中,libglfw3.a、libGLEW.a需要点击 add another,然后进入要路径/usr/local/lib中添加。

配置完成以后,就可以写代码了,进入main.cpp文件,将main中修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
void Render(void)
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
{
glColor3f(1.0,0.0,0.0);
glVertex2f(0, .5);
glColor3f(0.0,1.0,0.0);
glVertex2f(-.5,-.5);
glColor3f(0.0, 0.0, 1.0);
glVertex2f(.5, -.5);
}
glEnd();
}
int main(void) {
GLFWwindow* win;
if(!glfwInit()){
return -1;
}
win = glfwCreateWindow(640, 480, "OpenGL Base Project", NULL, NULL);
if(!win)
{
glfwTerminate();
exit(EXIT_FAILURE);
}
if(!glewInit())
{
return -1;
}
glfwMakeContextCurrent(win);
while(!glfwWindowShouldClose(win)){
Render();
glfwSwapBuffers(win);
glfwPollEvents();
}
glfwTerminate();
exit(EXIT_SUCCESS);
return 0;
}

见证奇迹的时刻到了,运行,你将会看到一个漂亮的三角形

mac 工程搭建,就说这么多了。


感谢以下文章的作者:

Mac系统下配置OpenGL环境(小白自学版)

OpenGL学习笔记(二)—— (macOS)环境搭建

initial blog

发表于 2019-08-19 | 分类于 DesignPattern , Objective-C , sundry , OpenGL , net , Cocoa | | 阅读次数:
12

吴珂

机会是留给有准备的人?不对,准备留给有机会的人,没机会的就别瞎准备了。

14 日志
7 分类
1 标签
GitHub
© 2019 — 2020 吴珂
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4