网络通信,收发包规则代码说明

转自:http://hi.baidu.com/vc_net/item/513ac068b71c0035ad3e8343

本文分2部分:
第1次基础:远控视频教程中的最基本网络消息机制,虽然很简单,但是可以很好的理解消息机制
第2次软件正式代码:来源于正式软件项目,每个包带有标识符
-------------------------------第1种:介绍-------------------------------
typedef struct
{
 int ID;
 BYTE lparam[2048];
}COMMAND;

typedef struct
{
 char FileName[MAX_PATH];//搜索到的文件夹名称#define MAX_PATH 260,此260是windows文件名最长值
 int FileLen;//文件与文件夹的大小
 char Time[50];//时间大小,如修改时间,访问时间等
 BOOL IsDir;//判断是文件还是文件夹
 BOOL Error;//如果有错误,就包含错误
 HICON hIcon;//所获取的文件的一些图标,原有的是什么图标,就是什么图标
}FILEINFO;//详见问题1

整个远控中,只有这2个结构体.注意这里是不带标识符的,不过没关系,第2种解决了这种问题

-------------------------------第2种:介绍-------------------------------



前言:所谓的包,即结构体(带有标识符)
第1步:新建command文件目录--->
(1)扩展新建command.h(一些定义的值(包括10进制,16进制)头文件)------详见备注1
(2)扩展新建strPublic.h(一些公共包头,主要是协议的标识符包)--------详见备注2
(3)扩展新建strServer.h与strClient.h(即客户端与服务端的包文件)----详见备注3

备注1:  ncmd = CMD_C_MACHINE_LOGIN_SYSTEM;即CMD_C_MACHINE_LOGIN_SYSTEM=0x110123;之类的定义
备注2:  公共包头
typedef struct cmdHeader{
 unsigned long  nReserved;    
 unsigned long  ncmd;
 unsigned long  ncmdID;    //指令ID
 unsigned long  nSerial;             //指令序列
 unsigned long  nlen;                //数据包长度
 cmdHeader(){
  memset(this, 0x00, sizeof(cmdHeader));
        ncmdID = rand();
 }
}CMD_HEADER, *PCMD_HEADER;

备注3:
--->客户端:
typedef struct  c_machine_login_system{
    PUB_HEADER;
    char            sMachineCode[32];       //机器码
    unsigned long   nVersion;               //客户端版本号,n进制 n可选
    unsigned long   nReserved1;          //保留字段
    unsigned long   nReserved2;          //保留字段
    c_machine_login_system(){
        memset(this, 0x00, sizeof(c_machine_login_system));
        ncmd = CMD_C_MACHINE_LOGIN_SYSTEM;
        nlen = sizeof(c_machine_login_system)-sizeof(CMD_HEADER);
        ncmdID = rand();
    }
}C_MACHINE_LOGIN_SYSTEM, *PC_MACHINE_LOGIN_SYSTEM;

--->服务端:
typedef struct  arep_c_machine_login{
    PUB_HEADER;
    unsigned long   nStatus;     //回复机器登录状态(可能需要公钥验证,后续处理)
    unsigned long   nMachineID;         //机器码对应数据库唯一ID
    unsigned long   nMachineStatus;     //终端机状态信息,0-未激活状态 1-激活状态 2-已注册状态
    unsigned long   nReserved1;      //保留字段
    unsigned long   nReserved2;      //保留字段
    //跟包信息position_info_detail,见strPublic.h,仅当MachineStatus==2时有效
    arep_c_machine_login(){
        memset(this, 0x00, sizeof(arep_c_machine_login));
        ncmd = CMD_AS_REP_C_MACHINE_LOGIN;
        nlen = sizeof(arep_c_machine_login)-sizeof(CMD_HEADER);
        ncmdID = rand();
    }
}AREP_C_MACHINE_LOGIN, *PAREP_C_MACHINE_LOGIN;

第2步:统一发包规则(根据服务端的发包规则,首先必须要了解且亲自操作后才能做决定)
(1)客户端发包
 C_MACHINE_LOGIN_SYSTEM cmd;
 strcpy(cmd.sMachineCode,"20100904164702750199");//机器码

 if(send(socket_client,(char*)&cmd,sizeof(cmd),0)==SOCKET_ERROR)
 {
#ifdef _DEBUG  
  ::OutputDebugString("发送失败:发送机器码!\n");
#endif // _DEBUG
 }

(2)服务端收包
    PCMD_HEADER pcm = (PCMD_HEADER)pbuf;//收到登录包的结构体后,取出其ncmd标识符
    switch ( pcm->ncmd )
    {
    case CMD_C_MACHINE_LOGIN_SYSTEM://很明显这个pcm->ncmd,是登录包中ncmd标识符
        {
            onMachineLoginSystem((const PC_MACHINE_LOGIN_SYSTEM)pcm);//2次指针
        }
        break;

void MachineContext::onMachineLoginSystem(const PC_MACHINE_LOGIN_SYSTEM pLoginSystem)
{
    arep_c_machine_login arp_c_login;//回复包的结构体

(3)服务端收包,验证数据库后,开始发包
--->成功后发包(用包的指针形式发)
    //回复机器登录信息
    PAREP_C_MACHINE_LOGIN pArep = (PAREP_C_MACHINE_LOGIN)bytesbufer.getDynamicData();
    pArep->nStatus = STATUS_SUCCESS;
    pArep->nMachineID = _machine_id;
    pArep->nMachineStatus = _machine_status;
    send(bytesbufer.getData(),bytesbufer.getDataSize());
--->失败后发包(用包的结构体形式发)
        arep_c_machine_login arp_c_login;
        arp_c_login.nStatus = STATUS_FAILED;
        send((const char*)&arp_c_login,sizeof(arp_c_login));
(4)以此类推,不断循环

----------------------------------------公共头文件

command.h头文件:

#ifndef COMMAND_WL2011_H_INCLUDED
#define COMMAND_WL2011_H_INCLUDED

enum{   //所有命令字为整型32位

 //客户到服务器命令字
 CMD_C_MACHINE_LOGIN_SYSTEM = 0x01100001,             //登录包(Client->Server)

 //服务器回复客户端命令字  
 CMD_AS_REP_C_MACHINE_LOGIN = 0x01100002,             //回复登录包(Server->Client)

};

#endif//COMMAND_WL2011_H_INCLUDED

 

strPublic.h头文件:

#ifndef PUBLIC_WL2011_H_INCLUDED
#define PUBLIC_WL2011_H_INCLUDED


#include <memory.h>        //memset()需要用到
#include <stdlib.h>        //rand()需要用到


#pragma pack(push,1)       //为什么定义这个,因为sizeof引起.定义上,统一采用此种方案

typedef struct cmdHeader{
 unsigned long  nReserved;    
 unsigned long  ncmd;
 unsigned long  ncmdID;    //指令ID
 unsigned long  nSerial;             //指令序列
 unsigned long  nlen;                //数据包长度
 cmdHeader(){
  memset(this, 0x00, sizeof(cmdHeader));
  ncmdID = rand();
 }
}CMD_HEADER, *PCMD_HEADER;


//预定义,因为有太多的结构体包含此定义了(这样扩展也好,一改,所有结构体包统一更改),注意:\后面千万不要放//注释符之类
#define PUB_HEADER  int nReserved;  \
 unsigned long  ncmd;       \
 unsigned long  ncmdID;     \
 unsigned long  nSerial;    \
 unsigned long  nlen


#pragma pack(pop) //恢复对齐状态


#endif//PUBLIC_WL2011_H_INCLUDED


 strServer头文件:

#ifndef WLSERVER_WL2011_H_INCLUDED
#define WLSERVER_WL2011_H_INCLUDED

#include "strPublic.h"

#pragma pack(push,1)


typedef struct  arep_c_machine_login{
 PUB_HEADER;
 unsigned long   nStatus;     //回复机器登录状态(可能需要公钥验证,后续处理)
 unsigned long   nMachineID;         //机器码对应数据库唯一ID
 unsigned long   nMachineStatus;     //终端机状态信息,0-未激活状态 1-激活状态 2-已注册状态
 unsigned long   nReserved1;      //保留字段
 unsigned long   nReserved2;      //保留字段
 //跟包信息position_info_detail,见strPublic.h,仅当MachineStatus==2时有效
 arep_c_machine_login(){
  memset(this, 0x00, sizeof(arep_c_machine_login));
  ncmd = CMD_AS_REP_C_MACHINE_LOGIN;
  nlen = sizeof(arep_c_machine_login)-sizeof(CMD_HEADER);
  ncmdID = rand();
 }
}AREP_C_MACHINE_LOGIN, *PAREP_C_MACHINE_LOGIN;

 

#pragma pack(pop)

#endif//WLSERVER_WL2011_H_INCLUDED

strClient源文件:

#ifndef WLCLIENT_WL2011_H_INCLUDED
#define WLCLIENT_WL2011_H_INCLUDED

#include "strPublic.h"

#pragma pack(push,1)


typedef struct  c_machine_login_system{
 PUB_HEADER;
 char            sMachineCode[32];       //机器码
 unsigned long   nVersion;               //客户端版本号,n进制 n可选
 unsigned long   nReserved1;          //保留字段
 unsigned long   nReserved2;          //保留字段
 c_machine_login_system(){
  memset(this, 0x00, sizeof(c_machine_login_system));
  ncmd = CMD_C_MACHINE_LOGIN_SYSTEM;
//  nlen = sizeof(c_machine_login_system)-sizeof(CMD_HEADER);
  ncmdID = rand();
 }
}C_MACHINE_LOGIN_SYSTEM, *PC_MACHINE_LOGIN_SYSTEM;

 

#pragma pack(pop)

#endif//WLCLIENT_WL2011_H_INCLUDED

----------------------------------------服务端代码:


int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
 //第1步:初始化,创建,连接套接字//
 WSADATA WsaData;int err;
 err = WSAStartup (0x0002, &WsaData);if(err!=0) return 1;    //0x0002代表版本2.0

 SOCKET socket_bind=socket(AF_INET,SOCK_STREAM,0);
 if(socket_bind==INVALID_SOCKET)
 {
#ifdef _DEBUG  
  ::OutputDebugString("创建套接字错误!\n");
#endif // _DEBUG
  return 1;
 }
 SOCKADDR_IN serveraddr;
 serveraddr.sin_family=AF_INET;
 serveraddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
 serveraddr.sin_port=htons(55551);
 bind(socket_bind,(SOCKADDR*)&serveraddr,sizeof(SOCKADDR));
 listen(socket_bind,50);

 

 SOCKET client;
 SOCKADDR_IN saRemote;int nRemoteLen = sizeof(saRemote);
 if((client=accept(socket_bind,(sockaddr*)&saRemote, &nRemoteLen))!=INVALID_SOCKET )
 { 

  while(1)
  {
   char m_RecvBuffer[4096];
   int  m_nRecvedSize=sizeof(m_RecvBuffer);
   PCMD_HEADER pcm = (PCMD_HEADER)m_RecvBuffer;

   if(recv(client,(char*)&m_RecvBuffer,m_nRecvedSize,0)==SOCKET_ERROR)
   {
    AfxMessageBox("接收失败,退出重recv接收!");   
    break;
   }
   else 
   {    
    switch ( pcm->ncmd )
    {
    case CMD_C_MACHINE_LOGIN_SYSTEM://很明显这个pcm->ncmd,是登录包中ncmd标识符
     {
      printf("收到登录包(Client->Server)!\n");
      PC_MACHINE_LOGIN_SYSTEM pcmd=(PC_MACHINE_LOGIN_SYSTEM)pcm;
      printf("收到的机器码是:%s\n",pcmd->sMachineCode);

     }
     break;
    }
   }
  } 
 

 }
 closesocket(client);
 WSACleanup();
 return 0;
}

----------------------------------------客户端代码:

DWORD WINAPI Connect(LPVOID lpParam);
void CMyDlg::OnBnClickedButtonConnect()
{
 CreateThread(0,0,Connect,this,0,0);
}

SOCKET socket_client;
DWORD WINAPI Connect(LPVOID lpParam)
{
 //第1步:初始化,创建,连接套接字//
 WSADATA WsaData;int err;
 err = WSAStartup (0x0002, &WsaData);if(err!=0) return 1;    //0x0002代表版本2.0

 socket_client=socket(AF_INET,SOCK_STREAM,0);
 if(socket_client==INVALID_SOCKET){AfxMessageBox("创建套接字错误!\n");return 1;}

 SOCKADDR_IN sconnect_pass;
 sconnect_pass.sin_family=AF_INET;
 sconnect_pass.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
 sconnect_pass.sin_port=htons(55551);
 if(SOCKET_ERROR==connect(socket_client,(SOCKADDR*)&sconnect_pass,sizeof(SOCKADDR))) {printf("连接服务端失败!\n");return 1;}
 else
 {

  while(1)
  {
   char m_RecvBuffer[4096];
   int  m_nRecvedSize=sizeof(m_RecvBuffer);
   PCMD_HEADER pcm = (PCMD_HEADER)m_RecvBuffer;

   if(recv(socket_client,(char*)&m_RecvBuffer,m_nRecvedSize,0)==SOCKET_ERROR)
   {
    AfxMessageBox("接收失败,退出重recv接收!");   
    break;
   }
   else 
   {
    switch ( pcm->ncmd )
    {
    case CMD_AS_REP_C_MACHINE_LOGIN://很明显这个pcm->ncmd,是登录包中ncmd标识符
     {
      AfxMessageBox("收到登录包(Client->Server)!");
     }
     break;
    }
   }
  }
  
 }

 closesocket(socket_client);
 WSACleanup();
 return 0;
}

void CMyDlg::OnBnClickedButtonRun()
{/*
 PC_MACHINE_LOGIN_SYSTEM cmd;
 strcpy(cmd->sMachineCode,"20100904164702750199");//机器码
 cmd->

CMD_HEADER cmd1;
// cmd1.
if(send(socket_client,(char*)&cmd,sizeof(cmd),0)==SOCKET_ERROR)
{
#ifdef _DEBUG  
 ::OutputDebugString("发送失败:发送机器码!\n");
#endif // _DEBUG
}
*/ 
 //发包
 C_MACHINE_LOGIN_SYSTEM cmd;
 strcpy(cmd.sMachineCode,"20100904164702750199");//机器码
 CString str;
 str.Format("%d",cmd.nVersion);

// cmd.nVersion
// CMD_HEADER cmd1;
// cmd1.
 if(send(socket_client,(char*)&cmd,sizeof(cmd),0)==SOCKET_ERROR)
 {
#ifdef _DEBUG  
  ::OutputDebugString("发送失败:发送机器码!\n");
#endif // _DEBUG
 }

}

 

附注1:第2种项目正式收发包规则举例代码,已上传至邮箱,即取即用
附注2:
//预定义,因为有太多的结构体包含此定义了(这样扩展也好,一改,所有结构体包统一更改)
//提醒:千万别在" nReserved;  \"的后面使用注释符//,否则会报错!
#define PUB_HEADER  int nReserved;  \
unsigned long  ncmd;       \
unsigned long  ncmdID;     \
unsigned long  nSerial;    \
unsigned long  nlen
它是属于结构体的成员,但是只有通过指针才能访问到.毕竟它在结构体内是#define形式,
另外结构体内的构造函数初始化了一些东西,起主要作用的是 ncmd = CMD_C_MACHINE_LOGIN_SYSTEM;表示标识符
其实很好理解:同第1种一样.ncmd对ncmd,即发包方有个ncmd,那么收包也有个对应的ncmd

附注3:
附加包含目录:上1层用../command,上2层用../../command.注意:选择的时候一定要选择整个工程(对应整个工程),而不是选择单个文件(对应单个文件),因为它们属性是不同的.

按需所写了,也许有的文件不需要附加包含此目录
至此,原理已讲完了,目前只做了登录包介绍,后期随需求扩展

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值