天天看點

c語言自定義tcp協定實作socket通信(windows版本)

    前面一篇部落格介紹了mac/linux下通過C語言自定義協定實作socket通信的示例,因為大部分api與windows還有很多差別,這裡就特意把windows下的tcp通信執行個體給介紹一下。

    無論是linux,還是windows,其實c語言都是預設小端序,這個需要注意,還有一個就是結構體的記憶體對齊問題也是存在的,是以協定結構體我們需要注意他的大小就行了,在進行拷貝的時候,不能直接使用sizeof來計算發送資料的長度。

    因為是windows,是以我們可以通過網絡小助手來模拟一個服務端,而不需要通過netcat指令了,其實都一樣,我的windows專業版好像不支援netcat也就是nc,當你運作nc指令,系統會預設把他幹掉,很遺憾。

    協定中最重要的部分,就是資料體,這個部分嚴格來說會不一樣,這裡結合了cJSON這個庫來做資料json格式化。借助了rand函數來做一個随機數。是以會比上一個示例複雜一些。

    這裡多說一句,就是cJSON這個庫是開源免費的,可以直接将頭檔案和cpp源檔案加入項目中就可以使用了。

    show me the code:

#include <winsock.h>
#include <windows.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "cJSON.h"
#pragma comment(lib,"ws2_32.lib")
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
#define PAYLOAD_SIZE 1024
typedef struct _pktdata{
	uint16_t flag;    //幀頭辨別符 0x5aa5
	uint16_t version; //版本       0x0001
	uint16_t type;    //類型       0x0001
	uint16_t reserved; //保留位    0xffff
	uint32_t length;   //資料長度  int
	uint8_t payload[PAYLOAD_SIZE];//資料體 可變長度
	uint8_t checksum;  //校驗位  本例中沒有設定
}pktdata;
void createPacket(pktdata* data){
	data->flag = (0x5aa5);
	data->version = (0x0001);
	data->type = (0x0001);
	data->reserved = (0xffff);
	//build payload
	cJSON* root = cJSON_CreateObject();
	int random = rand();
	char name[20];
	sprintf(name,"xxx%d",random);
	cJSON_AddItemToObject(root,"name",cJSON_CreateString(name));
	cJSON_AddItemToObject(root,"age",cJSON_CreateNumber(18));
	char* payload = cJSON_PrintUnformatted(root);
	data->length = strlen(payload);//25 -> {"name":"admin","age":18}
	printf("%s\n",payload);
	memcpy(data->payload,payload,data->length);
}
int main(int argc,char** argv){
	srand((unsigned)time(NULL));
	WORD sockVersion = MAKEWORD(2,2);
	WSADATA data;
	if(WSAStartup(sockVersion,&data)!=0){
	    return 0;
	}
	printf("start up.\n");
	SOCKET sclient = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(sclient==INVALID_SOCKET){
		printf("invalid socket!\n");
	    return 0;
	}
	struct sockaddr_in servAddr;
	servAddr.sin_family = AF_INET;
	servAddr.sin_port = htons(6666);
	servAddr.sin_addr.S_un.S_addr = inet_addr("172.16.5.33");
	if(connect(sclient,(struct sockaddr*)&servAddr,sizeof(servAddr))==SOCKET_ERROR){
		printf("connect error!\n");
		closesocket(sclient);
		return 0;
	}

	for(int i=0;i<10;i++){
		pktdata pd;
		createPacket(&pd);
		char sendData[1040] = {0};
		//strlen() = 3 是因為字元串數組中有0,這裡不能使用strlen()來求字元串長度
		int len = pd.length + 12;
		memcpy(sendData,(void *)&pd,len); //? 如果拷貝struct _pktdata的長度,會是一個1040的長度,是以取的是payload資料長度+其餘字段除去checksum長度
		send(sclient,sendData,len+1,0);//發送的時候多發送一位,是為了保證有一位留給校驗位,雖然沒有給它設定值
		Sleep(10);
	}
	closesocket(sclient);
	WSACleanup();
	printf("done!\n");
    return 0;
}
           

    這個代碼,我是在visual c++6.0編輯器中運作的,同樣的,在運作之前,先開啟一個tcp server,這裡使用網絡小助手來模拟,監聽本機6666端口。

    控制台列印的10條payload資料:

c語言自定義tcp協定實作socket通信(windows版本)

    小助手收到的資料hex表示:

c語言自定義tcp協定實作socket通信(windows版本)

    這裡資料長度會發生變化,這是模拟的一個變化的資料體,無論怎麼變化,我們都需要根據這個長度去求取資料體payload的内容。數字類型也都是小端序,高位在後,低位在前,我們在計算他們真實值的時候需要注意,尤其是服務端使用java來編碼的時候。

繼續閱讀