`
jiagou
  • 浏览: 2537403 次
文章分类
社区版块
存档分类
最新评论

C 消息连接的一种系统方法

 
阅读更多

 用过C 进行过面向对象程序设计的用户都知道,程序中的对象很少单独存在。不考虑对象间的相互作用几乎是不可能的。所以,标识对象间的关系或建立对象间的消息连接是面向对象程序设计的一项重要任务。本文着重从C 程序设计的角度,提出一种建立对象间消息连接的实用方法。如果你想详细了解面向对象程序设计技术,请参阅有关专著。大家都知道对象是数据和方法的封装体。在C 中,它们分别表现为数据成员和成员函数。程序设计者通过执行对象的各种方法,来改变对象的状态(即改变对象的属性数据)。从而使该对象发生某些“事件”。当一对象发生某事件时,它通常需向其它相关对象发送“消息”,请求它们作出一些处理。 这时,发生事件并向其它对象请求处理的对象被称为“事件对象”,而处理事件的对象被称为“回调对象”。回调对象对事件的处理称为“回调函数”。在C 中,这一过程相当于:当事件对象发生事件时,调用回调对象的某些成员函数。通常的作法是回调对象向事件对象传递对象指针。但这种方法不通用。为了减少程序设计的工作量,本文提出一种建立对象间消息连接的系统方法。它的思路是:将“事件发生→请求处理→执行处理”这一过程抽象成一个“回调”(CallBack)类。通过继承,用户可以轻松获取建立对象间消息连接的机制。

  一、回调类的数据结构及其成员函数

  本文提出的CallBack类支持三种回调函数。它们是:回调对象中的成员函数,属于回调类的静态成员函数和普通的C函数。CallBackle类中包含一回调函数表callBackList。它用于记录事件名称,指向回调函数及回调对象的指针。该表的每一个节点为一个事件记录EventRecord。每个事件记录包含三个域:事件名指针eventName,指向回调对象的指针pointerToCBO,指向回调函数的指针pointerToCBF或pointerToCBSF(其中,pointerToCBF指向回调对象的成员函数,pointerToCBSF指向回调类的静态成员函数或普通函数。它们同处于一共用体内)。CallBack类所提供的回调机制是这样的:在事件对象上注册回调对象中的回调函数;当事件发生时,事件对象在其回调表中检索并执行回调函数。从而使二者的消息连接得以建立。(关于该类的具体实现,请参阅文后所附的程序清单) 回调对象

  事件对象

事件名 回调对象指针 回调函数指针
“event” pointerCBO
pointerToCBF或pointerTOCBSF


  AddCallBack: 注册事件名和指向回调函数,回调对象的指针

  CallCallBack: 在回调表中,检索注册在指定事件上回调函数并调用它们

  事件发生时,调用CallCallBack函数

  对事件event进行处理的成员函数

  从CallBack类继承的回调表callBackList, 成员函数AddCallBack和CallCallBack。

  当回调函数为静态成员函数或普通C函数时, pointerToCBO为NULL。

  事件名是回调表callBackLis中的检索关键字。

  回调对象中其它成员函数

  CallBack类的成员函数AddCallBack用来将回调函数注册到事件对象的回调表中。它有两个重载版本:

void CallBack::AddCallBack(char *event,CallBackFunction cbf,CallBack *p);

void CallBack::AddCallBack(char *event,CallBackStaticFunction cbsf);


  其中,第一个AddCallBack用来将某回调对象的成员函数注册到事件对象的回调表中。第二个AddCallBack用来将或某回调类的静态成员函数注册到事件对象的回调表中。在上参数表中,event是指向事件名字符串的指针,p是指向回调对象的指针,cbf和cbsf分别是指向成员函数及静态成员函数(或普通函数)的指针。当回调函数来自某回调对象SomeObject时,传递成员函数指针应采用如下格式:(CallBackFunction)&SomeObject::MemberFunctionName; 传递SomeObject类的某静态成员函数指针应采用格式:(CallBackStaticFunction)& SomeObject::FunctionName;传递程序中普通函数指针时,只需传递函数名即可。

  CallBack类的成员函数void CallBack::CallCallBack(char *ename, CallData calldata = NULL)用来调用注册在事件ename上的所有回调函数。其中,calldata为数据指针(CallData实际上就是void*,详见程序清单)。事件对象可通过它向回调对象传递有用的数据。该成员函数通常在事件对象的成员函数中调用,因为通常只有事件对象的成员函数才能改变对象的内部数据,从而使某些事件发生。

  成员函数RemoveCallback用来删除注册在事件对象上的回调函数。它的三个重载版本依次为:

void CallBack::RemoveCallBack(char *event,CallBackFunction cbf,CallBack *p);

void CallBack::RemoveCallBack(char *event,CallBackStaticFunction cbsf);

void CallBack::RemoveCallBack(char *event);


  其中,event,cbf,cbsf,p等参数和成员函数AddCallBack中各参数一样。第一个RemoveCallBack用于删除注册在事件event上某回调对象的一个成员函数。第二个RemoveCallBack用于删除注册在事件event上的某普通函数或某回调类的一个静态成员函数。第三个RemoveCallBack用于删除注册在事件event上的全部回调函数。

共3页。 1 2 3 :  二、CallBack类的使用方法

  使用CallBack类,可按以下步骤进行:

  1.确定程序中哪些对象间存在关系,需要建立消息连接。并确定在各特定消息连接关系中,哪个对象是事件对象,哪个对象是回调对象。

  2.事件对象类和回调对象类都必须从CallBack类继承,以获得回调支持。

  3.为事件对象注册回调数据。包括:事件名,回调函数名,指向回调对象的指针。

  4.当你感兴趣的事件发生时,在事件对象类引发事件的成员函数中调用CallCallBack函数。

  下面是一个具体的例子。通过它你会对Callback类的使用方法有进一步的了解。

//测试程序文件:test.cpp

#include"callback.h"

//“扬声器”类

class Speaker:public CallBack

{

private:
 int volume;
public:
 Speaker(int v): volume(v) {}
void IncreaseVolume(int v) //增加音量成员函数
 {
  volume = v;
  if(volume > 20){ //“音量大于20”事件发生了
   //调用注册在两事件上的回调函数
   CallCallBack("音量改变了");
   CallCallBack("音量大于20", &volume);
 }
}

void DecreaseVolume(int v) //降低音量成员函数

{
 volume -= v;
 if(volume < 5){ //“音量小于5”事件发生了
 //调用注册在两事件上的回调函数
  CallCallBack("音量改变了");
  CallCallBack("音量小于5", &volume);
}

}

};

//“耳朵”类

class Ear : public CallBack

{
 public:
  static void Response(CallData callData) //对“音量改变”的反应
  {
   cout<<"音量改变了."<<endl;
  }

void HighVoiceResponse(CallData callData)//对高音的反应

{
 cout<<”喂!太吵了!现在音量是:"<<*((int *)callData)<<endl;
}

void LowVoiceResponse(CallData callData)// 对低音的反应

{
 cout<<"啊!我听不清了。现在音量是:"<<*((int *)callData)<<endl;
}

};

void main(void)

{
Speaker s(10); //现在音量为10
Ear e;

//为事件对象s注册回调函数
s.AddCallBack("音量大于20”,(CallBackFunction)&Ear::HighVoiceResponse,&e);
s.AddCallBack("音量小于5”,(CallBackFunction)&Ear::LowVoiceResponse,&e);
s.AddCallBack("音量改变了",(CallBackStaticFunction)&Ear::Response);
s.IncreaseVolume(12);//将音量增加12,现在音量位22
s.DecreaseVolume(20);//将音量减少20,现在音量位2

}

  运行结果:

  音量改变了.

   喂!太吵了!现在音量是:22

  音量改变了.

   啊!我听不清了。现在音量是:2

  在上例中,扬声器对象s为事件对象,耳朵对象e为回调对象。。s上被注册了三个事件:“音量改变了”,“音量大于20”,“音量小于5”。 回调函数分别为:Ear::Response, Ear::HighVoiceResponse,Ear::LowVoiceResponse。当扬声器s通过其成员函数IncreaseVolume和 DecreaseVolume改变音量时,回调对象e会自动作出反应。可见,通过使用CallBack类,在对象间建立消息连接已变为一项很简单和优美的工作。

共3页。 9 1 2 3 :  附:程序清单(本程序在MS VC 5.0和TC 3.0上均编译通过)

//回调类的类结构:callback.h

#ifndef _CALLBACK_H

#define _CALLBACK_H

#include<stdlib.h>

#include<string.h>

#include<iostream.h>

#define CALLBACKLIST_INIT_SIZE 10

#define CALLBACKLIST_INCREMENT 5

class CallBack;

typedef void *CallData;//回调数据指针类型定义

typedef void (CallBack::*CallBackFunction)(CallData); //指向回调成员函数的指针

typedef void (*CallBackStaticFunction)(CallData); //指向静态成员函数或普通函数的指针类型定义

class EventRecord{

private:

char *eventName; //回调事件名称

CallBack *pointerToCBO;//指向回调对象的指针

//指向成员函数的指针和指向静态成员函数(或普通函数)指针的共用体

union{

CallBackFunction pointerToCBF;

CallBackStaticFunction pointerToCBSF;

};

public:

EventRecord(void); //事件记录类的缺省构造函数

//构造包含成员函数的事件记录

EventRecord(char *ename,CallBack *pCBO,CallBackFunction pCBF);

//构造包含静态成员函数或普通函数的事件记录

EventRecord(char *ename,CallBackStaticFunction pCBSF);

~EventRecord(void);//析构事件记录

void operator = (const EventRecord& er);//重载赋值运算符

//判断当前事件记录的事件名是否为ename

int operator == (char *ename) const;

//判断当前事件记录是否和指定事件记录相等

int operator == (const EventRecord& er) const;

void Flush(void); //将当前事件记录清空

int IsEmpty(void) const;//判断事件记录是否为空(即事件名是否为空)

friend class CallBack; //让CallBack类能访问EventRecord的私有成员;

};

class CallBack {

private:

EventRecord *callBackList; //回调事件表

int curpos; //当前事件记录位置

int lastpos; //回调表中最后一空闲位置

int size; //回调表的大小

void MoveFirst(void) { curpos = 0; }//将当前记录置为第一条记录

void MoveNext(void) //将下一条记录置为当前记录

{

if(curpos == lastpos) return;

curpos ;

}

//判断回调表是否被遍历完

int EndOfList(void) const { return curpos == lastpos; }

public:

CallBack(void);//构造函数

CallBack(const CallBack& cb);//拷贝构造函数

~CallBack(void);//析构函数

void operator = (const CallBack& cb);// 重载赋值运算符

//将回调对象的成员函数、静态成员函数(或普通函数)

//注册为事件对象的回调函数

void AddCallBack(char *event,CallBackFunction cbf,CallBack *p);

void AddCallBack(char *event,CallBackStaticFunction cbsf);

//删除注册在指定事件上的回调函数

void RemoveCallBack(char *event,CallBackFunction cbf,CallBack *p);

void RemoveCallBack(char *event,CallBackStaticFunction cbsf);

void RemoveCallBack(char *event);// 删除某事件的全部记录

//执行注册在某一事件上的所有回调函数

void CallCallBack(char *event, CallData calldata = NULL);

};

#endif

//回调类的实现:callback.cpp

#include"callback.h"

 //EventRecord类的实现

EventRecord::EventRecord(void)

{

eventName = NULL;

pointerToCBO = NULL;

//因为sizeof(CallBackFunction) > sizeof(CallBackStaticFunction)

pointerToCBF = NULL;

}

EventRecord::EventRecord(char *ename, CallBack *pCBO, CallBackFunction pCBF)

:pointerToCBO(pCBO), pointerToCBF(pCBF)

{

eventName = strdup(ename);

}

EventRecord::EventRecord(char *ename, CallBackStaticFunction pCBSF)

:pointerToCBO(NULL), pointerToCBSF(pCBSF)

{

eventName = strdup(ename);

}

EventRecord::~EventRecord(void)

{

if(eventName) delete eventName;

}

void EventRecord::operator = (const EventRecord& er)

{

if(er.eventName)

eventName = strdup(er.eventName);

else

eventName = NULL;

pointerToCBO = er.pointerToCBO;

pointerToCBF = er.pointerToCBF;

}

int EventRecord::operator == (char *ename) const

{

if((eventName == NULL)||ename == NULL)

return eventName == ename;

else

return strcmp(eventName,ename) == 0;

}

int EventRecord::operator == (const EventRecord& er) const

{

return (er == eventName) /*er和eventname不能交换位置*/

&&(pointerToCBO == er.pointerToCBO)

&&(pointerToCBO ?

(pointerToCBF == er.pointerToCBF):

(pointerToCBSF == er.pointerToCBSF));

}

void EventRecord::Flush(void)

{

if(eventName){

delete eventName;

eventName = NULL;

}

pointerToCBO = NULL;

pointerToCBF = NULL;

}

int EventRecord::IsEmpty(void) const

{

if(eventName == NULL)

return 1;

else

return 0;

}

//Callback类的实现

CallBack::CallBack(void)

{

//按初始尺寸为回调表分配内存空间

callBackList = new EventRecord[CALLBACKLIST_INIT_SIZE];

if(!callBackList){

cerr<<"CallBack: memory allocation error."<<endl;

exit(1);

}

size = CALLBACKLIST_INIT_SIZE;

lastpos = 0;

curpos = 0;

}

CallBack::CallBack(const CallBack& cb): curpos(cb.curpos),lastpos(cb.lastpos),size(cb.size)

{

callBackList = new EventRecord[size];

if(!callBackList){

cerr<<"CallBack: memory allocation error."<<endl;

exit(1);

}

//一一复制各条事件记录

for(int i = 0; i < size; i ) callBackList[i] = cb.callBackList[i];

}

void CallBack::operator = (const CallBack& cb)

{

curpos = cb.curpos;

lastpos = cb.lastpos;

size = cb.size;

delete [] callBackList;//删除旧的回调表

callBackList = new EventRecord[size];//重新分配内存空间

if(!callBackList){

cerr<<"CallBack: memory allocation error."<<endl;

exit(1);

}

//一一复制各条事件记录

for(int i = 0; i < size; i ) callBackList[i] = cb.callBackList[i];

}

CallBack::~CallBack(void)

{

delete [] callBackList;

}

void CallBack::AddCallBack(char *event, CallBackFunction pCBF, CallBack *pCBO)

{

//如事件名为空,退出

if( (event == NULL)?1:(strlen(event) == 0)) return;

//寻找因删除事件记录而产生的第一个空闲位置,并填写新事件记录

for(int start=0;start<lastpos;start )

if(callBackList[start].IsEmpty()){

callBackList[start] = EventRecord(event,pCBO,pCBF);

break;

}

if(start < lastpos) return; //确实存在空闲位置

//没有空闲位置,在回调表后追加新记录

if(lastpos == size) //回调表已满,需“伸长”

{

EventRecord *tempList = callBackList;//暂存旧回调表指针

//以一定的步长“伸长”回调表

callBackList = new EventRecord[size CALLBACKLIST_INCREMENT];

if(!callBackList){

cerr<<"CallBack: memory allocation error."<<endl;

exit(1);

}

//复制旧回调表中的记录

for(int i = 0; i < size; i ) callBackList[i] = tempList[i];

delete [] tempList;//删除旧回调表

size = CALLBACKLIST_INCREMENT;//记下新回调表的尺寸

}

//构造新的事件记录并将其填入回调表中

callBackList[lastpos] = EventRecord(event,pCBO,pCBF);

lastpos ;

}

void CallBack::AddCallBack(char *event,CallBackStaticFunction pCBSF)

{

if( (event == NULL)?1:(strlen(event) == 0)) return;

for(int start=0;start<lastpos;start )

if(callBackList[start].IsEmpty()){

callBackList[start] = EventRecord(event,pCBSF);

break;

}

if(start < lastpos) return; //a hole is found

if(lastpos == size) //event list is insufficient

{

EventRecord *tempList = callBackList;

callBackList = new EventRecord[size CALLBACKLIST_INCREMENT];



if(!callBackList){

cerr<<"CallBack: memory allocation error."<<endl;

exit(1);

}

for(int i = 0; i < size; i ) callBackList[i] = tempList[i];

delete [] tempList;

size = CALLBACKLIST_INCREMENT;

}



callBackList[lastpos] = EventRecord(event,pCBSF);

lastpos ;

}



//删除注册在指定事件上的成员函数

void CallBack::RemoveCallBack(char *event, CallBackFunction pCBF, CallBack *pCBO)

{

if( (event == NULL)?1:(strlen(event) == 0)) return;

EventRecord er(event,pCBO,pCBF);



for(int i = 0; i < lastpos; i )

if(callBackList[i] == er) callBackList[i].Flush();

}



//删除注册在指定事件上的静态成员函数或普通函数

void CallBack::RemoveCallBack(char *event,CallBackStaticFunction pCBSF)

{

if( (event == NULL)?1:(strlen(event) == 0)) return;

EventRecord er(event,pCBSF);



for(int i = 0; i < lastpos; i )

if(callBackList[i] == er) callBackList[i].Flush();

}

//删除注册在指定事件上的所有回调函数

void CallBack::RemoveCallBack(char *event)

{

if( (event == NULL)?1:(strlen(event) == 0)) return;

for(int i = 0; i < lastpos; i )

if(callBackList[i] == event) callBackList[i].Flush();

}



void CallBack::CallCallBack(char *event, CallData callData)

{

if( (event == NULL)?1:(strlen(event) == 0)) return;



CallBack *pCBO;

CallBackFunction pCBF;

CallBackStaticFunction pCBSF;



MoveFirst();

while(!EndOfList())

{

//如当前事件记录和指定事件不匹配,转入下一条记录继续循环

if(!(callBackList[curpos] == event))

{

MoveNext();

continue;

}

//如找到匹配记录

pCBO = callBackList[curpos].pointerToCBO;

//如事件记录中回调对象指针为空,说明该记录中保存的是静态函数指针

if(pCBO == NULL){

pCBSF = callBackList[curpos].pointerToCBSF;

pCBSF(callData);//调用该静态回调函数

}

else //如事件记录中回调对象指针非空,说明该记录中保存的是成员函数指针

{

pCBF = callBackList[curpos].pointerToCBF;

(pCBO->*pCBF)(callData);// 调用该回调对象的成员函数

}

MoveNext();

}

分享到:
评论

相关推荐

    Linux系统C语言编程连接MySql数据库实现的用户用户组色权限管理系统(图形界面)

    《Mysql 最后程序的总结—— Linux系统C语言编程连接MySql数据库实现的用户角色权限管理系统》 修改的问题: 1、新增 添加用户组模块; 2、新增 显示用户组模块; 3、修改 新增用户模块 选择 其所属用户组 并把...

    C语言连接SQL数据库

    在配置ODBC时有用户DSN、系统DSN、和文件DSN三种方法,为了稳妥起见,采用系统DSN。 DSN的名字叫LocalServer,帐号:sa,密码123456 第二步:打开VC,建一个win32 Console Application工程 ………………

    一种应用于车载的无线射频识别系统设计

    如传感器、射频识别(RFID)技术、全球定位系统、红外感应器、激光扫描器、气体感应器等各种装置与技术,实时采集任何需要监控、连接、互动的物体或过程,采集其声、光、电、生物、位置等各种需要的信息,与互联网结合...

    使用C语言与ODBC驱动连接SQL Server数据库搭建旅游管理系统。.zip

    管理系统是一种通过计算机技术实现的用于组织、监控和控制各种活动的软件系统。这些系统通常被设计用来提高效率、减少错误、加强安全性,同时提供数据和信息支持。以下是一些常见类型的管理系统: 学校管理系统: ...

    嵌入式系统/ARM技术中的一种I2C设备控制方法的设计和实现

    是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,器件封装形式小,通信速率较高等优点。AT91SAM7X256是Atmel公司于2005年推出的基于ARM7的工业级芯片,他以...

    基于c语言实现的UDP传输系统

    快速传输: UDP是一种无连接、低开销的传输协议,适用于需要快速传输数据的场景。 无需握手: UDP不需要建立连接,减少了通信的延迟时间。 简单实现: 基于C语言实现UDP传输系统相对简单,适合快速开发和原型验证。 ...

    C语言教程-UDP传输系统实现,启动步骤和源码分享与教程解析

    UDP是一种无连接的传输协议,它不保证数据传输的可靠性,但是传输速度快。在C语言中,实现UDP传输系统需要使用sock编程。以下是实现UDP传输系统的启动步骤和源码分享:. 创建sock:使用sock()函数创建一个sock,指定...

    C程序设计语言》(第2版·新版

    对于任何一种操作系统环境,C函数的ABI(Application Binary Interface)与汇编语言的子过程(routine/procedure)的ABI一定是完全兼容的。 尽管C语言提供了许多低级处理的功能,但仍然保持着良好跨平台的特性,以一...

    论文研究-一种基于以太网的防尾随电梯门禁控制系统.pdf

    针对物联网技术在社区管理中的广泛应用,采用嵌入式技术,利用现代高速发展的网络平台设计了一种基于STM32单片机的电梯门禁控制系统。控制系统在电梯控制系统的基础上,结合门禁控制技术、图像识别技术和RFID技术,...

    一个基于 C 语言的学生管理系统,使用了并归排序,不需要连接数据库.zip

    管理系统是一种通过计算机技术实现的用于组织、监控和控制各种活动的软件系统。这些系统通常被设计用来提高效率、减少错误、加强安全性,同时提供数据和信息支持。以下是一些常见类型的管理系统: 学校管理系统: ...

    如何测试Linux下tcp最大连接数限制详解

    关于TCP服务器最大并发连接数有一种误解就是“因为端口号上限为65535,所以TCP服务器理论上的可承载的最大并发连接数也是65535”。 先说结论:对于TCP服务端进程来说,他可以同时连接的客户端数量并不受限于可用端口...

    几种DSP与外接存储器的连接方法

    存储器接口分为ROM接口和RAM接口两种。ROM包括EPROM和FLASH,而RAM主要是指SRAM。TMS320C5409具有32K字的片内RAM和...因此设计一个TMS320C5409硬件系统一般应该包括其与EPROM/FLASH和SRAM的接口设计,以存放程序和数据

    将数据包与 Linux 系统中的负责进程相关联。通过添加进程信息来诊断连接_C语言_代码_相关文件_下载

    correlator,一种将数据包与进程相关联的开源工具 诊断在网络上看到的问题。 虽然将数据包与进程相关联的想法很简单,但 实现需要修改内核的方式来改变 网络堆栈工作。也许这种并发症是造成 事实上,没有其他工具...

    C语言基础面试题(06-Unix系统编程).docx

    许多键盘或显示器受到一种机制的管理连接到不同的程序。 2操作系统的职责 计算机用操作系统管理所有资源,将不同设备和程序连接起来。操作系统其实是一个特殊 的程序。 操作系统又称为内核。 3系统资源 处理器:程序...

    二级C语言公共基础知识

    (33) Jackson结构化程序设计方法是英国的M.Jackson提出的,它是一种面向______的设计方法。 答:数据结构 (34) 数据库设计分为以下6个设计阶段:需求分析阶段、______、逻辑设计阶段、物理设计阶段、实施阶段、运行...

    Delphi7 Access实战图书管理系统.rar

     第一种方法请先将database文件夹拷贝到C:\中。  第二种方法把图书管理系统示例中DM数据窗口内ADOConnection1组件的数据库连接路径改成“..\DataBase\BookManager.mdb”。用户号密码:liu a 更多的用户名、密码和...

    Konnected 将有线传感器和开关连接到 SmartThings、Home Assistant、Hubitat _C语言

    运行此软件的设备可以使用我们的2 路实时 REST API连接到本地家庭自动化平台, 或连接到 Konnected Cloud,这是一种云服务,可以与 SmartThings 或 Alexa 进行简单集成(目前免费使用!)。 该项目基于NodeMCU Lua ...

    C语言基础教程TXT

    8.5 实例—fopen和getc函数的一种实现 方法 8.6 实例—目录显示 8.7 实例—存储分配程序 附录A 参考手册 A.1 引言 A.2 词法规则 A.3 语法符号 A.4 标识符的含义 A.5 对象和左值 A.6 转换 A.7 表达式 A.8 ...

    c语言课程设计学生成绩管理系统(1).doc

    排序函数void sort() 这个函数用printf输出操作提示,可以用for,if来实现对每一种方式的排序,在按数字 时用通过函数的调用地方法完成进入每一种排序的页面。 退出界面函数void quit() 这个函数主要通过printf输出...

    基于PCAP的网络入侵检测系统C语言实现源码+项目说明+报告PDF(高分课设)zip

    基于PCAP的网络入侵检测系统C语言实现源码+项目说明+报告PDF(高分课设)zip本项目将实现一个基本的网络入侵检测系统, 涉及到对TCP/IP网络协议和线程的知识理解. 项目将使用libpcap库在特定的接口拦截 (嗅探) 数据包...

Global site tag (gtag.js) - Google Analytics