5.rocketmq源代码学习--客户端消息消费(消息拉取)

一、获取消费进度

rocketmq有两种消费模式:
广播模式—同一个消费者ID对应的多个消费者,将消息各消费一遍
集群模式–同一个消费者ID对应的多个消费者,共同消费一份消息

广播模式

对应LocalFileOffsetStore,从本地获取消费进度

public final static String LocalOffsetStoreDir = System.getProperty(
            "rocketmq.client.localOffsetStoreDir",
            System.getProperty("user.home") + File.separator + ".rocketmq_offsets");


    public LocalFileOffsetStore(MQClientInstance mQClientFactory, String groupName) {
        this.mQClientFactory = mQClientFactory;
        this.groupName = groupName;
        //存储路径   用户目录/.rocketmq_offsets/clientId/groupName/offset.json
        this.storePath = LocalOffsetStoreDir + File.separator + //
                this.mQClientFactory.getClientId() + File.separator + //
                this.groupName + File.separator + //
                "offsets.json";
    }

集群模式

从上一章:
4.rocketmq源代码学习----客户端消息消费(负载均衡)

我们知道,当rocketmq客户端启动的时候或者主题队列变更时
RebalanceService会调用computePullFromWhere()获取消费进度,然后构造PullRequest,调用PullMessageService提交了消费者请求

RebalancePushImpl.computeFromWhere()

 @Override
    public long computePullFromWhere(MessageQueue mq) {
        long result = -1;
        final ConsumeFromWhere consumeFromWhere =
                this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getConsumeFromWhere();
        final OffsetStore offsetStore = this.defaultMQPushConsumerImpl.getOffsetStore();
        switch (consumeFromWhere) {
        case CONSUME_FROM_LAST_OFFSET_AND_FROM_MIN_WHEN_BOOT_FIRST:
        case CONSUME_FROM_MIN_OFFSET:
        case CONSUME_FROM_MAX_OFFSET:
        case CONSUME_FROM_LAST_OFFSET: {
            long lastOffset = offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE);
            if (lastOffset >= 0) {
                result = lastOffset;
            }
            // First start,no offset
            else if (-1 == lastOffset) {
                if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    result = 0L;
                }
                else {
                    try {
                        result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq);
                    }
                    catch (MQClientException e) {
                        result = -1;
                    }
                }
            }
            else {
                result = -1;
            }
            break;
        }
        case CONSUME_FROM_FIRST_OFFSET: {
            long lastOffset = offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE);
            if (lastOffset >= 0) {
                result = lastOffset;
            }
            else if (-1 == lastOffset) {
                result = 0L;
            }
            else {
                result = -1;
            }
            break;
        }
        case CONSUME_FROM_TIMESTAMP: {
            long lastOffset = offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE);
            if (lastOffset >= 0) {
                result = lastOffset;
            }
            else if (-1 == lastOffset) {
                if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    try {
                        result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq);
                    }
                    catch (MQClientException e) {
                        result = -1;
                    }
                }
                else {
                    try {
                        long timestamp =
                                UtilAll.parseDate(
                                    this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer()
                                        .getConsumeTimestamp(), UtilAll.yyyyMMddHHmmss).getTime();
                        result = this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp);
                    }
                    catch (MQClientException e) {
                        result = -1;
                    }
                }
            }
            else {
                result = -1;
            }
            break;
        }

        default:
            break;
        }

        return result;
    }

rocketmq支持三种消费选择:

  • CONSUME_FROM_LAST_OFFSET:获取主题最大进度id,从最后开始消费
  • CONSUME_FROM_FIRST_OFFSET:从主题第一条消息开始消费
  • CONSUME_FROM_TIMESTAMP:从指定时间开始消费

但是…
但是你以为就都是这样的吗?
答案是否定的,因为在服务端藏着这么一段代码:

ClientManagerProcessor.queryConsumerOffset()

private RemotingCommand queryConsumerOffset(ChannelHandlerContext ctx, RemotingCommand request)
            throws RemotingCommandException {
        final RemotingCommand response =
                RemotingCommand.createResponseCommand(QueryConsumerOffsetResponseHeader.class);
        final QueryConsumerOffsetResponseHeader responseHeader =
                (QueryConsumerOffsetResponseHeader) response.readCustomHeader();
        final QueryConsumerOffsetRequestHeader requestHeader =
                (QueryConsumerOffsetRequestHeader) request
                    .decodeCommandCustomHeader(QueryConsumerOffsetRequestHeader.class);

        long offset =
                this.brokerController.getConsumerOffsetManager().queryOffset(
                    requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId());

        // 订阅组存在
        if (offset >= 0) {
            responseHeader.setOffset(offset);
            response.setCode(ResponseCode.SUCCESS);
            response.setRemark(null);
        }
        // 订阅组不存在
        else {
            long minOffset =
                    this.brokerController.getMessageStore().getMinOffsetInQuque(requestHeader.getTopic(),
                        requestHeader.getQueueId());
            // 订阅组不存在情况下,如果这个队列的消息最小Offset是0,则表示这个Topic上线时间不长,服务器堆积的数据也不多,那么这个订阅组就从0开始消费。
            // 尤其对于Topic队列数动态扩容时,必须要从0开始消费。
            if (minOffset <= 0
                    && !this.brokerController.getMessageStore().checkInDiskByConsumeOffset(
                        requestHeader.getTopic(), requestHeader.getQueueId(), 0)) {
                responseHeader.setOffset(0L);
                response.setCode(ResponseCode.SUCCESS);
                response.setRemark(null);
            }
            // 新版本服务器不做消费进度纠正
            else {
                response.setCode(ResponseCode.QUERY_NOT_FOUND);
                response.setRemark("Not found, V3_0_6_SNAPSHOT maybe this group consumer boot first");
            }
        }

        return response;
    }

通过注释一目了然,订阅组不存在情况下,如果这个队列的消息最小Offset是0,则表示这个Topic上线时间不长,服务器堆积的数据也不多,那么这个订阅组就从0开始消费(什么时候不为0呢,rocketmq消息可不是一直存在磁盘的哦,默认3天会做一次清理,此时最小的消费进度就不是0了)。=== 尤其对于Topic队列数动态扩容时,必须要从0开始消费。===

综上,消费进度是这样的:

1、如果消费者ID订阅关系存在,则从上一次的消费进度开始消费,不管设置的consumeFromWhere是啥…
2、如果消费者ID订阅关系不存在(即新的消费者),则
A、如果服务端主题最小进度为0(即主题的消息从没有被清理过),则从头开始消费
B、如果服务端主题最小进度 > 0,才是下面的

CONSUME_FROM_LAST_OFFSET:获取主题最大进度id,从最后开始消费
CONSUME_FROM_FIRST_OFFSET:从主题第一条消息开始消费
CONSUME_FROM_TIMESTAMP:从指定时间开始消费

知道消息从哪里消费后, 接下来就是从服务端拉取消息了:

二、拉取消息

从上一章,我们知道,RebalanceService负载均衡线程在分配队列后,会构造pullRequest,并调用dispatchPullRequest提交队列:

RebalancePushImpl.dispatchPullRequest

@Override
    public void dispatchPullRequest(List<PullRequest> pullRequestList) {
        for (PullRequest pullRequest : pullRequestList) {
            this.defaultMQPushConsumerImpl.executePullRequestImmediately(pullRequest);
            log.info("doRebalance, {}, add a new pull request {}", consumerGroup, pullRequest);
        }
    }

DefaultMQPushConsumerImpl.executePullRequestImmediately

 public void executePullRequestImmediately(final PullRequest pullRequest) {
        this.mQClientFactory.getPullMessageService().executePullRequestImmediately(pullRequest);
    }

PullMessageService.executePullRequestImmediately

public void executePullRequestImmediately(final PullRequest pullRequest) {
        try {
            this.pullRequestQueue.put(pullRequest);
        }
        catch (InterruptedException e) {
            log.error("executePullRequestImmediately pullRequestQueue.put", e);
        }
    }
  • RebalanceService负载均衡线程最终将本机器消费的队列对应的pullRequest提交到pullRequestQueue队列。
  • PullMessageService线程run方法就是从PullRequestQueue中获取PullRequest请求,去拉取消息

那我们来分析PullMessageService的下run()

@Override
    public void run() {
        log.info(this.getServiceName() + " service started");

        while (!this.isStoped()) {
            try {
	            //从pullRequestQueue中获取pullRequest
                PullRequest pullRequest = this.pullRequestQueue.take();
                if (pullRequest != null) {
	                //调用拉取消息方法
                    this.pullMessage(pullRequest);
                }
            }
            catch (InterruptedException e) {
            }
            catch (Exception e) {
                log.error("Pull Message Service Run Method exception", e);
            }
        }

        log.info(this.getServiceName() + " service end");
    }
    
    private void pullMessage(final PullRequest pullRequest) {
        final MQConsumerInner consumer = this.mQClientFactory.selectConsumer(pullRequest.getConsumerGroup());
        if (consumer != null) {
            DefaultMQPushConsumerImpl impl = (DefaultMQPushConsumerImpl) consumer;
            //调用consumer拉取消息方法
            impl.pullMessage(pullRequest);
        }
        else {
            log.warn("No matched consumer for the PullRequest {}, drop it", pullRequest);
        }
    }
   

再来看DefaultMqPushConsumerImpl.pullMessage方法:
pullMessage方法()思路为:
1、从broker服务端拉取消息
2、拉取消息后,提交给ConsumeMessageService消费
3、并将pullRequest继续扔到pullRequestQueue中,继续拉取消息

注意哦:rocketmq对拉取消息做了流控,避免客户端积累太多消息在内存中。

public void pullMessage(final PullRequest pullRequest) {
        final ProcessQueue processQueue = pullRequest.getProcessQueue();
        //processQueue啥时候会变成dropped,在负载均衡那一章中提过,当ReblanceService负载均衡线程,重新负载均衡后,队列减少时,则丢弃pullRequest请求
        if (processQueue.isDropped()) {
            log.info("the pull request[{}] is droped.", pullRequest.toString());
            return;
        }

        pullRequest.getProcessQueue().setLastPullTimestamp(System.currentTimeMillis());

        try {
            this.makeSureStateOK();
        }
        catch (MQClientException e) {
            log.warn("pullMessage exception, consumer state not ok", e);
            this.executePullRequestLater(pullRequest, PullTimeDelayMillsWhenException);
            return;
        }
		
        if (this.isPause()) {
            log.warn("consumer was paused, execute pull request later. instanceName={}",
                this.defaultMQPushConsumer.getInstanceName());
            this.executePullRequestLater(pullRequest, PullTimeDelayMillsWhenSuspend);
            return;
        }

        long size = processQueue.getMsgCount().get();
        //当本地拉取的消息积累>1000时,触发流控,避免将java内存撑爆
        if (size > this.defaultMQPushConsumer.getPullThresholdForQueue()) {
            this.executePullRequestLater(pullRequest, PullTimeDelayMillsWhenFlowControl);
            if ((flowControlTimes1++ % 1000) == 0) {
                log.warn("the consumer message buffer is full, so do flow control, {} {} {}", size,
                    pullRequest, flowControlTimes1);
            }
            return;
        }

        if (!this.consumeOrderly) {
            if (processQueue.getMaxSpan() > this.defaultMQPushConsumer.getConsumeConcurrentlyMaxSpan()) {
                this.executePullRequestLater(pullRequest, PullTimeDelayMillsWhenFlowControl);
                if ((flowControlTimes2++ % 1000) == 0) {
                    log.warn("the queue's messages, span too long, so do flow control, {} {} {}",
                        processQueue.getMaxSpan(), pullRequest, flowControlTimes2);
                }
                return;
            }
        }

        final SubscriptionData subscriptionData =
                this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());
        if (null == subscriptionData) {
            // 由于并发关系,即使找不到订阅关系,也要重试下,防止丢失PullRequest
            this.executePullRequestLater(pullRequest, PullTimeDelayMillsWhenException);
            log.warn("find the consumer's subscription failed, {}", pullRequest);
            return;
        }

        final long beginTimestamp = System.currentTimeMillis();

        PullCallback pullCallback = new PullCallback() {
            @Override
            public void onSuccess(PullResult pullResult) {
                if (pullResult != null) {
                    pullResult =
                            DefaultMQPushConsumerImpl.this.pullAPIWrapper.processPullResult(
                                pullRequest.getMessageQueue(), pullResult, subscriptionData);

                    switch (pullResult.getPullStatus()) {
                    case FOUND:
                        long prevRequestOffset = pullRequest.getNextOffset();
                        pullRequest.setNextOffset(pullResult.getNextBeginOffset());
                        long pullRT = System.currentTimeMillis() - beginTimestamp;
                        DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullRT(
                            pullRequest.getConsumerGroup(), pullRequest.getMessageQueue().getTopic(), pullRT);

                        long firstMsgOffset = Long.MAX_VALUE;
                        if (pullResult.getMsgFoundList() == null || pullResult.getMsgFoundList().isEmpty()) {
                            DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
                        }
                        else {
                            firstMsgOffset = pullResult.getMsgFoundList().get(0).getQueueOffset();

                            DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullTPS(
                                pullRequest.getConsumerGroup(), pullRequest.getMessageQueue().getTopic(),
                                pullResult.getMsgFoundList().size());
							//当从消费端拉取到消息后,将消息存入processQueue中
                            boolean dispathToConsume = processQueue.putMessage(pullResult.getMsgFoundList());
							//将消息提交给consumeMessageService,进行消息消费
                            DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest(//
                                pullResult.getMsgFoundList(), //
                                processQueue, //
                                pullRequest.getMessageQueue(), //
                                dispathToConsume);
							//继续提交拉取消息
                            if (DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval() > 0) {
                                DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest,
                                    DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval());
                            }
                            else {
                                DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
                            }
                        }

                        if (pullResult.getNextBeginOffset() < prevRequestOffset//
                                || firstMsgOffset < prevRequestOffset) {
                            log.warn(
                                "[BUG] pull message result maybe data wrong, nextBeginOffset: {} firstMsgOffset: {} prevRequestOffset: {}",//
                                pullResult.getNextBeginOffset(),//
                                firstMsgOffset,//
                                prevRequestOffset);
                        }

                        break;
                    case NO_NEW_MSG:
                        pullRequest.setNextOffset(pullResult.getNextBeginOffset());

                        DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest);

                        DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
                        break;
                    case NO_MATCHED_MSG:
                        pullRequest.setNextOffset(pullResult.getNextBeginOffset());

                        DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest);

                        DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest);
                        break;
                    case OFFSET_ILLEGAL:
                        log.warn("the pull request offset illegal, {} {}",//
                            pullRequest.toString(), pullResult.toString());

                        pullRequest.setNextOffset(pullResult.getNextBeginOffset());

                        pullRequest.getProcessQueue().setDropped(true);
                        DefaultMQPushConsumerImpl.this.executeTaskLater(new Runnable() {

                            @Override
                            public void run() {
                                try {
                                    DefaultMQPushConsumerImpl.this.offsetStore.updateOffset(
                                        pullRequest.getMessageQueue(), pullRequest.getNextOffset(), false);

                                    DefaultMQPushConsumerImpl.this.offsetStore.persist(pullRequest
                                        .getMessageQueue());

                                    DefaultMQPushConsumerImpl.this.rebalanceImpl
                                        .removeProcessQueue(pullRequest.getMessageQueue());

                                    log.warn("fix the pull request offset, {}", pullRequest);
                                }
                                catch (Throwable e) {
                                    log.error("executeTaskLater Exception", e);
                                }
                            }
                        }, 10000);
                        break;
                    default:
                        break;
                    }
                }
            }


            @Override
            public void onException(Throwable e) {
                if (!pullRequest.getMessageQueue().getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    log.warn("execute the pull request exception", e);
                }

                DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest,
                    PullTimeDelayMillsWhenException);
            }
        };

        boolean commitOffsetEnable = false;
        long commitOffsetValue = 0L;
        if (MessageModel.CLUSTERING == this.defaultMQPushConsumer.getMessageModel()) {
            commitOffsetValue =
                    this.offsetStore.readOffset(pullRequest.getMessageQueue(),
                        ReadOffsetType.READ_FROM_MEMORY);
            if (commitOffsetValue > 0) {
                commitOffsetEnable = true;
            }
        }

        String subExpression = null;
        boolean classFilter = false;
        SubscriptionData sd =
                this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());
        if (sd != null) {
            if (this.defaultMQPushConsumer.isPostSubscriptionWhenPull() && !sd.isClassFilterMode()) {
                subExpression = sd.getSubString();
            }

            classFilter = sd.isClassFilterMode();
        }

        int sysFlag = PullSysFlag.buildSysFlag(//
            commitOffsetEnable, // commitOffset
            true, // suspend
            subExpression != null,// subscription
            classFilter // class filter
            );
        try {
            this.pullAPIWrapper.pullKernelImpl(//
                pullRequest.getMessageQueue(), // 1
                subExpression, // 2
                subscriptionData.getSubVersion(), // 3
                pullRequest.getNextOffset(), // 4
                this.defaultMQPushConsumer.getPullBatchSize(), // 5
                sysFlag, // 6
                commitOffsetValue,// 7
                BrokerSuspendMaxTimeMillis, // 8
                ConsumerTimeoutMillisWhenSuspend, // 9
                CommunicationMode.ASYNC, // 10
                pullCallback// 11
                );
        }
        catch (Exception e) {
            log.error("pullKernelImpl exception", e);
            this.executePullRequestLater(pullRequest, PullTimeDelayMillsWhenException);
        }
    }

在上一步,拉取到消息后,调用了ConsumeMessageService.submitConsumeRequest()方法
1、当消费者设置的为单个消费时,该方法会将消息拆为一个一个,放到consumeExecutor线程池中,等待消息消费
2、当消费者设置为批量消费时,则按批量消费的数量,将消息拆分,放到consumeExecutor线程池中,等待消息消费
代码如下:

@Override
    public void submitConsumeRequest(//
            final List<MessageExt> msgs, //
            final ProcessQueue processQueue, //
            final MessageQueue messageQueue, //
            final boolean dispatchToConsume) {
        final int consumeBatchSize = this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize();
        if (msgs.size() <= consumeBatchSize) {
            ConsumeRequest consumeRequest = new ConsumeRequest(msgs, processQueue, messageQueue);
            this.consumeExecutor.submit(consumeRequest);
        }
        else {
            for (int total = 0; total < msgs.size();) {
                List<MessageExt> msgThis = new ArrayList<MessageExt>(consumeBatchSize);
                for (int i = 0; i < consumeBatchSize; i++, total++) {
                    if (total < msgs.size()) {
                        msgThis.add(msgs.get(total));
                    }
                    else {
                        break;
                    }
                }

                ConsumeRequest consumeRequest = new ConsumeRequest(msgThis, processQueue, messageQueue);
                this.consumeExecutor.submit(consumeRequest);
            }
        }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现在窗口中显示按键信息 #include <windows.h> #include <stdio.h> //全局变量 RECT rc; //记录滚屏的矩形区域 int xChar, yChar; //文本输入点坐标 WNDCLASSEX wnd; //窗口类结构变量 char szAppName[] = "键盘消息监视程序"; //窗口类名 //函数声名 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); BOOL MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE hInstance,int iCmdShow); //函数:WinMain //作用:入口函数 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR szCmdLine, int iCmdShow) { MSG msg; if(!MyRegisterClass(hInstance)) { return FALSE; } if(!InitInstance(hInstance,iCmdShow)) { return FALSE; } while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } return msg.wParam; } //函数:ShowKey //作用:实现在窗口中显示按键信息 void ShowKey (HWND hwnd, int iType, char *szMessage,WPARAM wParam, LPARAM lParam) { static char *szFormat[2] = { "%-14s %3d %c %6u %4d %5s %5s %6s %6s", "%-14s %3d %c %6u %4d %5s %5s %6s %6s" } ; char szBuffer[80]; HDC hdc; ScrollWindowEx(hwnd, 0, -yChar, &rc, &rc,NULL,NULL,SW_INVALIDATE); hdc = GetDC (hwnd); SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)); TextOut (hdc, xChar, rc.bottom - yChar, szBuffer, wsprintf (szBuffer, szFormat [iType], szMessage, //消息 wParam,//虚拟键代码 (BYTE) (iType ? wParam : ),//显示字符值 LOWORD (lParam),//重复次数 HIWORD (lParam) & 0xFF,//OEM键盘扫描码 //判断是否为增强键盘的扩展键 (PSTR) (0x01000000 & lParam ? "是" : "否"), //判断是否同时使用了ALT键 (PSTR) (0x20000000 & lParam ? "是" : "否"), (PSTR) (0x40000000 & lParam ? "按下" : "抬起"),//判断前一次击键状态 (PSTR) (0x80000000 & lParam ? "按下" : "抬起"))//判断转换状态 ); ReleaseDC (hwnd, hdc); ValidateRect (hwnd, NULL); } //函数:WndProc //作用:处理主窗口的消息 LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { static char szTop[] = "消息 键 字符 重复数 扫描码 扩展码 ALT 前一状态 转换状态"; static char szUnd[] = "_______ __ ____ _____ ______ ______ ___ _______ ______"; //在窗口中输出文字作为信息标题 HDC hdc; PAINTSTRUCT ps; TEXTMETRIC tm; switch (iMsg) { case WM_CREATE://处理窗口创建的消息 hdc = GetDC (hwnd); //设定字体 SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)); //检当前字体的度量数据 GetTextMetrics (hdc, &tm); xChar = tm.tmAveCharWidth;//保存字体平均宽度 yChar = tm.tmHeight;//保存字体高度 ReleaseDC (hwnd, hdc); rc.top = 3 * yChar / 2; return 0; case WM_SIZE://处理窗口大小改变的消息 //窗体改变后保存新的滚屏区域右下角坐标 rc.right = LOWORD (lParam); rc.bottom = HIWORD (lParam); UpdateWindow (hwnd); return 0; case WM_PAINT://处理窗口重绘消息 InvalidateRect (hwnd, NULL, TRUE); hdc = BeginPaint (hwnd, &ps); SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; SetBkMode (hdc, TRANSPARENT) ; TextOut (hdc, xChar, yChar / 2, szTop, (sizeof szTop) - 1) ; TextOut (hdc, xChar, yChar / 2, szUnd, (sizeof szUnd) - 1) ; EndPaint (hwnd, &ps); return 0; case WM_KEYDOWN://处理键盘上某一键按下的消息 ShowKey (hwnd, 0, "WM_KEYDOWN", wParam, lParam); return 0; case WM_KEYUP://处理键盘上某一按下键被释放的消息 ShowKey (hwnd, 0, "WM_KEYUP", wParam, lParam); return 0; case WM_CHAR://处理击键过程中产生的非系统键的可见字符消息 ShowKey (hwnd, 1, "WM_CHAR", wParam, lParam); return 0; case WM_DEADCHAR://处理击键过程中产生的非系统键"死字符"消息 ShowKey (hwnd, 1, "WM_DEADCHAR", wParam, lParam); return 0; case WM_SYSKEYDOWN://处理系统键按下的消息 ShowKey (hwnd, 0, "WM_SYSKEYDOWN", wParam, lParam); break; case WM_SYSKEYUP://处理系统键抬起的消息 ShowKey (hwnd, 0, "WM_SYSKEYUP", wParam, lParam); break; case WM_SYSCHAR://处理系统键可见字符消息 ShowKey (hwnd, 1, "WM_SYSCHAR", wParam, lParam); break; case WM_SYSDEADCHAR://处理系统键"死字符"消息 ShowKey (hwnd, 1, "WM_SYSDEADCHAR", wParam, lParam); break; case WM_DESTROY://处理结束应用程序的消息 PostQuitMessage (0); return 0; } return DefWindowProc (hwnd, iMsg, wParam, lParam); } //函数:MyRegisterClass //作用:注册窗口类 BOOL MyRegisterClass(HINSTANCE hInstance) { wnd.cbSize= sizeof (wnd); wnd.style = CS_HREDRAW | CS_VREDRAW; wnd.lpfnWndProc = WndProc; wnd.cbClsExtra = 0; wnd.cbWndExtra = 0; wnd.hInstance = hInstance; wnd.hIcon = LoadIcon (NULL, IDI_APPLICATION); wnd.hCursor = LoadCursor (NULL, IDC_ARROW); wnd.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); wnd.lpszMenuName = NULL; wnd.lpszClassName = szAppName; wnd.hIconSm = LoadIcon (NULL, IDI_APPLICATION); return RegisterClassEx (&wnd); } //函数:InitInstance //作用:创建主窗口 BOOL InitInstance(HINSTANCE hInstance,int iCmdShow) { HWND hwnd; hwnd = CreateWindow (szAppName, "键盘消息监视程序", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, NULL,NULL,hInstance,NULL ); if(!hwnd) { return FALSE; } ShowWindow (hwnd, iCmdShow); UpdateWindow (hwnd); return TRUE; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值