Wireshark の実践では、ネットワーク データ パケットをキャプチャし、C プログラミングを使用して modbus プロトコルを完成させ、クラウド サーバーから温度と湿度のデータを読み取ります。


序文

タスク:
1.

Wireshark を練習して、ネットワーク パケットをキャプチャします。「Crazy Chat Room」プログラムを 2 台のコンピューター (ラップトップ win10 ホストと ubuntu 仮想マシン。ネットワーク カードのブリッジ モードを選択して 2 つのサブネット IPv4 アドレスを取得します) で実行し、wireshark を介してパケットをキャプチャします。1) このプログラムのネットワーク接続を分析します。
使用しているプロトコル (TCP、UDP) とポート番号は?
2) 盗まれたチャット情報をキャプチャ パケットから探します (英字と漢字は何らかのエンコード変換が行われている可能性があり、データ パケットは平文ではありません) 3) ネットワーク接続が TCP を使用している場合は、いつ確立されるかを分析し
ます接続 3 ウェイ ハンドシェイク、切断時の 4 ウェイ ハンドシェイク; UDP の場合、プログラムが同時に複数のコンピューター (同じチャット ルーム番号のみ) 間でチャット データを送信できる理由を説明してください。

二、

C プログラミングを使用して modbus プロトコルを完成させ、クラウド サーバーから温度と湿度のデータを読み取ります。

ソフトウェア:
クレイジー チャット ルーム:
リンク:クレイジー チャット ルーム
抽出コード: 1234
wireshark:
リンク: wireshark
抽出コード: 1234
RedPandaDE:
リンク: RedPandaDE
抽出コード: 1234


1.クレイジーチャットルーム

この実験を行う場合、同じローカル エリア ネットワークの下に 2 台のコンピューターが必要です。

1. ネットワークを構成する

コントロール パネル---->ネットワークとインターネット---->ネットワークと共有センター---->アダプターの設定の変更:
2 台のコンピューターが接続されているネットワークのみを残して、他のゲートウェイを無効にします。

ここに画像の説明を挿入

2.部屋を作る

両方のコンピューターに Crazy Chat Room をダウンロードする必要があります。

crazychat.exe をクリックしてルームを作成します:
ここに画像の説明を挿入
ルーム番号 8888 を作成し、名前を付けます:
ここに画像の説明を挿入

3.お互いにメッセージを送る

ここに画像の説明を挿入

2. Wireshark がネットワーク パケットをキャプチャする

Wireshark のインストールはずっと次へと同意します。

1.対応するパッケージを見つける

クレイジーなチャット ルームでメッセージを送信すると、wireshark はすぐに IP アドレスがブロードキャスト アドレス 255.255.255.255 であるパッケージを見つけます。
ここに画像の説明を挿入

上の図でキャプチャされたパケット分析から、
このチャット プログラムが UDP プロトコルと 255.255.255.255 のポート番号を使用していることがわかります。

2. キャプチャされたパッケージ内の情報を分析する

キャプチャされたパッケージをダブルクリックして、特定の情報を表示します。

数字:
ここに画像の説明を挿入
英語:
ここに画像の説明を挿入
中国語:

ここに画像の説明を挿入
ここに画像の説明を挿入

上の図の分析から、英字と数字は通常、プレーン テキストで表示できますが、漢字はエンコードされた後はプレーン テキストで表示できないことがわかります.3 つの同一の中国語の 16 進数コードを送信した後、3 バイトがエンコードされていることがわかりました。漢字を入力してエンコーディングを推測します. 形式は UTF-8 です.

3. Modbus プロトコルの概要

1. Modbus プロトコルは、1979 年に Modicon (現 Schneider Electric) によって発行された、プログラマブル ロジック コントローラ (PLC) 通信を使用するためのシリアル通信プロトコルです。Modbusプロトコルは、産業分野の通信プロトコルの業界標準となったアプリケーション層プロトコルであり、産業用電子機器間の一般的な接続方式です。
2. Modbus はマスター/スレーブ アーキテクチャ プロトコルです. 1 つのノードがマスター ノードであり, Modbus プロトコルを使用して通信に参加する他のノードはスレーブ ノードです. 各スレーブ デバイスには一意のアドレスがあります. マスター ノードとして指定されたノードのみがコマンドを開始できます。すべての Modbus データ フレームには、伝送の正確性を保証するためのチェック コードが含まれています。基本的な ModBus コマンドは、スレーブ デバイスにそのレジスタの 1 つの値を変更し、I/O ポートを制御または読み取り、1 つまたは複数のレジスタのデータを送り返すようにデバイスに指示することができます。

1. Modbus マスター/スレーブプロトコルの原理

Modbus シリアル リンク プロトコルは、マスター/スレーブ プロトコルです。同時に、バスに接続できるマスター ステーションは 1 つだけで、同じシリアル バスに 1 つまたは複数のスレーブ ステーション (最大数 247) を接続できます。Modbus 通信は、常にマスタ ステーションによって開始されます。スレーブ ステーションがマスタ ステーションから要求を受信しない場合、データは送信されません。マスタ ステーションは同時に 1 つの Modbus トランザクションしか開始できず、スレーブ ステーションは相互に通信できません。

ここに画像の説明を挿入

2. 一般的な Modbus フレーム構造 - プロトコル データ ユニット (PDU)

Modbus プロトコルは、基礎となる通信層から独立した単純なプロトコル データ ユニット (PDU) を定義します. 特定のバスまたはネットワーク上の Modbus プロトコル マッピングは、アプリケーション データ ユニット (ADU) にいくつかの追加フィールドを導入できます.

ここに画像の説明を挿入

3. 2 つの Modbus シリアル伝送モード

RTU モード: 各 8 ビット バイトには 2 つの 4 ビット 16 進文字が含まれます.利点は、同じボー レートで、ASCII よりも多くのデータを送信できることですが、各メッセージは連続したデータ ストリーミングでなければなりません.

ここに画像の説明を挿入

ASCII モード: 情報の 8 ビット バイトごとに 2 つの ASCII 文字が必要です。これには、文字の送信間隔をエラーなしで 1 秒にできるという利点があります。

ここに画像の説明を挿入

4. ModbusTCP 通信構造

Modbus TCP/IP 用の通信デバイス: TCP/IP ネットワークに接続された Modbus TCP/IP クライアントおよびサーバー デバイス。
TCP/IP ネットワークとシリアル リンク サブネット間を相互接続するブリッジ、ルーター、ゲートウェイなどの相互接続デバイス。

ここに画像の説明を挿入

4. C プログラミングは modbus プロトコルを完了し、クラウド サーバーから温度と湿度のデータを読み取ります。

RedPandaDE を開き、新しいプロジェクトを作成します。
ここに画像の説明を挿入

1. 構成コード

1. ソケット dll を初期化し、IP 経由でサーバーの対応するポートに接続します。

WORD winsock_version = MAKEWORD(2,2);
	WSADATA wsa_data;
	if (WSAStartup(winsock_version, &wsa_data) != 0) {
    
    
		printf("Failed to init socket!\n");
		return 1;
	}
	
	SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (client_socket == INVALID_SOCKET) {
    
    
		printf("Failed to create server socket!\n");
		return 2;
	}
	
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
	if (connect(client_socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
    
    
		printf("Failed to connect server: %ld !\n", GetLastError());
		return 3;
	}

2. 確認コードを生成します。

uint16_t CRC_16(uint8_t *temp)
{
    
    
	uint8_t i,j;
	uint16_t CRC_1 = 0xFFFF;          //声明CRC寄存区,也就是步骤1
	for(i = 0;i < 6;i++)       //这里的for循环说的是步骤6中的重复步骤 2 到步骤 5
	{
    
    
		CRC_1 ^= temp[i]; //这里就是步骤2,进行异或运算
		for(j = 0;j < 8;j++)         //用来将异或后的低八位全部移出的for循环
		{
    
    
			if(CRC_1 & 0x01)         //判断低八位的最后一位是否为1,为1时执行下列语句,也就是步骤3说的移位判断与步骤5说的右移8次
			{
    
    
				/*一定要先移位,再异或*/
				CRC_1 >>=1;          //移位后再异或,就是步骤4
				CRC_1 ^= 0xA001;     //0xA001为0x8005的逆序
			}
			else                    //若不为1,则直接移位。
			{
    
    
				CRC_1 >>=1;
			}
		}
	}
	
	//	CRC_1 = (((CRC_1 & 0xFF)<<8) + (CRC_1>>8));
	//	printf("%04x\r\n",CRC_1);     //用于打印检测CRC校验码
	return(CRC_1);
}

3. 温度と湿度のデータを取得します。

int ret = recv(client_socket, recv_data, BUFFER_SIZE, 0);
		if (ret < 0) {
    
    
			printf("Failed to receive data!\n");
			break;
		}
		recv_data[ret]=0; // correctly ends received string
		char yb[4],wd[4];
		for(int i=0;i<4;i++){
    
    
			//TODO
			yb[i] = recv_data[4+i];
			wd[i] = recv_data[8+i];
			
		}
		float mic = hexToDec(yb)/100.0;
		float strain_temp = hexToDec(wd)/100.0;
		printf("应变:%f\r\n",mic);
		printf("温度:%f\r\n",strain_temp);

4. 完全なコード

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <winsock2.h>  
#include <math.h>
#include "stdint.h"
#define length_8 8    //定义一个宏,为传入8位16进制数的个数
#define PORT 8002
#define SERVER_IP "123.56.90.74"
#define BUFFER_SIZE 4196

const char* kExitFlag = "exit";


/* 返回ch字符在sign数组中的序号 */
int getIndexOfSigns(char ch)
{
    
    
	if(ch >= '0' && ch <= '9')
	{
    
    
		return ch - '0';
	}
	if(ch >= 'A' && ch <='F') 
	{
    
    
		return ch - 'A' + 10;
	}
	if(ch >= 'a' && ch <= 'f')
	{
    
    
		return ch - 'a' + 10;
	}
	return -1;
}
/* 十六进制数转换为十进制数 */
int hexToDec(char *source)
{
    
    
	int sum = 0;
	int t = 1;
	int i, len=4;
	char low,high;
	for(int i=0,j=7;i<4;i++){
    
    
		//TODO
		high = (source[i] & 0xf0)>>4;
		low = source[i] & 0x0f;
		sum += high*pow(16,j--)+low*pow(16,j--); 
	}
	return sum;
}



const unsigned char *fromhex(const char *str)
{
    
    
	static unsigned char buf[512];
	size_t len = strlen(str) / 2;
	if (len > 512) len = 512;
	for (size_t i = 0; i < len; i++) {
    
    
		unsigned char c = 0;
		if (str[i * 2] >= '0' && str[i*2] <= '9') 
			c += (str[i * 2] - '0') << 4;
		if ((str[i * 2] & ~0x20) >= 'A' && (str[i*2] & ~0x20) <= 'F') 
			c += (10 + (str[i * 2] & ~0x20) - 'A') << 4;
		if (str[i * 2 + 1] >= '0' && str[i * 2 + 1] <= '9') 
			c += (str[i * 2 + 1] - '0');
		if ((str[i * 2 + 1] & ~0x20) >= 'A' && (str[i * 2 + 1] & ~0x20) <= 'F')
			c += (10 + (str[i * 2 + 1] & ~0x20) - 'A');
		buf[i] = c;
	}
	return buf;
}

uint16_t CRC_16(uint8_t *temp)
{
    
    
	uint8_t i,j;
	uint16_t CRC_1 = 0xFFFF;          //声明CRC寄存区,也就是步骤1
	for(i = 0;i < 6;i++)       //这里的for循环说的是步骤6中的重复步骤 2 到步骤 5
	{
    
    
		CRC_1 ^= temp[i]; //这里就是步骤2,进行异或运算
		for(j = 0;j < 8;j++)         //用来将异或后的低八位全部移出的for循环
		{
    
    
			if(CRC_1 & 0x01)         //判断低八位的最后一位是否为1,为1时执行下列语句,也就是步骤3说的移位判断与步骤5说的右移8次
			{
    
    
				/*一定要先移位,再异或*/
				CRC_1 >>=1;          //移位后再异或,就是步骤4
				CRC_1 ^= 0xA001;     //0xA001为0x8005的逆序
			}
			else                    //若不为1,则直接移位。
			{
    
    
				CRC_1 >>=1;
			}
		}
	}
	
	//	CRC_1 = (((CRC_1 & 0xFF)<<8) + (CRC_1>>8));
	//	printf("%04x\r\n",CRC_1);     //用于打印检测CRC校验码
	return(CRC_1);
}

int main() {
    
    
	// 初始化socket dll。
	WORD winsock_version = MAKEWORD(2,2);
	WSADATA wsa_data;
	if (WSAStartup(winsock_version, &wsa_data) != 0) {
    
    
		printf("Failed to init socket!\n");
		return 1;
	}
	
	SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (client_socket == INVALID_SOCKET) {
    
    
		printf("Failed to create server socket!\n");
		return 2;
	}
	
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
	if (connect(client_socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
    
    
		printf("Failed to connect server: %ld !\n", GetLastError());
		return 3;
	}
	
	char recv_data[BUFFER_SIZE+1];
	while (true) {
    
    
		uint8_t data[length_8];
		printf("0+传感器编号(1,2,3,4,5)0300010002\r\n");
		scanf("%s",data);
		uint16_t crc;
		unsigned char * cmd;
		char crc1[8];
		cmd = fromhex(data);
		crc = CRC_16(cmd);
		uint8_t a = 0xFF;
		for(int i=0;i<6;i++){
    
    
			//TODO
			crc1[i] = cmd[i];
		}
		crc1[6] = a & crc;
		crc1[7] = (crc >> 8) & a;
		
		if (send(client_socket, crc1, 8, 0) < 0) {
    
    
			printf("Failed to send data!\n");
			break;
		}
		
		int ret = recv(client_socket, recv_data, BUFFER_SIZE, 0);
		if (ret < 0) {
    
    
			printf("Failed to receive data!\n");
			break;
		}
		recv_data[ret]=0; // correctly ends received string
		char yb[4],wd[4];
		for(int i=0;i<4;i++){
    
    
			//TODO
			yb[i] = recv_data[4+i];
			wd[i] = recv_data[8+i];
			
		}
		float mic = hexToDec(yb)/100.0;
		float strain_temp = hexToDec(wd)/100.0;
		printf("应变:%f\r\n",mic);
		printf("温度:%f\r\n",strain_temp);
		
		
		//		printf("Receive data from server: \"%x\"\n",recv_data);
		if (strcmp(data,kExitFlag)==0) {
    
    
			printf("Exit!\n");
			break;
		}
	}
	
	closesocket(client_socket);
	WSACleanup();
	
	return 0;
}

2.効果

ここに画像の説明を挿入


V. まとめ

メッセージの送信時にブロードキャストアドレス 255.255.255.255 を使用するため、複数の相手がメッセージを受信できます。Modbus 通信プロトコルと ModbusTCP 通信プロトコルおよび構造の基本原則を理解します。

6.参考文献

Wireshark を使用してチャット情報をキャプチャします (ローカル エリア ネットワークでの udp 通信)
C 言語を使用して modbus プロトコルを完成させ、クラウド サーバーから温度と湿度の情報を読み取ります

おすすめ

転載: blog.csdn.net/qq_52215423/article/details/128444683