来自我的博客Minecode.link
消息封/解装仿真
功能要求
按照TCP五层模型仿真消息在两台主机之间的通信过程。
- 在发送端模拟数据从高层到低层的封装过程,在接收端模拟数据从低层到高层的解封装过程。
- 按照每层的功能对数据填加报头,并显示每一层得到的封/解装格式。
- 传输层和网络层的封装格式参考TCP/IP的相应各层协议格式。
- 网络层的IP报文需要模拟报文分段和重组的过程。
- 数据链路层帧格式参考局域网的MAC帧格式。
- 物理层显示为0或1比特串。
Socket编程简介
Socket是网络文件描述符。在基于Socket的编程技术中,用户不直接访问发送和接收包的网络接口设备,而是建立一个中间文件描述符来处理编程接口到网络的操作。简单来说,Socket就是我们常说的“套接字”。
本段只介绍了本实验需要设计的知识,更多Socket用法可Google一下。
Socket包含的内容
- 一个特殊的通信域,比如一个网络连接
- 一个特殊的通信类型,比如流或者数据报
- 一个特殊的协议,比如TCP或者UDP
其中,可以实现面向连接和无连接的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五层模型消息解/封装仿真