业务场景需求
标题:Java - C++之MFC通讯
1 业务场景描述
本人岗位背景:Java开发菜狗一条;
业务描述:需要对接各大厂家的设备;
对接信息:对方只提供了x86的库文件给我;
我他喵的,我哪会啊.........被迫学习。
2 解决方案
目前有如下但不局限于这些的解决方法
- 方案一:通过 Windows rundll32.exe执行x86库文件;
- 方案二:使用Python做接口暴露;
- 方案三:使用C++做接口暴露;
本文采取了方案三。
3 技术点
主要分为三个部分
- C++ / Java的异步多线程
- C++ MFC框架的使用
- C++ / Java的TCP通讯
我们根据上述的思路开始下面的打码。
一、C++代码
提示:这里可以添加本文要记录的大概内容
1 编译环境的部署
首先必须将所需要的模块进行安装,如MFC等。
这一块就不大想说太多了,如图所示。
2 完整代码片段
先上代码再说
// MatrixSDKTestDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "MatrixSDKTest.h"
#include "MatrixSDKTestDlg.h"
#include "afxdialogex.h"
#include <vector>
#include <sstream>
//获取地址信息
#include <WINSOCK2.H>
#include <WS2TCPIP.H>
#pragma comment (lib, "WS2_32.lib")
// 关于TCP的...
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include "winsock2.h"
#include<cstdlib>
#include <thread> // 这个用于多线程开辟
using namespace std;
char recvBuf[100];
bool connectStatus = false;
SOCKET sockConn;
// ...over
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// ...此处省略无关文章的业务
// ...
///////////////////////////////// 功能枚举 /////////////////////////////////////////////
const CString code_100001 = "100001";// 初始化
const CString code_100002 = "100002";// 代表XXX功能
const CString code_100003 = "100003";// 代表XXX功能
const CString code_100004 = "100004";// 代表XXX功能
const CString code_100005 = "100005";// 代表XXX功能
///////////////////////////////// TCP程序 /////////////////////////////////////////////
// 切割方法
vector<string> split(string s, char token) {
stringstream iss(s);
string word;
vector<string> vs;
while (getline(iss, word, token)) {
vs.push_back(word);
}
return vs;
}
/**
* TCP
* 在一个新的线程里面接收数据
*/
DWORD WINAPI Fun(LPVOID lpParamter)
{
while (true) {
memset(recvBuf, 0, sizeof(recvBuf));
//接收数据
recv(sockConn, recvBuf, sizeof(recvBuf), 0);
printf("%s\n", recvBuf);
// 数据处理代码 - 根据过来的消息分配函数 - 处理的比较无脑简单
CString strCode = recvBuf;
// 初始化 字符串切割
string s1First = strCode;
vector<string> sData = split(s1First, ',');
if (strCode.Find(code_100001) != -1) {
// 初始化
string ipAddr = sData[1];
char* ipAddr_char = (char*)ipAddr.data();
unsigned short wLocalPort = 5060;
// 执行初始化动作
bool bRt = InitDevice(ipAddr_char, wLocalPort);
if (bRt)
{
send(sockConn, "初始化成功", sizeof("初始化成功"), 0);
}
else
{
send(sockConn, "初始化失败", sizeof("初始化失败"), 0);
}
}
else if (strCode.Find(code_100002) != -1) {
// ...
}
else if(strCode.Find(code_100003) != -1){
// ...
}
else {
int iSendDDD3 = send(sockConn, "-1", 2, 0);
}
// 消息回复 Demo
//char sendbuf[] = "你好,我是服务端,我已经收到了你的消息";
// 消息发送 Demo --- 如果回去的数据长度不够 看看是否是第三个参数限制太短了
//int iSend = send(sockConn, sendbuf, sizeof(sendbuf), 0);
}
closesocket(sockConn);
}
int startUpTCP()
{
WSADATA wsaData;
int port = 12022;// 端口号
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("初始化失败");
return 0;
}
//创建用于监听的套接字,即服务端的套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(port); //1024以上的端口号
/**
* INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 一般来说,在各个系统中均定义成为0值。
*/
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
//int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
//if (retVal == SOCKET_ERROR) {
// printf("连接失败:%d\n", WSAGetLastError());
// return 0;
//}
if (listen(sockSrv, 10) == SOCKET_ERROR) {
printf("监听失败:%d", WSAGetLastError());
return 0;
}
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while (1)
{
//等待客户请求到来
sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
if (sockConn == SOCKET_ERROR) {
printf("等待请求失败:%d", WSAGetLastError());
break;
}
printf("客户端的IP是:[%s]\n", inet_ntoa(addrClient.sin_addr));
//发送数据
char sendbuf[] = "你好,我是服务端,咱们一起聊天吧";
int iSend = send(sockConn, sendbuf, sizeof(sendbuf), 0);
if (iSend == SOCKET_ERROR) {
printf("发送失败");
break;
}
HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
CloseHandle(hThread);
}
closesocket(sockSrv);
WSACleanup();
return 0;
}
// TCP up
void CMatrixSDKTestDlg::TcpUp()
{
// TODO: 在此添加控件通知处理程序代码
// 连接状态未开启的情况下
if (!connectStatus) {
connectStatus = true;
CString strshow;
strshow.Format("TCP ----- UP");
addinforshow(strshow);
// 启动中 要异步执行 --- join()会导致主线程阻塞
thread th1(startUpTCP);
th1.detach();
//th1.join();
}
else {
// 连接状态已经开启了
CString strshow;
strshow.Format("请勿重复启动TCP");
addinforshow(strshow);
}
}
// TCP down
void CMatrixSDKTestDlg::TcpDown()
{
// TODO: 在此添加控件通知处理程序代码
}
调试工具的效果如图
MFC界面的效果图
然后开始对代码情况进行简单说明。
3 第一步:为控件添加事件处理程序
打开界面查看版,进行添加事件处理程序
此时在弹出的选择框内可以选择类
我们可以通过属性按钮,修改控件的名称和一些相关属性
大概就是这样。
4 第二步:异步多线程开启Socket通信
注意,在线程启动的时候,如果选择thread1.join();,会导致主线程被阻塞。
/**
* TCP
* 在一个新的线程里面接收数据
*/
DWORD WINAPI Fun(LPVOID lpParamter)
{
while (true) {
memset(recvBuf, 0, sizeof(recvBuf));
//接收数据
recv(sockConn, recvBuf, sizeof(recvBuf), 0);
printf("%s\n", recvBuf);
// 消息回复 Demo
char sendbuf[] = "你好,我是服务端,我已经收到了你的消息";
// 消息发送 Demo --- 如果回去的数据长度不够 看看是否是第三个参数限制太短了
int iSend = send(sockConn, sendbuf, sizeof(sendbuf), 0);
}
closesocket(sockConn);
}
int startUpTCP()
{
WSADATA wsaData;
int port = 12022;// 端口号
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("初始化失败");
return 0;
}
//创建用于监听的套接字,即服务端的套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(port); //1024以上的端口号
/**
* INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”。 一般来说,在各个系统中均定义成为0值。
*/
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
//int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));
//if (retVal == SOCKET_ERROR) {
// printf("连接失败:%d\n", WSAGetLastError());
// return 0;
//}
if (listen(sockSrv, 10) == SOCKET_ERROR) {
printf("监听失败:%d", WSAGetLastError());
return 0;
}
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while (1)
{
//等待客户请求到来
sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
if (sockConn == SOCKET_ERROR) {
printf("等待请求失败:%d", WSAGetLastError());
break;
}
printf("客户端的IP是:[%s]\n", inet_ntoa(addrClient.sin_addr));
//发送数据
char sendbuf[] = "你好,我是服务端,咱们一起聊天吧";
int iSend = send(sockConn, sendbuf, sizeof(sendbuf), 0);
if (iSend == SOCKET_ERROR) {
printf("发送失败");
break;
}
HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
CloseHandle(hThread);
}
closesocket(sockSrv);
WSACleanup();
return 0;
}
// TCP up
void CMatrixSDKTestDlg::TcpUp()
{
// TODO: 在此添加控件通知处理程序代码
// 连接状态未开启的情况下
if (!connectStatus) {
connectStatus = true;
CString strshow;
strshow.Format("TCP ----- UP");
addinforshow(strshow);
// 启动中 要异步执行 --- join()会导致主线程阻塞
thread th1(startUpTCP);
th1.detach();
//th1.join();
}
else {
// 连接状态已经开启了
CString strshow;
strshow.Format("请勿重复启动TCP");
addinforshow(strshow);
}
}
5 第三步:消息接收、解析、返回
我这边思路是通过功能码区分消息类型,通过逗号将参数带入并分解。
///////////////////////////////// 功能枚举 /////////////////////////////////////////////
const CString code_100001 = "100001";// 初始化
const CString code_100002 = "100002";// 代表XXX功能
const CString code_100003 = "100003";// 代表XXX功能
const CString code_100004 = "100004";// 代表XXX功能
const CString code_100005 = "100005";// 代表XXX功能
///////////////////////////////// TCP程序 /////////////////////////////////////////////
// 切割方法
vector<string> split(string s, char token) {
stringstream iss(s);
string word;
vector<string> vs;
while (getline(iss, word, token)) {
vs.push_back(word);
}
return vs;
}
/**
* TCP
* 在一个新的线程里面接收数据
*/
DWORD WINAPI Fun(LPVOID lpParamter)
{
while (true) {
memset(recvBuf, 0, sizeof(recvBuf));
//接收数据
recv(sockConn, recvBuf, sizeof(recvBuf), 0);
printf("%s\n", recvBuf);
// 数据处理代码 - 根据过来的消息分配函数 - 处理的比较无脑简单
CString strCode = recvBuf;
// 初始化 字符串切割
string s1First = strCode;
vector<string> sData = split(s1First, ',');
if (strCode.Find(code_100001) != -1) {
// 初始化
string ipAddr = sData[1];
char* ipAddr_char = (char*)ipAddr.data();
unsigned short wLocalPort = 5060;
// 执行初始化动作
bool bRt = InitDevice(ipAddr_char, wLocalPort);
if (bRt)
{
send(sockConn, "初始化成功", sizeof("初始化成功"), 0);
}
else
{
send(sockConn, "初始化失败", sizeof("初始化失败"), 0);
}
}
else if (strCode.Find(code_100002) != -1) {
// ...
}
else if(strCode.Find(code_100003) != -1){
// ...
}
else {
int iSendDDD3 = send(sockConn, "-1", 2, 0);
}
// 消息回复 Demo
//char sendbuf[] = "你好,我是服务端,我已经收到了你的消息";
// 消息发送 Demo --- 如果回去的数据长度不够 看看是否是第三个参数限制太短了
//int iSend = send(sockConn, sendbuf, sizeof(sendbuf), 0);
}
closesocket(sockConn);
}
适配器代码已经完成,接下来编写我们的Java客户端请求代码。
二、Java代码
关于这块我不做太多描述,使用Netty框架进行TCP通讯。
1 基本通讯信息配置
public class ToaConfig {
/** 本地适配器地址及端口 */
public static final String IPADDR_LOCAL = "192.168.247.185";
public static final String IPADDR_PORT = "12022";
}
2 客户端初始化
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.common.tool.ThreadPoolUtils;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Consumer;
/**
* 客户端初始化
* @author lijiamin
*/
@Component
public class ClientInitialize {
private static Logger log = LoggerFactory.getLogger(ClientInitialize.class);
public static EventLoopGroup eventLoopGroup;
public static Bootstrap bootstrap;
public static CopyOnWriteArraySet<String> falseConnectSet = new CopyOnWriteArraySet();
/**
* Netty初始化配置
*/
static {
eventLoopGroup = new NioEventLoopGroup();
bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new StringDecoder());
socketChannel.pipeline().addLast(new StringEncoder());
socketChannel.pipeline().addLast(new ClientHandler());
}
});
// 异步持续监听连接失败的地址
CompletableFuture.runAsync(new Runnable() {
@Override
public void run() {
while (true) {
try {
if (falseConnectSet.size() != 0) {
// 循环集合内元素
falseConnectSet.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
String[] strings = s.split(":");
connectServer(strings[0],strings[1]);
}
});
}
Thread.sleep(10000);
} catch (Exception e) {
log.error("Netty初始化配置监听地址出现异常");
e.printStackTrace();
}
}
}
}, ThreadPoolUtils.getThreadPool());
}
/**
* 服务连接
* @param host
* @param portStr
*/
public static void connectServer(String host, String portStr) {
// 获取地址及端口
int port = Integer.valueOf(portStr);
// 异步连接tcp服务端
bootstrap.remoteAddress(host, port).connect().addListener((ChannelFuture futureListener) -> {
if (!futureListener.isSuccess()) {
log.error(host + ":" + port + "连接失败!!!!!!!! 当前时间:" + new Date());
futureListener.channel().close();
// 连接失败信息插入Set
falseConnectSet.add(host + ":" + port);
} else {
log.info(host + ":" + port + "连接成功,当前时间:" + new Date());
// 连接成功信息从Set拔除
falseConnectSet.remove(host + ":" + port);
}
});
}
/**
* 初始化方法
*/
@PostConstruct
public void initialize() {
connectServer(ToaConfig.IPADDR_LOCAL, ToaConfig.IPADDR_PORT);
}
}
3 处理器
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
/**
* 客户端处理器
* @author lijiamin
*/
public class ClientHandler extends ChannelInboundHandlerAdapter {
private static Logger log = LoggerFactory.getLogger(ClientHandler.class);
/** 与适配器连接的通道 */
public static Channel channel = null;
/**
* 连接建立
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
channel = ctx.channel();
super.channelActive(ctx);
}
/**
* 连接断开
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
int port = ipSocket.getPort();
String host = ipSocket.getHostString();
log.error("与设备" + host + ":" + port + "连接断开!");
// 连接断开后的最后处理
ctx.pipeline().remove(this);
ctx.deregister();
ctx.close();
// 将失败信息插入Set集合
ClientInitialize.falseConnectSet.add(host + ":" + port);
channel = null;
super.channelInactive(ctx);
}
/**
* 通道数据读取
* 业务操作
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
}
/**
* 通道数据处理完成
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
super.channelReadComplete(ctx);
}
/**
* 事件触发
* @param ctx
* @param evt
* @throws Exception
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
super.userEventTriggered(ctx, evt);
}
/**
* 异常触发
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
4 最后说明
在服务端与客户端数据通讯时,我们是通过处理器中的通道进行通讯,这块Netty的知识不再过多说明。
暂且告一段落。
总结
提示:这里对文章进行总结: 例如:以上就是今天要讲的内容。