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

消息封/解装仿真

功能要求

按照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)

最终效果

Showcase

运行逻辑

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

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

服务端(Server)

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

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
- (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()

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
- (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()

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
- (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]];

}

报头编码

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

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#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五层模型消息解/封装仿真