TCP/IP五层模型消息解/封装仿真

来自我的博客Minecode.link

消息封/解装仿真

功能要求

按照TCP五层模型仿真消息在两台主机之间的通信过程。

  1. 在发送端模拟数据从高层到低层的封装过程,在接收端模拟数据从低层到高层的解封装过程。
  2. 按照每层的功能对数据填加报头,并显示每一层得到的封/解装格式。
  3. 传输层和网络层的封装格式参考TCP/IP的相应各层协议格式。
  4. 网络层的IP报文需要模拟报文分段和重组的过程。
  5. 数据链路层帧格式参考局域网的MAC帧格式。
  6. 物理层显示为0或1比特串。
五层模型

Socket编程简介

Socket是网络文件描述符。在基于Socket的编程技术中,用户不直接访问发送和接收包的网络接口设备,而是建立一个中间文件描述符来处理编程接口到网络的操作。简单来说,Socket就是我们常说的“套接字”
本段只介绍了本实验需要设计的知识,更多Socket用法可Google一下。

Socket包含的内容

  • 一个特殊的通信域,比如一个网络连接
  • 一个特殊的通信类型,比如流或者数据报
  • 一个特殊的协议,比如TCP或者UDP

其中,可以实现面向连接和无连接的Socket

面向连接的Socket

面向连接的Socket

面向无连接的Socket

面向无连接的Socket

模拟TCP

语言 Objective-C + C
平台 Mac OSX
工具 XCode (LLVM)

最终效果

[图片上传失败...(image-d8ca88-1512797750867)]

运行逻辑

本实验模拟了TCP五层模型中的消息解/封装仿真,建立在现有网络的基础上,使用Socket进行通信,使用了面向连接的Socket。除了两台机器相互通信之外,我们也可以将服务端绑定到网卡端口,使用客户端与服务端在本机相互通信。

由服务端绑定端口并侦听客户端消息。而后客户端连接服务端,并相互发送消息。

服务端(Server)

首先,服务器绑定端口,并侦听客户端连接请求,当客户端连接后进行消息侦听和发送。

- (void)bindSocketWithPort:(NSInteger)port {
    // 创建Socket地址
    struct sockaddr_in server_addr;                             // socket地址
    server_addr.sin_len = sizeof(struct sockaddr_in);           // 设置地址结构体大小
    server_addr.sin_family = AF_INET;                           // AF_INET地址簇
    server_addr.sin_port = htons((short)port);                  // 设置端口
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);            // 服务器地址
    
    // 创建Socket
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);        // 创建Socket
    if (server_socket == -1) {
        [self showMessageWithMsg:@"创建Socket失败"];
        return;
    }
    
    int reuse = 1;
    int sockOpt = setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    if (sockOpt == -1) {
        [self showMessageWithMsg:@"重设Socket失败"];
        return;
    }
    
    // 绑定Socket
    // 将创建的Socket绑定到本地IP和端口,用于侦听客户端请求
    int bind_result = bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (bind_result == -1) {
        [self showMessageWithMsg:@"绑定Socket失败"];
        return;
    }
    
    // 侦听客户端消息
    if (listen(server_socket, 5) == -1) {
        [self showMessageWithMsg:@"开启侦听失败"];
        return;
    }
    
    // 获取客户端端口信息
    struct sockaddr_in client_address;
    socklen_t address_len;
    int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &address_len);
    if (client_socket == -1) {
        [self showMessageWithMsg:@"客户端握手失败"];
        return;
    }
    
    char recv_msg[RECV_BUFFER_SIZE];
    char reply_msg[REPLY_BUFFER_SIZE];
    
    while (YES) {
        bzero(recv_msg, RECV_BUFFER_SIZE);
        bzero(reply_msg, REPLY_BUFFER_SIZE);
        
        long byteLen = recv(client_socket, recv_msg, RECV_BUFFER_SIZE, 0);
        recv_msg[byteLen] = '\0';                   // 添加消息结尾
        NSMutableString *msgStr = [NSMutableString stringWithFormat:@"%s", recv_msg];
        [self clearCurrentMsg];
        strcpy(recv_msg, [[self reciveFromClient:msgStr] UTF8String]);
        
        if (strcmp(recv_msg, "") != 0) {
            strcpy(reply_msg, "服务端消息:收到");
            strcat(reply_msg, recv_msg);
            send(client_socket, reply_msg, REPLY_BUFFER_SIZE, 0);
        }
    }
}

客户端(Client)

首先,客户端要和服务端建立连接。调用socket的方法顺序为:
socket() -> connect()

- (void)bindSocketWithIP:(NSString *)ipStr andPort:(NSInteger)port {
    
    // 创建Socket地址
    struct sockaddr_in server_addr;                                      // 创建Socket地址
    server_addr.sin_len = sizeof(struct sockaddr_in);                    // 设置结构体长度
    server_addr.sin_family = AF_INET;                                    // AF_INET地址簇
    server_addr.sin_port = htons((short)port);                           // 设置端口
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);                     // 服务器地址
    
    // 创建Socket
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        [self showMessageWithMsg:@"创建Socket失败"];
        return;
    }
    else {
        // 保存Socket
        self.server_socket = server_socket;
        // 保存port
        self.port_num = (short)port;
        [self showMessageWithMsg:@"创建Socket成功"];
        [NSThread sleepForTimeInterval:0.3];
    }
    
    int connect_result = connect(server_socket, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in));
    if (connect_result == -1) {
        [self showMessageWithMsg:@"连接主机失败"];
        return;
    }
    else {
        [self showMessageWithMsg:@"连接主机成功,等待发送消息..."];
        [NSThread sleepForTimeInterval:0.3];
    }
    
    // 连接成功后的操作
    [self didConnected];
}

连接成功后,双方互相发送和接收消息
send() -> recv()

- (void)sendMsgAction {
    NSMutableString *msg = [NSMutableString stringWithFormat:@"%@",self.msgField.stringValue];
    
    char recv_msg[RECV_BUFFER_SIZE];
    char send_msg[REPLY_BUFFER_SIZE];
    // 发送消息,并接收服务器回信
    bzero(recv_msg, RECV_BUFFER_SIZE);
    bzero(send_msg, REPLY_BUFFER_SIZE);
    
    // 向服务端通过socket发消息
    NSMutableString *resStr = [self appLayerWithString:msg];
    strcpy(send_msg, resStr.UTF8String);
    long send_result = send(self.server_socket, send_msg, REPLY_BUFFER_SIZE, 0);
    if (send_result == -1) {
        [self showMessageWithMsg:@"消息发送失败"];
        return;
    }
    else {
        [self showMessageWithMsg:@"消息发送成功"];
        [NSThread sleepForTimeInterval:0.3];
    }
    
    // 接收服务端消息
    long recv_result = recv(self.server_socket, recv_msg, RECV_BUFFER_SIZE, 0);
    [self reciveFromServer:[NSString stringWithUTF8String:recv_msg]];
    
}

报头编码

各层在传递给下一层之前,要对数据进行封装,增加对应的报头。具体代码如下:

#pragma mark - 五层传输协议
// 模拟网络层对数据的包装
- (NSMutableString *)appLayerWithString:(NSMutableString *)str {
    NSMutableString *resStr = [NSMutableString stringWithFormat:@"AppHeader#%@", str];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.appLayer.stringValue = [resStr copy];
    });
    
    [NSThread sleepForTimeInterval:0.3];
    
    return [self transferLayerWithString:resStr];
}

// 模拟传输层对数据的包装
- (NSMutableString *)transferLayerWithString:(NSMutableString *)str {
    NSMutableString *resStr = [NSMutableString string];
    // 添加源端口 16位(0-15)
    [resStr appendFormat:@"0000000011111111"];
    // 添加目的端口 16位(16-31)
    [resStr appendFormat:@"%@", [self intToBinary:self.port_num]];
    // 添加序列编号 32位
    [resStr appendFormat:@"00000000000000000000000000001011"];
    // 添加确认帧 32位
    [resStr appendFormat:@"00000000000000000000000011111011"];
    // 添加报头长度
    [resStr appendFormat:@"0101"];
    // 添加保留长度
    [resStr appendFormat:@"000000"];
    // 添加FLag
    [resStr appendFormat:@"000000"];
    // 添加窗口大小
    [resStr appendFormat:@"0000000000000111"];
    // 添加确认值
    [resStr appendFormat:@"0101010101010010"];
    // 添加UrgentPointer
    [resStr appendFormat:@"0000000000001111"];
    // 添加Header结尾
    [resStr appendFormat:@"#%@", str];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.transLayer.stringValue = [resStr copy];
    });
    
    [NSThread sleepForTimeInterval:0.3];
    
    return [self networkLayerWith:resStr];
}

// 模拟网络层对数据的包装
- (NSMutableString *)networkLayerWith:(NSMutableString *)str {
    NSMutableString *resStr = [NSMutableString string];
    
    // 添加VER
    [resStr appendFormat:@"0100"];
    // 添加HLEN
    [resStr appendFormat:@"1111"];
    // 添加Service
    [resStr appendFormat:@"00000000"];
    // 添加totalLength
    [resStr appendFormat:@"0101010101010101"];
    // 添加Identification
    [resStr appendFormat:@"0000000000000000"];
    // 添加Flag
    [resStr appendFormat:@"000"];
    // 添加FragmentationOffset
    [resStr appendFormat:@"0000000000000"];
    // 添加TTL
    [resStr appendFormat:@"00000000"];
    // 添加Protocol
    [resStr appendFormat:@"00000000"];
    // 添加HeaderChecksum
    [resStr appendFormat:@"0000000000000000"];
    // 添加SourIPAddress
    [resStr appendFormat:@"00000000000000000000000000000000"];
    // 添加DestinationIPAddress
    [resStr appendFormat:@"00000000000000000000000000000000"];
    
    // 添加Header结尾
    [resStr appendFormat:@"#%@", str];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.networkLayer.stringValue = [resStr copy];
    });
    
    [NSThread sleepForTimeInterval:0.3];
    
    return [self dlinkLayerWithString:resStr];
}

// 模拟链路层对数据的包装
- (NSMutableString *)dlinkLayerWithString:(NSMutableString *)str {
    NSMutableString *resStr1 = [NSMutableString string];
    // 添加FrameFlag1
    [resStr1 appendFormat:@"00001111"];
    // 添加FrameAdd
    [resStr1 appendFormat:@"11101011"];
    // 添加FrameControl
    [resStr1 appendFormat:@"01111000"];
    
    NSMutableString *resStr2 = [NSMutableString string];
    // 添加FrameFCS
    [resStr2 appendFormat:@"00001111"];
    // 添加FrameFlag2
    [resStr2 appendFormat:@"11101011"];
    
    // 合成帧
    NSMutableString *resStr = [NSMutableString stringWithFormat:@"%@#%@#%@", resStr1, str, resStr2];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.dlinkLayer.stringValue = [resStr copy];
    });
    
    [NSThread sleepForTimeInterval:0.3];
    
    return [self phyLayerWithString:resStr];
}

// 模拟物理层对数据的包装
- (NSMutableString *)phyLayerWithString:(NSMutableString *)str {
    NSMutableString *resStr = [NSMutableString stringWithFormat:@"PhysicsHeader#%@", str];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.phyLayer.stringValue = [resStr copy];
    });
    
    [NSThread sleepForTimeInterval:0.3];
    
    return resStr;
}

其他细节

1、在定义Socket地址端口时,要注意端口值类型,若使用htons,则需要转换为short类型(16位)。
server_addr.sin_port = htons((short)port);

本文源代码:Github: https://github.com/Minecodecraft/TCP-IP-Model-Simulation
原文地址:Minecode's Blog: TCP五层模型消息解/封装仿真

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容