前面一篇部落格介紹了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資料:

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