前面一篇博客介绍了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来编码的时候。