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

模拟TCP

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

最终效果

运行逻辑

本实验模拟了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五层模型消息解/封装仿真

你可能感兴趣的:(——文章收藏,▲—iOS开发—▲)