C 语言实现 Modbus 协议并获取云端服务器参数

C 语言实现 Modbus 协议并获取云端服务器参数

实验目标

在消化学习 server.c 和 client.c 套接字代码、python-modbus-over-tcp.py 代码基础上,试着用 C 编程完成 modbus 协议,从云端服务器读取温湿度数据

实验原理

详细原理看我的另外两篇博客:

1.STM32 —— Modbus 协议

2.C 语言实现 Windows 下 Socket 编程

示例代码

首先看给出的示例代码,这个代码是用 Python 编写的,代码如下:

点击查看TCP完整代码
import socket
import sys
import struct
import time
import tcp
import threading
import _thread
# import pymysql

#本程序是应变传感器采集,可以通过发送ALL指令进行全部传感器的采集


def crc16(string):
    #data = bytes.fromhex(string)
    data=string
    crc = 0xFFFF
    for pos in data:
        crc ^= pos
        for i in range(8):
            if ((crc & 1) != 0):
                crc >>= 1
                crc ^= 0xA001
            else:
                crc >>= 1
    return hex(((crc & 0xff) << 8) + (crc >> 8))
#连接数据
def MySQLConnect():
    connection = pymysql.connect(
        host='localhost',  # IP,MySQL数据库服务器IP地址
        port=3306,  # 端口,默认3306,可以不输入
        user='root',  # 数据库用户名
        password='123456',  # 数据库登录密码
        database='sensor',  # 要连接的数据库
        charset='utf8'  # 字符集,注意不是'utf-8'
    )
    return connection

#插入数据到数据库
def AddData(num,yb,wd,time):
    # 连接数据库
    conn = MySQLConnect()
    # 使用cursor()方法创建一个游标对象cursor
    cursor = conn.cursor()
    # 插入数据库
    sql = "INSERT INTO strain_sensor(id ,mic, strain_temp, time) VALUES (%s,%s,%s,%s); "
    cursor.execute(sql, [num,yb, wd, time])
    # 提交事务
    conn.commit()
    # 关闭游标
    cursor.close()
    # 关闭数据库连接
    conn.close()

#获取一次数据
def getStain(cmd,num,time):
    #print(cmd)
    #print(num)
    cmd = bytes.fromhex(cmd)
    crc = crc16(cmd)
    crc = bytes.fromhex(crc[2:])
    cmd = cmd + crc
    #print(cmd)
    #发送对应的指令
    tcp.send(cmd)
    try:
        data = tcp.recv(8192)
    except socket.timeout:
        print("超时")
        sys.exit(1)
    crc = data[-2:]
    crc1 = crc16(data[:-2])
    crc1 = crc1[2:]
    if len(crc1) == 3:
        crc1 = '0' + crc1
    crc1 = bytes.fromhex(crc1)
    if crc != crc1:
        print("CRC16校验失败!")
        sys.exit(2)
    yb, wd = struct.unpack('>ii', data[4:12])
    yb = yb / 100.0
    wd = wd / 100.0
    print("应变:", yb, "温度:", wd)
    # print(time)
    yb = str(yb)
    wd = str(wd)
    # AddData(num, yb, wd, time)
    print(num,"\n",yb,"\n",wd,"\n")

def setCircleData(cmd,num):
    count=0
    flag=0
    last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    last1 = time.time()
    while True:
        now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        now1 = time.time()
        # print(now)
        if (flag == 0):
            count=count+1
            flag = 1
            getStain(cmd, num, last)
            last = now
            last1 = now1
        if now1 - last1 > 5:
            if count>=5:
                str = input("请选择是否继续采集(y表示继续,n表示退出):")
                if str == 'y':
                    count = 0
                    continue
                else:
                    break
            count = count + 1
            getStain(cmd, num, now)
            last = now
            last1 = now1

#同时采集全部应变传感器
def setCircleAll(cmd):
    flag=0
    count=0
    last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    last1 = time.time()
    while True:
        now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        now1 = time.time()
        #count=0
        # print(now)
        if (flag == 0):
            flag = 1
            count=count+1
            for cmd1 in cmd:

                id='00'+cmd1[0:2]
                #print(id)
                #print(cmd1)
                getStain(cmd1,id,last)
            last = now
            last1 = now1
        if now1 - last1 > 5:
            if count>=5:
                str=input("请选择是否继续采集(y表示继续,n表示退出):")
                if str=='y':
                    count=0
                    continue
                else:
                    break
            count = count + 1
            for cmd1 in cmd:
                id = '00' + cmd1[0:2]
                getStain(cmd1, id, now)
            last = now
            last1 = now1
if __name__ == '__main__':
    tcp = socket.socket(socket.AF_INET,
                        socket.SOCK_STREAM,
                        socket.IPPROTO_TCP)
    tcp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    tcp.settimeout(5)
    try:
        tcp.connect(('demo-monitor.igong.com', 8002))
        print("TCP连接成功")
    except:
        print("连接TCP失败")
        sys.exit(1)
    flag=0
    while True:
        print("具体指令给格式为000+传感器编号(1,2,3,4,5)")
        num = input("请输入采集传感器的编号(All表示采集全部传感器,0表示退出采集):")
        if num == '0001':
            cmd = '010300010002'
            setCircleData(cmd,num)

        elif num == '0002':
            cmd = '020300010002'
            setCircleData(cmd, num)

        elif num == '0003':
            cmd = '030300010002'
            setCircleData(cmd, num)
        elif num == '0004':
            cmd = '040300010002'
            setCircleData(cmd, num)
        elif num == '0005':
            cmd = '050300010002'
            setCircleData(cmd, num)
        elif num == 'All':
            cmd={'010300010002','020300010002','030300010002','040300010002','050300010002'}
            setCircleAll(cmd)
        elif num == '0':
            break
        else:
            print("输入信息不合法,请重新输入")
点击查看UDP完整代码
import socket
import sys
import struct
import time
# import pymysql
import datetime

#实现采集温度传感器和静力水准仪

def crc16(string):
    #data = bytes.fromhex(string)
    data=string
    crc = 0xFFFF
    for pos in data:
        crc ^= pos
        for i in range(8):
            if ((crc & 1) != 0):
                crc >>= 1
                crc ^= 0xA001
            else:
                crc >>= 1
    return hex(((crc & 0xff) << 8) + (crc >> 8))

#连接数据库
def MySQLConnect():
    connection = pymysql.connect(
        host='localhost',  # IP,MySQL数据库服务器IP地址
        port=3306,  # 端口,默认3306,可以不输入
        user='root',  # 数据库用户名
        password='123456',  # 数据库登录密码
        database='sensor',  # 要连接的数据库
        charset='utf8'  # 字符集,注意不是'utf-8'
    )
    return connection

#插入温湿度采集到数据库
def AddData1(wd,sd,time):
    # 连接数据库
    conn = MySQLConnect()
    # 使用cursor()方法创建一个游标对象cursor
    cursor = conn.cursor()
    # 插入数据库
    sql = "INSERT INTO temp_hum_sensor(temp, hum, time) VALUES (%s,%s,%s); "
    cursor.execute(sql, [wd, sd, time])
    # 提交事务
    conn.commit()
    # 关闭游标
    cursor.close()
    # 关闭数据库连接
    conn.close()

#插入静力水准仪采集到数据库
def AddData2(id,water_level,time):
    # 连接数据库
    conn = MySQLConnect()
    # 使用cursor()方法创建一个游标对象cursor
    cursor = conn.cursor()
    # 插入数据库
    sql = "INSERT INTO static_level(id, water_level, time) VALUES (%s,%s,%s); "
    cursor.execute(sql, [id, water_level, time])
    # 提交事务
    conn.commit()
    # 关闭游标
    cursor.close()
    # 关闭数据库连接
    conn.close()

#采集温度传感器的数据
def getDataTemp(cmd):
    #flag标志采集的次数
    flag=0
    last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    print(last)
    last1 = time.time()
    cmd = bytes.fromhex(cmd)
    #print(cmd)
    crc = crc16(cmd)
    crc = bytes.fromhex(crc[2:])
    #得到发送的指令(modbus协议定义内容+校验)
    cmd = cmd + crc
    udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
    try:
        data, addr = udp.recvfrom(8192)
    except socket.timeout:
        print("超时")
        sys.exit(1)
    crc = data[-2:]
    crc1 = crc16(data[:-2])
    crc1 = crc1[2:]
    if (len(crc1) == 3):
        crc1 = '0' + crc1
    crc1 = bytes.fromhex(crc1)

    # print(crc1)
    if crc != crc1:
        print("CRC16校验失败!")
        sys.exit(2)
    # 解析数据
    wd, sd = struct.unpack('>ii', data[4:12])
    wd = wd / 100.
    print("温度:", wd, "湿度:", sd)
    # AddData1(wd, sd, last)
    flag=flag+1
    while True:
        now= time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        #print(s)
        now1=time.time()
        #每隔5s获取一次数据
        if(now1-last1>5):
            udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
            try:
                data, addr = udp.recvfrom(8192)
            except socket.timeout:
                print("超时")
                sys.exit(1)
            crc = data[-2:]
            crc2=bytes.hex(crc)
            #print(crc2)
            crc1 = crc16(data[:-2])
            crc1=crc1[2:]
            if(len(crc1)==3):
                crc1='0'+crc1
            #print(crc1)
            crc1=bytes.fromhex(crc1)
            #print(crc1)
            if crc != crc1:
                print("CRC16校验失败!")
                sys.exit(2)
            #解析数据
            wd, sd = struct.unpack('>ii', data[4:12])
            wd = wd / 100.0
            #当前时间
            print(now)
            #获取得到的数据
            print("温度:", wd, "湿度:", sd)
            last=now
            last1=now1
            wd=str(wd)
            sd=str(sd)
            # AddData1(wd,sd,now)
            flag = flag + 1
            if flag >= 5:
                str1 = input("请选择是否继续采集(y表示继续,n表示退出):")
                if str1 == 'y':
                    flag = 0
                    continue
                else:
                    break



def getDataStaticLevel(cmd):
    id=cmd[0:2]
    #print(id)
    if id=='02':
        #print("2号")
        id='00'+id
        getData(id,cmd)
    elif id=='03':
        #print("3号")
        id = '00' + id
        getData(id,cmd)
    elif id=='04':
        #print("4号")
        id = '00' + id
        getData(id,cmd)
    elif id=='05':
        #print("5号")
        id = '00' + id
        getData(id,cmd)

def getData(id,cmd):
    flag=0
    last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    print(last)
    last1 = time.time()
    cmd = bytes.fromhex(cmd)
    crc = crc16(cmd)
    crc = crc[2:]
    if (len(crc) == 3):
        crc = '0' + crc
    crc = bytes.fromhex(crc)
    cmd = cmd + crc
    udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
    print("发送数据成功")
    try:
        data, address = udp.recvfrom(8192)
        #print(data)
    except socket.timeout:
        print("超时")
        sys.exit(1)
    #print(len(data))
    crc = data[-2:]
    #print(data[:-2])
    crc1 = crc16(data[:-2])
    #print(crc1)
    crc1=crc1[2:]
    if len(crc1) == 3:
        crc1 = '0' + crc1
    #print(crc1)
    crc1 = bytes.fromhex(crc1)
    if crc != crc1:
        print("CRC16校验失败!")
        sys.exit(2)
    #print(data[4:8])
    nd = struct.unpack('>i', data[4:8])
    #print(nd)
    nd1 = nd[0]*10.0
    nd1=str(nd1)
    #print(last)
    print("挠度:"+nd1)
    AddData2(id,nd1,last)
    flag=flag+1

    while True:

        now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        # print(s)
        now1 = time.time()
        # 每隔5s获取一次数据
        if (now1 - last1 > 5):
            udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
            try:
                data, addr = udp.recvfrom(8192)
            except socket.timeout:
                print("超时")
                sys.exit(1)
            crc = data[-2:]
            crc2 = bytes.hex(crc)
            # print(crc2)
            crc1 = crc16(data[:-2])
            crc1 = crc1[2:]
            if (len(crc1) == 3):
                crc1 = '0' + crc1
            # print(crc1)
            crc1 = bytes.fromhex(crc1)
            # print(crc1)
            if crc != crc1:
                print("CRC16校验失败!")
                sys.exit(2)
            # 解析数据
            nd = struct.unpack('>i', data[4:8])
            nd = nd[0] * 10.0
            nd1=str(nd)
            print(now)
            print("挠度:" + nd1)
            nd=str(nd)
            AddData2(id, nd1, now)
            last=now
            last1=now1
            flag = flag + 1
            if flag >= 5:
                str1 = input("请选择是否继续采集(y表示继续,n表示退出):")
                if str1 == 'y':
                    flag = 0
                    continue
                else:
                    break

if __name__ == '__main__':
    print("开始程序")
    print("程序相关说明:")
    print("本程序采用UDP协议,其中当输入指令为0就退出整个程序。")
    print("命令格式类似于地址(01,02,03,04,05)+03+传感器地址(0001)+传感器个数(0001,0002)")
    print("例如:010300010002(温度传感器),020300010001(静力水准仪1)")
    print("如果出现不合法指令就输出提示信息,并重新输入指令。")
    udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    udp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    udp.settimeout(5)

    while True:
        cmd = input("请输入相关命令:")
        #print(len(cmd))
        num = cmd[8:]
        #print(num)
        if num == '0001' and cmd[0:2] == '01':
            print("此处不实现只对温度采集!!!")
        elif num == '0001':
            print("一个传感器")
            getDataStaticLevel(cmd)
        elif num == '0002':
            print("两个传感器")
            getDataTemp(cmd)
        elif cmd == '0':
            break
        else:
            print("指令不合法!!!")

TCP 连接代码分析

这里我们需要用到 CRC 验证,生成 2 字节的校验位数据,代码如下:

def crc16(string):
    #data = bytes.fromhex(string)
    data=string
    crc = 0xFFFF
    for pos in data:
        crc ^= pos
        for i in range(8):
            if ((crc & 1) != 0):
                crc >>= 1
                crc ^= 0xA001
            else:
                crc >>= 1
    return hex(((crc & 0xff) << 8) + (crc >> 8))

我们需要知道如何获取相关是数据,完整代码流程如下:

#获取一次数据
def getStain(cmd,num,time):
    #print(cmd)
    #print(num)
    cmd = bytes.fromhex(cmd)
    crc = crc16(cmd)
    crc = bytes.fromhex(crc[2:])
    cmd = cmd + crc
    #print(cmd)
    #发送对应的指令
    tcp.send(cmd)
    try:
        data = tcp.recv(8192)
    except socket.timeout:
        print("超时")
        sys.exit(1)
    crc = data[-2:]
    crc1 = crc16(data[:-2])
    crc1 = crc1[2:]
    if len(crc1) == 3:
        crc1 = '0' + crc1
    crc1 = bytes.fromhex(crc1)
    if crc != crc1:
        print("CRC16校验失败!")
        sys.exit(2)
    yb, wd = struct.unpack('>ii', data[4:12])
    yb = yb / 100.0
    wd = wd / 100.0
    print("应变:", yb, "温度:", wd)
    # print(time)
    yb = str(yb)
    wd = str(wd)
    # AddData(num, yb, wd, time)
    print(num,"\n",yb,"\n",wd,"\n")

参照上述的数据接收格式我们知道 data[1] 为地址位,data[2] 为指令,data[3] 是数据区长度,data 的后面两位为 crc16 校验位

我首先对接收的数据进行 crc16 校验,校验通过后,表明数据有效,在通过 struct 中封装的函数 unpack 进行数据位解析拿到我们的应变以及温度,数据包的解析格式为 ‘>ii’ 即大端方式 8 位解析

UDP 连接代码分析

采集温度数据函数如下:

#采集温度传感器的数据
def getDataTemp(cmd):
    #flag标志采集的次数
    flag=0
    last = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    print(last)
    last1 = time.time()
    cmd = bytes.fromhex(cmd)
    #print(cmd)
    crc = crc16(cmd)
    crc = bytes.fromhex(crc[2:])
    #得到发送的指令(modbus协议定义内容+校验)
    cmd = cmd + crc
    udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
    try:
        data, addr = udp.recvfrom(8192)
    except socket.timeout:
        print("超时")
        sys.exit(1)
    crc = data[-2:]
    crc1 = crc16(data[:-2])
    crc1 = crc1[2:]
    if (len(crc1) == 3):
        crc1 = '0' + crc1
    crc1 = bytes.fromhex(crc1)

    # print(crc1)
    if crc != crc1:
        print("CRC16校验失败!")
        sys.exit(2)
    # 解析数据
    wd, sd = struct.unpack('>ii', data[4:12])
    wd = wd / 100.
    print("温度:", wd, "湿度:", sd)
    # AddData1(wd, sd, last)
    flag=flag+1
    while True:
        now= time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        #print(s)
        now1=time.time()
        #每隔5s获取一次数据
        if(now1-last1>5):
            udp.sendto(cmd, ('demo-monitor.igong.com', 8001))
            try:
                data, addr = udp.recvfrom(8192)
            except socket.timeout:
                print("超时")
                sys.exit(1)
            crc = data[-2:]
            crc2=bytes.hex(crc)
            #print(crc2)
            crc1 = crc16(data[:-2])
            crc1=crc1[2:]
            if(len(crc1)==3):
                crc1='0'+crc1
            #print(crc1)
            crc1=bytes.fromhex(crc1)
            #print(crc1)
            if crc != crc1:
                print("CRC16校验失败!")
                sys.exit(2)
            #解析数据
            wd, sd = struct.unpack('>ii', data[4:12])
            wd = wd / 100.0
            #当前时间
            print(now)
            #获取得到的数据
            print("温度:", wd, "湿度:", sd)
            last=now
            last1=now1
            wd=str(wd)
            sd=str(sd)
            # AddData1(wd,sd,now)
            flag = flag + 1
            if flag >= 5:
                str1 = input("请选择是否继续采集(y表示继续,n表示退出):")
                if str1 == 'y':
                    flag = 0
                    continue
                else:
                    break

这里我们只需要根据示例代码了解数据获取的流程即可,然后我们按照 Modbus 协议的指令格式,能够模拟出我们设计的合理数据即可输入到 cmd 获取目标传感器数据

可视化代码

点击查看可视化代码:main.py
import wx
import socket
import threading
import subprocess
import struct
import time
import wxpy

class CalcFrame(wxpy.MyFrame1):
    def __init__(self):
        wxpy.MyFrame1.__init__(self, None)
        # self.Maximize()   #直接运行窗口最大化
        self.sensors = {1: {'cgq': [{'cgqmc': '环境温湿度',      #udp
                                     'cmd': b'\x01\x03\x00\x01\x00\x02\x95\xcb',
                                     'inst': "struct.unpack('>i',data[4:8])[0]/100.0",
                                     'glz': "struct.unpack('>i',data[8:12])[0]"},
                                    {'cgqmc': '武隆方向挠度基准点',
                                     'cmd': b'\x02\x03\x00\x01\x00\x01\xd5\xf9',
                                     'inst': "struct.unpack('>i',data[4:8])[0]/10.0",
                                     'glz': None},
                                    {'cgqmc': '武隆方向1#引拱挠度',
                                     'cmd': b'\x03\x03\x00\x01\x00\x01\xd4\x28',
                                     'inst': "struct.unpack('>i',data[4:8])[0]/10.0",
                                     'glz': None},
                                    {'cgqmc': '武隆方向挠度',
                                     'cmd': b'\x04\x03\x00\x01\x00\x01\xd5\x9f',
                                     'inst': "struct.unpack('>i',data[4:8])[0]/10.0",
                                     'glz': None},
                                    {'cgqmc': '跨中挠度',
                                     'cmd': b'\x05\x03\x00\x01\x00\x01\xd4\x4e',
                                     'inst': "struct.unpack('>i',data[4:8])[0]/10.0",
                                     'glz': None},
                                    ]},
                        2: {'cgq': {1:{'cgqmc': '主拱跨中1#应变',  #tcp
                                     'cmd': b'\x01\x03\x00\x01\x00\x02\x95\xcb',
                                     'inst': "struct.unpack('>i',data[4:8])[0]/100.0",
                                     'glz': "struct.unpack('>i',data[8:12])[0]/100.0"},
                                    2:{'cgqmc': '主拱跨中2#应变',
                                     'cmd': b'\x02\x03\x00\x01\x00\x02\x95\xf8',
                                     'inst': "struct.unpack('>i',data[4:8])[0]/100.0",
                                     'glz': "struct.unpack('>i',data[8:12])[0]/100.0"},
                                    3:{'cgqmc': '主拱跨中3#应变',
                                     'cmd': b'\x03\x03\x00\x01\x00\x02\x94\x29',
                                     'inst': "struct.unpack('>i',data[4:8])[0]/100.0",
                                     'glz': "struct.unpack('>i',data[8:12])[0]/100.0"},
                                    4:{'cgqmc': '主拱跨中4#应变',
                                     'cmd': b'\x04\x03\x00\x01\x00\x02\x95\x9e',
                                     'inst': "struct.unpack('>i',data[4:8])[0]/100.0",
                                     'glz': "struct.unpack('>i',data[8:12])[0]/100.0"},
                                    5:{'cgqmc': '主拱跨中5#应变',
                                     'cmd': b'\x05\x03\x00\x01\x00\x02\x94\x4f',
                                     'inst': "struct.unpack('>i',data[4:8])[0]/100.0",
                                     'glz': "struct.unpack('>i',data[8:12])[0]/100.0"},
                                    }},
                        }
        self.start = False
        self.t_udp = None
        self.t_tcp = None

    def u_client(self,address,port):
        udp = socket.socket(socket.AF_INET,
                            socket.SOCK_DGRAM,
                            socket.IPPROTO_UDP)
        udp.setsockopt(socket.SOL_SOCKET,
                       socket.SO_REUSEADDR,
                       1)
        udp.settimeout(2)
        print("udp")
        try:
            self.input_all.write("  udp连接!\n"+"  地址:   "+str(address)+"\n"+"  端口号:"+str(port)+"\n")
            self.u_search(udp,address,port) #实现查询函数
            print("连接成功!")
        except socket.timeout:
            print("超时,连接失败!")
            return False
        except:
            print("端口号或地址错误,连接失败!")
            return False
        try:
            print("进行查询操作")
        finally:
            print('u_close')
            udp.close()

    def t_client(self,address,port):
        tcp = socket.socket(socket.AF_INET,
                            socket.SOCK_STREAM,
                            socket.IPPROTO_TCP)
        tcp.setsockopt(socket.SOL_SOCKET,
                       socket.SO_REUSEADDR,
                       1)
        tcp.settimeout(30)
        print("tcp")
        try:  # 已经连接到远程端口
            print(address, type(address))
            print(port, type(port))
            cmd = 'netstat -ano | findstr %s' % port
            print(cmd)
            tcp.connect((address, port))  # 连接目标地址和端口的套接字
            print("成功")
            self.input_all.write("  tcp连接!\n"+"  地址:   "+str(address)+"\n"+"  端口号:"+str(port)+"\n")
            result1 = subprocess.getoutput(cmd)
            print(result1)
            # self.input_all.AppendText(result1+"\n  ")
            self.t_search(tcp) #实现查询函数
            print("链接成功!")
        except socket.timeout:
            print("超时,连接失败!")
            return
        except BaseException:
            print("端口号或地址错误,连接失败!")
            return
        finally:
            print('t_close')
            tcp.close()

    def t_search(self,tcp):
        choose_s = self.kind_choice.Selection
        num = self.num_chose.Selection
        try:
            cgq=self.sensors[2]['cgq']
            if choose_s==0:
                print('温度传感器')
                self.input_all.write("  tcp通道无温度传感器!\n")
                self.start=False
            elif choose_s==1:
                print('静力水准仪')
                self.input_all.write("  tcp通道无静力水准仪!\n")
                self.start=False
            elif choose_s==2:
                print('应变计')
                if self.start:
                    # print('cgq0:',cgq[0])
                    cgq=cgq[num+1]
                else:
                    self.input_all.write("  连接失败!\n")
            if self.start:
                print(cgq)
                cgqmc = cgq['cgqmc']
                cmd = cgq['cmd']
                inst_gs = cgq['inst']
                glz_gs = cgq['glz']
                tcp.sendall(cmd)
                try:
                    data = tcp.recv(8192)
                except socket.timeout:
                    self.input_all.AppendText('采集数据超时')
                    wx.Yield()
                crc = data[-2:]
                if crc != crc16(data[:-2]):
                    self.input_all.AppendText('  采集 %s 数据时,CRC16校验失败!\n' % cgqmc)
                    wx.Yield()
                try:
                    inst = eval(inst_gs)
                    if glz_gs is not None:
                        glz = eval(glz_gs)
                    else:
                        glz = None
                except BaseException:
                    self.input_all.AppendText('  采集 %s 数据时,解析数据失败!\n' % cgqmc)
                    wx.Yield()
                self.input_all.AppendText('  %s:  inst=%s  glz=%s\n' % (cgqmc, inst, glz))
                wx.Yield()
                t = time.time()
                while self.start and time.time() - t <= 2:
                    time.sleep(0.1)
        finally:
            print('T__CLO')
            self.input_all.write("  查询结束!\n")
            tcp.close()

    def u_search(self,udp,address,port):
        choose_s = self.kind_choice.Selection
        num = self.num_chose.Selection
        try:
            cgq = self.sensors[1]['cgq']
            if choose_s==0:
                print('温度传感器')
                if num==0 and self.start:
                    cgq=cgq[1]
                else:
                    self.input_all.write("  udp通道无此编号温度传感器!\n")
                    self.start=False
            elif choose_s==1:
                print('静力水准仪')
                if num!=0 and self.start:
                    cgq = cgq[num+1]
                else:
                    self.input_all.write("  udp通道无此编号静力水准仪!\n")
                    self.start=False
            elif choose_s==2:
                print('应变计')
                self.input_all.write("  tcp通道无应变计!\n")
                self.start=False
            if self.start:
                print(cgq)
                server=(address,port)
                print(server)
                cgqmc = cgq['cgqmc']
                cmd = cgq['cmd']
                inst_gs = cgq['inst']
                glz_gs = cgq['glz']
                udp.sendto(cmd, server)
                try:
                    data, _ = udp.recvfrom(8192)
                except socket.timeout:
                    self.input_all.AppendText('  采集 %s 数据超时!\n' % cgqmc)
                    wx.Yield()
                crc = data[-2:]
                if crc != crc16(data[:-2]):
                    self.input_all.AppendText('  采集 %s 数据时,CRC16校验失败!\n' % cgqmc)
                    wx.Yield()
                try:
                    inst = eval(inst_gs)
                    if glz_gs is not None:
                        glz = eval(glz_gs)
                    else:
                        glz = None
                except BaseException:
                    self.input_all.AppendText('  采集 %s 数据时,解析数据失败!\n' % cgqmc)
                    wx.Yield()
                self.input_all.AppendText('  %s:  inst=%s  glz=%s\n' %
                    (cgqmc, inst, glz))
                wx.Yield()
                t = time.time()
                while self.start and time.time() - t < 2:
                    time.sleep(0.1)
        finally:
            self.input_all.write("  查询结束!\n")
            udp.close()

    def do_client_sys(self, event):
        choose = self.t_d_chose.Selection
        self.start = True
        address = self.address_input.GetValue()  # 实现输入判断端口与地址
        port = self.port_input.GetValue()
        if (port=="" or address=="") or (port=="" and address==""):
            self.input_all.write("  地址与端口号均不能为空!\n")
        else:
            port = int(port)
            # address = 'demo-monitor.igong.com'
            print("点击连接按钮")
            if choose == 0:
                # port = 8002
                self.t_tcp = threading.Thread(target=self.t_client,args=(address,port))
                self.t_tcp.setDaemon(True)
                self.t_tcp.start()
            elif choose == 1:
                # port=8001
                self.t_udp = threading.Thread(target=self.u_client,args=(address,port))
                self.t_udp.setDaemon(True)
                self.t_udp.start()
        event.Skip()

    def clear_input( self, event ):
        self.input_all.Clear()
        event.Skip()

    def exit_sys(self, event):
        choose=self.t_d_chose.Selection
        if choose==1:
            while self.t_udp.is_alive():
                time.sleep(0.1)
        # self.client_button.Enable(True)
        # self.exit_button.Enable(False)
        print('退出成功!')
        self.start = False
        self.Close()
        event.Skip()

def hex_bytes(x):
    if not isinstance(x, str):
        x = str(x, 'ascii')
    return bytes.fromhex(x)


def crc16(x):
    u'''
    @summary: 计算CRC16值
    @param x: bytes
    @return: 返回2字节值,类似:b'\x7B\x2A'。
    '''
    if not isinstance(x, bytes):
        raise ValueError('Parameter must be a bytes type')
    b = 0xA001
    a = 0xFFFF
    for byte in x:
        a = a ^ byte
        for _ in range(8):
            last = a % 2
            a = a >> 1
            if last == 1:
                a = a ^ b
    aa = '0' * (6 - len(hex(a))) + hex(a)[2:]
    ll, hh = int(aa[:2], 16), int(aa[2:], 16)
    rtn = '%x' % (hh * 256 + ll & 0xffff)
    while len(rtn) < 4:
        rtn = '0' + rtn
    rtn = hex_bytes(rtn)
    return rtn

class MyApp(wx.App):
    def OnInit(self):
        main = CalcFrame()
        main.Show()
        return True

    def OnExit(self):
        return True

if __name__ == '__main__':
    # app = wx.App(False)
    # frame = CalcFrame(None)
    # frame.Show(True)
    # app.MainLoop()
    app = MyApp()
    app.MainLoop()

点击查看可视化代码:wxpy.py
# -*- coding: utf-8 -*-

###########################################################################
## Python code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
## http://www.wxformbuilder.org/
##
## PLEASE DO *NOT* EDIT THIS FILE!
###########################################################################

import wx
import wx.xrc

###########################################################################
## Class MyFrame1
###########################################################################

class MyFrame1 ( wx.Frame ):

	def __init__( self, parent ):
		wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"采集数据", pos = wx.DefaultPosition, size = wx.Size( 790,562 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )

		self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
		self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_APPWORKSPACE ) )

		bSizer1 = wx.BoxSizer( wx.VERTICAL )

		bSizer2 = wx.BoxSizer( wx.HORIZONTAL )

		self.address = wx.StaticText( self, wx.ID_ANY, u"地址:", wx.Point( -1,-1 ), wx.DefaultSize, 0 )
		self.address.Wrap( -1 )

		self.address.SetFont( wx.Font( 15, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString ) )
		self.address.SetMinSize( wx.Size( 70,20 ) )

		bSizer2.Add( self.address, 0, wx.ALL, 5 )

		self.address_input = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
		self.address_input.SetMinSize( wx.Size( 150,28 ) )

		bSizer2.Add( self.address_input, 0, wx.ALL, 5 )

		self.port = wx.StaticText( self, wx.ID_ANY, u"端口:", wx.DefaultPosition, wx.DefaultSize, 0 )
		self.port.Wrap( -1 )

		self.port.SetFont( wx.Font( 15, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString ) )
		self.port.SetMinSize( wx.Size( 70,20 ) )

		bSizer2.Add( self.port, 0, wx.ALL, 5 )

		self.port_input = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
		self.port_input.SetMinSize( wx.Size( 150,28 ) )

		bSizer2.Add( self.port_input, 0, wx.ALL, 5 )

		self.tcp = wx.StaticText( self, wx.ID_ANY, u"tcp", wx.DefaultPosition, wx.DefaultSize, 0 )
		self.tcp.Wrap( -1 )

		self.tcp.SetFont( wx.Font( 15, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString ) )
		self.tcp.SetMinSize( wx.Size( 70,20 ) )

		bSizer2.Add( self.tcp, 0, wx.ALL, 5 )

		t_d_choseChoices = [ u"TCP", u"UDP" ]
		self.t_d_chose = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, t_d_choseChoices, 0 )
		self.t_d_chose.SetSelection( 0 )
		self.t_d_chose.SetFont( wx.Font( 15, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString ) )
		self.t_d_chose.SetMinSize( wx.Size( 100,30 ) )

		bSizer2.Add( self.t_d_chose, 0, wx.ALL, 5 )

		self.client_button = wx.Button( self, wx.ID_ANY, u"连接", wx.DefaultPosition, wx.DefaultSize, 0 )
		self.client_button.SetFont( wx.Font( 15, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString ) )

		bSizer2.Add( self.client_button, 0, wx.ALL, 5 )


		bSizer1.Add( bSizer2, 0, wx.ALIGN_CENTER_HORIZONTAL, 5 )

		bSizer4 = wx.BoxSizer( wx.HORIZONTAL )

		kind_choiceChoices = [ u"温度传感器", u"静力水准仪", u"应变计" ]
		self.kind_choice = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, kind_choiceChoices, 0 )
		self.kind_choice.SetSelection( 0 )
		self.kind_choice.SetFont( wx.Font( 15, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString ) )
		self.kind_choice.SetMinSize( wx.Size( 140,30 ) )

		bSizer4.Add( self.kind_choice, 0, wx.ALL, 5 )

		num_choseChoices = [ u"1", u"2", u"3", u"4", u"5" ]
		self.num_chose = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, num_choseChoices, 0 )
		self.num_chose.SetSelection( 0 )
		bSizer4.Add( self.num_chose, 0, wx.ALL, 5 )


		bSizer1.Add( bSizer4, 0, 0, 5 )

		bSizer12 = wx.BoxSizer( wx.HORIZONTAL )


		bSizer1.Add( bSizer12, 0, 0, 5 )

		self.input_all = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE )
		bSizer1.Add( self.input_all, 1, wx.ALL|wx.EXPAND, 5 )

		bSizer5 = wx.BoxSizer( wx.HORIZONTAL )

		self.clear_button = wx.Button( self, wx.ID_ANY, u"清除", wx.DefaultPosition, wx.DefaultSize, 0 )
		bSizer5.Add( self.clear_button, 0, wx.ALL, 5 )

		self.exit_button = wx.Button( self, wx.ID_ANY, u"退出", wx.DefaultPosition, wx.DefaultSize, 0 )
		bSizer5.Add( self.exit_button, 0, wx.ALL, 5 )


		bSizer1.Add( bSizer5, 0, wx.ALIGN_RIGHT, 5 )


		self.SetSizer( bSizer1 )
		self.Layout()

		self.Centre( wx.BOTH )

		# Connect Events
		self.client_button.Bind( wx.EVT_BUTTON, self.do_client_sys )
		self.clear_button.Bind( wx.EVT_BUTTON, self.clear_input )
		self.exit_button.Bind( wx.EVT_BUTTON, self.exit_sys )

	def __del__( self ):
		pass


	# Virtual event handlers, override them in your derived class
	def do_client_sys( self, event ):
		event.Skip()

	def clear_input( self, event ):
		event.Skip()

	def exit_sys( self, event ):
		event.Skip()


###########################################################################
## Class MyPanel1
###########################################################################

class MyPanel1 ( wx.Panel ):

	def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ):
		wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name )


	def __del__( self ):
		pass

代码设计

我们需要获取数据的 IP 为:demo-monitor.igong.com

由于通过 c 语言使用 Socket 进行网络连接并不能解析域名封装过的 IP,所以我们可以直接访问当前连接,然后通过 F12 查看网络协议参数,能够看到我们的所需要访问的真正 IP

首先我们需要设置一些默认的数据和头文件:

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <winsock2.h>  
#include <math.h>
#include "stdint.h"
#define length_8 8    //定义一个宏,为传入8位16进制数的个数
#define PORT 8002     // 设置默认端口,这里 tcp 需要使用的端口是 8002
#define SERVER_IP "123.56.90.74"  //设置默认IP
#define BUFFER_SIZE 4196

这里我们同样需要通过 CRC 验证得到两位校验位,C 语言实现 CRC 校验代码如下:

uint16_t CRC_16(uint8_t *temp)
{
	uint8_t i,j;
	uint16_t CRC_1 = 0xFFFF;          //声明CRC寄存区,也就是步骤1
	for(i = 0;i < 6;i++)       //这里的for循环说的是步骤6中的重复步骤 2 到步骤 5
	{
		CRC_1 ^= temp[i];      //这里就是步骤2,进行异或运算
		for(j = 0;j < 8;j++)         //用来将异或后的低八位全部移出的for循环
		{
			if(CRC_1 & 0x01)         //判断低八位的最后一位是否为1,为1时执行下列语句,也就是步骤3说的移位判断与步骤5说的右移8次
			{
				/*一定要先移位,再异或*/
				CRC_1 >>=1;          //移位后再异或,就是步骤4
				CRC_1 ^= 0xA001;     //0xA001为0x8005的逆序
			}
			else                    //若不为1,则直接移位。
			{
				CRC_1 >>=1;
			}
		}
	}
	
	//	CRC_1 = (((CRC_1 & 0xFF)<<8) + (CRC_1>>8));
	//	printf("%04x\r\n",CRC_1);     //用于打印检测CRC校验码
	return(CRC_1);
}

根据示例代码进行分析,我们还需要将十六进制从数组转为字符串,代码如下:

unsigned char *fromhex(char *str)
{
	static unsigned char buf[512];
	size_t len = strlen(str) / 2;
	if (len > 512) len = 512;
	for (size_t i = 0; i < len; i++) {
		unsigned char c = 0;
		if (str[i * 2] >= '0' && str[i*2] <= '9') 
			c += (str[i * 2] - '0') << 4;
		if ((str[i * 2] & ~0x20) >= 'A' && (str[i*2] & ~0x20) <= 'F') 
			c += (10 + (str[i * 2] & ~0x20) - 'A') << 4;
		if (str[i * 2 + 1] >= '0' && str[i * 2 + 1] <= '9') 
			c += (str[i * 2 + 1] - '0');
		if ((str[i * 2 + 1] & ~0x20) >= 'A' && (str[i * 2 + 1] & ~0x20) <= 'F')
			c += (10 + (str[i * 2 + 1] & ~0x20) - 'A');
		buf[i] = c;
	}
	return buf;
}

这里还需要如下两个函数:获取ch字符在sign数组中的序号,十六进制数转换为十进制数,代码如下:

/* 返回ch字符在sign数组中的序号 */
int getIndexOfSigns(char ch)
{
	if(ch >= '0' && ch <= '9')
	{
		return ch - '0';
	}
	if(ch >= 'A' && ch <='F') 
	{
		return ch - 'A' + 10;
	}
	if(ch >= 'a' && ch <= 'f')
	{
		return ch - 'a' + 10;
	}
	return -1;
}
/* 十六进制数转换为十进制数 */
int hexToDec(char *source)
{
	int sum = 0;
	char low,high;
	for(int i=0,j=7;i<4;i++){
		//TODO
		high = (source[i] & 0xf0)>>4;
		low = source[i] & 0x0f;
		sum += high*pow(16,j--)+low*pow(16,j--); 
	}
	return sum;
}

做完以上准备,我们就可以进行 TCP 或 UDP 连接了,下面我们通过代码进行介绍

TCP 连接

初始化及进行连接的代码如下:

	// 初始化socket dll。
	WORD winsock_version = MAKEWORD(2,2);
	//WSADATA结构包含有关Windows Sockets实现的信息。
	WSADATA wsa_data;
	//Winsock进行初始化
	//调用 WSAStartup 函数以启动使用 WS2 _32.dll
	//WSAStartup的 MAKEWORD (2,2) 参数发出对系统上 Winsock 版本2.2 的请求,并将传递的版本设置为调用方可以使用的最新版本的 Windows 套接字支持
	int iResult = WSAStartup(MAKEWORD(2, 2), &wsa_data);
	if (iResult != 0) {
		printf("WSAStartup 失败!\n");
		WSACleanup();
		return 1;
	}
	
	// socket 函数创建绑定到特定
	//为服务器创建一个SOCKET来监听客户端连接
	//socket函数创建绑定到特定传输服务提供者的套接字。
	//参数1:地址族规范
	//参数2:新套接字的类型规范
	//参数3:使用的协议
	SOCKET client_socket = INVALID_SOCKET;
	client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (client_socket == INVALID_SOCKET) {
		printf("套接字错误\n");
		WSACleanup();
		return 2;
	}
	
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
	//尝试连接到一个地址,直到一个成功	
	if (connect(client_socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
		printf("Failed to connect server: %ld !\n", GetLastError());
		closesocket(client_socket);//关闭一个已存在的套接字。
		client_socket = INVALID_SOCKET;
		return 3;
	}

下面我们需要获取数据

		char *data = new char[length_8];	// 要进行输入的指令数据
		printf("具体指令给格式为0+传感器编号(1,2,3,4,5)0300010002");
		printf("请输入采集传感器的指令) 输入exit退出:\r\n");
		scanf("%s",data);
		if (strcmp(data,kExitFlag)==0) {	// 输入退出
			printf("Exit!\n");
			break;
		}
		uint16_t crc;	// CRC 校验数据
		unsigned char * cmd;	// 要进行输入的控制台shuju
		char crc1[8];
		cmd = fromhex(data);	// 获取数据
		crc = CRC_16(cmd);		// 进行 CRC 校验
		uint8_t a = 0xFF;
		for(int i=0;i<6;i++){
			//TODO
			crc1[i] = cmd[i];
		}
		crc1[6] = a & crc;	// 设置最后两位为 CRC 校验位
		crc1[7] = (crc >> 8) & a;
		
		if (send(client_socket, crc1, 8, 0) < 0) {
			printf("发送失败!\n");
			break;
		}
		
		int ret = recv(client_socket, recv_data, BUFFER_SIZE, 0);
		if (ret < 0) {
			printf("接收失败!\n");
			break;
		}
		recv_data[ret]=0; // 正确结束收到的字符串
		char yb[4],wd[4];
		for(int i=0;i<4;i++){
			//TODO
			yb[i] = recv_data[4+i];
			wd[i] = recv_data[8+i];
			
		}
		float mic = hexToDec(yb)/100.0;		// 由于我们获取到的数据是十六进制,这里需要进行进制转换
		float strain_temp = hexToDec(wd)/100.0;
		printf("应变:%f\r\n",mic);
		printf("温度:%f\r\n",strain_temp);

最后我们需要关闭连接即可:

	closesocket(client_socket);
	WSACleanup();

完整 TCP 连接代码如下:

点击查看完整 TCP 连接代码
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <winsock2.h>  
#include <math.h>
#include "stdint.h"
#define length_8 8    //定义一个宏,为传入8位16进制数的个数
#define PORT 8002
#define SERVER_IP "123.56.90.74"
#define BUFFER_SIZE 4196

const char* kExitFlag = "exit";

/* 返回ch字符在sign数组中的序号 */
int getIndexOfSigns(char ch)
{
	if(ch >= '0' && ch <= '9')
	{
		return ch - '0';
	}
	if(ch >= 'A' && ch <='F') 
	{
		return ch - 'A' + 10;
	}
	if(ch >= 'a' && ch <= 'f')
	{
		return ch - 'a' + 10;
	}
	return -1;
}
/* 十六进制数转换为十进制数 */
int hexToDec(char *source)
{
	int sum = 0;
	char low,high;
	for(int i=0,j=7;i<4;i++){
		//TODO
		high = (source[i] & 0xf0)>>4;
		low = source[i] & 0x0f;
		sum += high*pow(16,j--)+low*pow(16,j--); 
	}
	return sum;
}


unsigned char *fromhex(char *str)
{
	static unsigned char buf[512];
	size_t len = strlen(str) / 2;
	if (len > 512) len = 512;
	for (size_t i = 0; i < len; i++) {
		unsigned char c = 0;
		if (str[i * 2] >= '0' && str[i*2] <= '9') 
			c += (str[i * 2] - '0') << 4;
		if ((str[i * 2] & ~0x20) >= 'A' && (str[i*2] & ~0x20) <= 'F') 
			c += (10 + (str[i * 2] & ~0x20) - 'A') << 4;
		if (str[i * 2 + 1] >= '0' && str[i * 2 + 1] <= '9') 
			c += (str[i * 2 + 1] - '0');
		if ((str[i * 2 + 1] & ~0x20) >= 'A' && (str[i * 2 + 1] & ~0x20) <= 'F')
			c += (10 + (str[i * 2 + 1] & ~0x20) - 'A');
		buf[i] = c;
	}
	return buf;
}

uint16_t CRC_16(uint8_t *temp)
{
	uint8_t i,j;
	uint16_t CRC_1 = 0xFFFF;          //声明CRC寄存区,也就是步骤1
	for(i = 0;i < 6;i++)       //这里的for循环说的是步骤6中的重复步骤 2 到步骤 5
	{
		CRC_1 ^= temp[i]; //这里就是步骤2,进行异或运算
		for(j = 0;j < 8;j++)         //用来将异或后的低八位全部移出的for循环
		{
			if(CRC_1 & 0x01)         //判断低八位的最后一位是否为1,为1时执行下列语句,也就是步骤3说的移位判断与步骤5说的右移8次
			{
				/*一定要先移位,再异或*/
				CRC_1 >>=1;          //移位后再异或,就是步骤4
				CRC_1 ^= 0xA001;     //0xA001为0x8005的逆序
			}
			else                    //若不为1,则直接移位。
			{
				CRC_1 >>=1;
			}
		}
	}
	
	//	CRC_1 = (((CRC_1 & 0xFF)<<8) + (CRC_1>>8));
	//	printf("%04x\r\n",CRC_1);     //用于打印检测CRC校验码
	return(CRC_1);
}

int main() {
	printf("启动TCP连接!\n");
	// 初始化socket dll。
	WORD winsock_version = MAKEWORD(2,2);
	//WSADATA结构包含有关Windows Sockets实现的信息。
	WSADATA wsa_data;
	//Winsock进行初始化
	//调用 WSAStartup 函数以启动使用 WS2 _32.dll
	//WSAStartup的 MAKEWORD (2,2) 参数发出对系统上 Winsock 版本2.2 的请求,并将传递的版本设置为调用方可以使用的最新版本的 Windows 套接字支持
	int iResult = WSAStartup(MAKEWORD(2, 2), &wsa_data);
	if (iResult != 0) {
		printf("WSAStartup 失败!\n");
		WSACleanup();
		return 1;
	}
	
	// socket 函数创建绑定到特定
	//为服务器创建一个SOCKET来监听客户端连接
	//socket函数创建绑定到特定传输服务提供者的套接字。
	//参数1:地址族规范
	//参数2:新套接字的类型规范
	//参数3:使用的协议
	SOCKET client_socket = INVALID_SOCKET;
	client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (client_socket == INVALID_SOCKET) {
		printf("套接字错误\n");
		WSACleanup();
		return 2;
	}
	
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(PORT);
	server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
	//尝试连接到一个地址,直到一个成功	
	if (connect(client_socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) {
		printf("Failed to connect server: %ld !\n", GetLastError());
		closesocket(client_socket);//关闭一个已存在的套接字。
		client_socket = INVALID_SOCKET;
		return 3;
	}
	
	char recv_data[BUFFER_SIZE+1];
	while (true) {
		char *data = new char[length_8];	// 要进行输入的指令数据
		printf("具体指令给格式为0+传感器编号(1,2,3,4,5)+0300010002,请输入采集传感器的编号(0表示退出采集):\n");
		scanf("%s",data);
		if (strcmp(data,kExitFlag)==0) {	// 输入退出
			printf("Exit!\n");
			break;
		}
		uint16_t crc;	// CRC 校验数据
		unsigned char * cmd;	// 要进行输入的控制台shuju
		char crc1[8];
		cmd = fromhex(data);	// 获取数据
		crc = CRC_16(cmd);		// 进行 CRC 校验
		uint8_t a = 0xFF;
		for(int i=0;i<6;i++){
			//TODO
			crc1[i] = cmd[i];
		}
		crc1[6] = a & crc;	// 设置最后两位为 CRC 校验位
		crc1[7] = (crc >> 8) & a;
		
		if (send(client_socket, crc1, 8, 0) < 0) {
			printf("发送失败!\n");
			break;
		}
		
		int ret = recv(client_socket, recv_data, BUFFER_SIZE, 0);
		if (ret < 0) {
			printf("接收失败!\n");
			break;
		}
		recv_data[ret]=0; // 正确结束收到的字符串
		char yb[4],wd[4];
		for(int i=0;i<4;i++){
			//TODO
			yb[i] = recv_data[4+i];
			wd[i] = recv_data[8+i];
			
		}
		float mic = hexToDec(yb)/100.0;		// 由于我们获取到的数据是十六进制,这里需要进行进制转换
		float strain_temp = hexToDec(wd)/100.0;
		printf("应变:%f\r\n",mic);
		printf("温度:%f\r\n",strain_temp);
		
		
		//printf("Receive data from server: \"%x\"\n",recv_data);
	}
	
	closesocket(client_socket);
	WSACleanup();
	
	return 0;
}



UDP 连接

这里我们的连接原理与 TCP 连接相似,这里我们直接给出完整代码:

点击查看完整 UDP 连接代码
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <winsock2.h>  
#include <math.h>
#include "stdint.h"
#define length_8 8    //定义一个宏,为传入8位16进制数的个数
#define PORT 8001
#define SERVER_IP "123.56.90.74"
#define  BUFFER_SIZE 4196

const char* kExitFlag = "exit";

/* 返回ch字符在sign数组中的序号 */
int getIndexOfSigns(char ch)
{
	if(ch >= '0' && ch <= '9')
	{
		return ch - '0';
	}
	if(ch >= 'A' && ch <='F') 
	{
		return ch - 'A' + 10;
	}
	if(ch >= 'a' && ch <= 'f')
	{
		return ch - 'a' + 10;
	}
	return -1;
}
/* 十六进制数转换为十进制数 */
int hexToDec(char *source)
{
	int sum = 0;
	char low,high;
	for(int i=0,j=7;i<4;i++){
		//TODO
		high = (source[i] & 0xf0)>>4;
		low = source[i] & 0x0f;
		sum += high*pow(16,j--)+low*pow(16,j--); 
	}
	return sum;
}



unsigned char *fromhex(const char *str)
{
	static unsigned char buf[512];
	size_t len = strlen(str) / 2;
	if (len > 512) len = 512;
	for (size_t i = 0; i < len; i++) {
		unsigned char c = 0;
		if (str[i * 2] >= '0' && str[i*2] <= '9') 
			c += (str[i * 2] - '0') << 4;
		if ((str[i * 2] & ~0x20) >= 'A' && (str[i*2] & ~0x20) <= 'F') 
			c += (10 + (str[i * 2] & ~0x20) - 'A') << 4;
		if (str[i * 2 + 1] >= '0' && str[i * 2 + 1] <= '9') 
			c += (str[i * 2 + 1] - '0');
		if ((str[i * 2 + 1] & ~0x20) >= 'A' && (str[i * 2 + 1] & ~0x20) <= 'F')
			c += (10 + (str[i * 2 + 1] & ~0x20) - 'A');
		buf[i] = c;
	}
	return buf;
}

uint16_t CRC_16(uint8_t *temp)
{
	uint8_t i,j;
	uint16_t CRC_1 = 0xFFFF;          //声明CRC寄存区,也就是步骤1
	for(i = 0;i < 6;i++)       //这里的for循环说的是步骤6中的重复步骤 2 到步骤 5
	{
		CRC_1 ^= temp[i]; //这里就是步骤2,进行异或运算
		for(j = 0;j < 8;j++)         //用来将异或后的低八位全部移出的for循环
		{
			if(CRC_1 & 0x01)         //判断低八位的最后一位是否为1,为1时执行下列语句,也就是步骤3说的移位判断与步骤5说的右移8次
			{
				/*一定要先移位,再异或*/
				CRC_1 >>=1;          //移位后再异或,就是步骤4
				CRC_1 ^= 0xA001;     //0xA001为0x8005的逆序
			}
			else                    //若不为1,则直接移位。
			{
				CRC_1 >>=1;
			}
		}
	}
	
	//	CRC_1 = (((CRC_1 & 0xFF)<<8) + (CRC_1>>8));
	//	printf("%04x\r\n",CRC_1);     //用于打印检测CRC校验码
	return(CRC_1);
}

int main() {
	printf("启动UDP连接!\n");
	// 初始化socket dll。
	WORD winsock_version = MAKEWORD(2,2);
	WSADATA wsa_data;
	if (WSAStartup(winsock_version, &wsa_data) != 0) {
		printf("Failed to init socket!\n");
		return 1;
	}
	
	SOCKET client_socket = socket(AF_INET, SOCK_DGRAM, 0);
	if (client_socket == INVALID_SOCKET) {
		printf("Failed to create server socket!\n");
		return 2;
	}
	
	char recv_data[BUFFER_SIZE+1];
	
	while (true) {
//		uint8_t data[length_8];
		char *data = new char[length_8];
		printf("具体指令给格式为0+传感器编号(1,2,3,4,5)+0300010002,请输入采集传感器的编号(0表示退出采集):\n");
		scanf("%s",data);
		if (strcmp(data,kExitFlag)==0) {
			printf("Exit!\n");
			break;
		}
		uint16_t crc;
		unsigned char * cmd;
		char crc1[8];
		cmd = fromhex(data);
		crc = CRC_16(cmd);
		uint8_t a = 0xFF;
		for(int i=0;i<6;i++){
			//TODO
			crc1[i] = cmd[i];
		}
		crc1[6] = a & crc;
		crc1[7] = (crc >> 8) & a;
		
		struct sockaddr_in server_addr;
		server_addr.sin_family = AF_INET;
		server_addr.sin_port = htons(PORT);
		server_addr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);
		
		if (sendto(client_socket, crc1, 8, 0,(struct sockaddr*)&server_addr,sizeof(server_addr)) < 0) {
			printf("Failed to send data!\n");
			break;
		}
		
		int ret = recvfrom(client_socket, recv_data, BUFFER_SIZE, 0,NULL,NULL);
		if (ret < 0) {
			printf("Failed to receive data!\n");
			break;
		}
		recv_data[ret]=0; // correctly ends received string
		char var = cmd[5];
		if(var == 2){
			printf("两个传感器:温度,湿度\r\n");
			//TODO
			char yb[4],wd[4];
			for(int i=0;i<4;i++){
				//TODO
				yb[i] = recv_data[4+i];
				wd[i] = recv_data[8+i];
				
			}
			float temp = hexToDec(yb)/100.0;
			float hum = hexToDec(wd);
			printf("温度:%4.2f\r\n",temp);
			printf("湿度:%4.2f\r\n",hum);
		}else if(var == 1){
			//TODO
			printf("一个传感器:静力水准仪\r\n");
			char nd[4];
			for(int i=0;i<4;i++){
				//TODO
				nd[i] = recv_data[4+i];
			}
			float water_level = hexToDec(nd)*10.0;
			printf("挠度:%6.2f\r\n",water_level);
		}
	}
	
	closesocket(client_socket);
	WSACleanup();
	
	return 0;
}

运行结果

image

image

参考资料

  1. libmodbus库在Windows下编译和使用
posted @ 2022-12-22 01:55  ppqppl  阅读(90)  评论(0编辑  收藏  举报