前言:
前段时间,服务器增加串口通信需求,早期串口框架仅仅只能支持单串口通信,还不支持热插拔。
由于在网上找了下没有自己想要的框架,因此,自己搭建了一个异步串口通信框架, 目前该框架已投入使用,没有发现内存泄露或者其他问题。该框架运用了【libserialport】【libuv】开源库,二者都支持跨平台,因此在可以放心在其他平台下使用该框架。
如果你确定要使用我的框架,请阅读最后的框架分点讲解部分,里面涉及到部分细节设置的地方。
功能:异步串口服务器框架(支持热插拔、多COM口通信框架)
源代码:链接:https://pan.baidu.com/s/1XmwsXP8UbG1l-AFESogFiQ 提取码:qdz7
平台:Linux_X86(该平台已验证)(支持跨平台)
语言:C
工程:
下载源代码后,直接在QCom目录下运行【make】命令即可生成测试程序【com】。框架代码在【QCom/src/ComFarme】目录下
框架主体:
我先把整体代码放出来,在分点讲解。
框架头文件:【comfarme.h】
#ifndef COMFARME_H
#define COMFARME_H
#ifdef __cplusplus
extern "C" {
#endif
int ComFarme_NewHandle(void **handle);
void ComFarme_FreeHandle(void *handle);
int ComFarme_WaitHandle(void *handle);
#ifdef __cplusplus
}
#endif /* #ifdef __cplusplus */
#endif
框架测试DEMO: 【main.c】
#include <stdio.h>
#include <unistd.h>
#include "libserialport.h"
#include "uv.h"
#include "../ComFarme/comfarme.h"
volatile sig_atomic_t _running = 1;
static void catch_signal(int nsig)
{
_running = 0;
}
int main()
{
void *handle = NULL;
int ret = 0;
signal(SIGINT, catch_signal);
signal(SIGTERM, catch_signal);
ret = ComFarme_NewHandle(&handle);
if (ret)
{
printf("ComFarme_NewHandle error\n");
return -1;
}
else
{
printf("ComFarme_NewHandle success\n");
}
while (_running)
{
sleep(2);
}
printf("begin free\n");
ComFarme_FreeHandle(handle);
return 0;
}
框架代码:【comfarme.c】
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "libserialport.h"
#include "uv.h"
#include "comfarme.h"
#define COMFARME_RUN 1
#define COMFARME_STOP 2
#define COMFARME_LOOPTIME 3
#define COMFARME_WAIETIME 2000
#define CONFARME_MAXCOM 1024
#define CONFARME_BUFLEN 1024
#define CONFARME_ADAPTER "/dev/ttyUSB"
typedef struct
{
void *hand;
void *next;
void *handle;
struct sp_port *list_ptr;
uv_thread_t thread_communicate;
unsigned char buf[CONFARME_BUFLEN];
}COMFARME_LIST;
typedef struct
{
uv_thread_t thread_findcom;
struct sp_port_config *config_ptr;
COMFARME_LIST *list;
pthread_mutex_t list_lock;
int comfarme_switch;
}COMFARME_HANDLE;
static int ComFarme_ListDelete(COMFARME_HANDLE *handle, struct sp_port *list_ptr);
/* 打印二进制数据 */
static void ComFarme_PrintfData(unsigned char *data, int len)
{
int i = 0;
for (i = 0; i < len; i++)
{
printf("%02x ", data[i]);
}
printf("\n");
}
/* 数据通信模块 */
static void ComFarme_Communicate(void* arg)
{
COMFARME_LIST *data = (COMFARME_LIST *)arg;
COMFARME_HANDLE *handle = (COMFARME_HANDLE *)data->handle;
struct sp_event_set *result_ptr = NULL;
enum sp_return sp_ret = SP_OK;
/* 打开串口 */
sp_ret = sp_open(data->list_ptr, SP_MODE_READ_WRITE);
if (sp_ret != SP_OK)
{
printf("sp_open error @ret %d @errmsg %s\n", sp_ret, sp_last_error_message());
goto ERR_OPEN;
}
else
{
printf("sp_open success\n");
}
/* 配置串口 */
sp_ret = sp_set_config(data->list_ptr, handle->config_ptr);
if (sp_ret != SP_OK)
{
printf("sp_set_config error @ret %d @errmsg %s\n", sp_ret, sp_last_error_message());
goto ERR_CONFIG;
}
else
{
printf("sp_set_config success\n");
}
/* 添加触发器 */
sp_ret = sp_new_event_set(&result_ptr);
if (sp_ret != SP_OK)
{
printf("sp_new_event_set error @ret %d @errmsg %s\n", sp_ret, sp_last_error_message());
goto ERR_CONFIG;
}
else
{
printf("sp_new_event_set success\n");
}
sp_ret = sp_add_port_events(result_ptr, data->list_ptr, SP_EVENT_ERROR);
if (sp_ret != SP_OK)
{
printf("sp_add_port_events error1 @ret %d @errmsg %s\n", sp_ret, sp_last_error_message());
goto ERR_EVENT;
}
sp_ret = sp_add_port_events(result_ptr, data->list_ptr, SP_EVENT_RX_READY);
if (sp_ret != SP_OK)
{
printf("sp_add_port_events error2 @ret %d @errmsg %s\n", sp_ret, sp_last_error_message());
goto ERR_EVENT;
}
/* 等待数据传输 */
while (handle->comfarme_switch == COMFARME_RUN)
{
sp_ret = sp_wait(result_ptr, 0);
if (sp_ret != SP_OK)
{
printf("sp_wait error @ret %d @errmsg %s\n", sp_ret, sp_last_error_message());
}
memset(data->buf, 0, CONFARME_BUFLEN);
sp_ret = sp_nonblocking_read(data->list_ptr, data->buf, CONFARME_BUFLEN);
if (sp_ret <= 0)
{
printf("sp_nonblocking_read error @ret %d @errmsg %s\n", sp_ret, sp_last_error_message());
goto ERR_EVENT;
}
else
{
printf("sp_nonblocking_read success @ret %d\n", sp_ret);
ComFarme_PrintfData(data->buf, (int)sp_ret);
sp_ret = sp_blocking_write(data->list_ptr, data->buf, (size_t )sp_ret, COMFARME_WAIETIME);
if (sp_ret <= 0)
{
printf("sp_nonblocking_write error @ret %d @errmsg %s\n", sp_ret, sp_last_error_message());
}
else
{
printf("send:\n");
ComFarme_PrintfData(data->buf, sp_ret);
}
}
}
ERR_EVENT:
sp_free_event_set(result_ptr);
ERR_CONFIG:
sp_close(data->list_ptr);
ERR_OPEN:
ComFarme_ListDelete(handle, data->list_ptr);
}
/* 注册串口事件 */
static int ComFarm_EventAdd(COMFARME_LIST *data)
{
return uv_thread_create(&(data->thread_communicate), ComFarme_Communicate, data);
}
/* 删除串口 */
static int ComFarme_ListDelete(COMFARME_HANDLE *handle, struct sp_port *list_ptr)
{
COMFARME_LIST *list = handle->list;
COMFARME_LIST *hand = NULL, *next = NULL;
pthread_mutex_lock(&handle->list_lock);
while ( list != NULL )
{
if (strcmp(sp_get_port_name(list->list_ptr), sp_get_port_name(list_ptr)) == 0 )
{
hand = (COMFARME_LIST *)list->hand;
next = (COMFARME_LIST *)list->next;
if (hand != NULL)
{
hand->next = next;
}
if (next != NULL)
{
next->hand = hand;
}
if (hand == NULL && next == NULL)
{
handle->list = NULL;
}
else if(hand == NULL)
{
handle->list = (COMFARME_LIST *)(handle->list)->next;
}
sp_free_port(list->list_ptr);
free(list);
pthread_mutex_unlock(&handle->list_lock);
return 0;
}
list = (COMFARME_LIST *)list->next;
}
pthread_mutex_unlock(&handle->list_lock);
return 0;
}
/* 增加串口 */
static int ComFarme_ListAdd(COMFARME_HANDLE *handle, struct sp_port *list_ptr)
{
struct sp_port *ptr = NULL;
enum sp_return sp_ret = SP_OK;
COMFARME_LIST *node = NULL;
int ret = 0;
pthread_mutex_lock(&handle->list_lock);
sp_ret = sp_copy_port(list_ptr, &ptr);
if (sp_ret != SP_OK)
{
pthread_mutex_unlock(&handle->list_lock);
return -1;
}
node = (COMFARME_LIST *)malloc(sizeof(COMFARME_LIST));
memset(node, 0, sizeof(COMFARME_LIST));
node->list_ptr = ptr;
node->handle = handle;
if (handle->list == NULL)
{
node->hand = NULL;
node->next = NULL;
handle->list = node;
}
else
{
node->hand = NULL;
node->next = handle->list;
handle->list = node;
}
ret = ComFarm_EventAdd(node);
if (ret)
{
pthread_mutex_unlock(&handle->list_lock);
return -1;
}
pthread_mutex_unlock(&handle->list_lock);
return 0;
}
static int ComFarme_ListCompare(COMFARME_HANDLE *handle, struct sp_port *list_ptr)
{
COMFARME_LIST *list = handle->list;
//printf("@ComFarme_ListCompare %s\n", sp_get_port_name(list_ptr));
/* 过滤COM口 */
if (memcmp(sp_get_port_name(list_ptr), CONFARME_ADAPTER, strlen(CONFARME_ADAPTER)) != 0)
{
//printf("@ComFarme_ListCompare -1 @len %d\n", strlen(CONFARME_ADAPTER));
return -1;
}
pthread_mutex_lock(&handle->list_lock);
while (list != NULL)
{
if ( strcmp(sp_get_port_name(list->list_ptr), sp_get_port_name(list_ptr) ) == 0 )
{
pthread_mutex_unlock(&handle->list_lock);
//printf("@ComFarme_ListCompare -2");
return -2;
}
list = (COMFARME_LIST *)list->next;
}
pthread_mutex_unlock(&handle->list_lock);
printf("@new com %s\n", sp_get_port_name(list_ptr));
return 0;
}
static int ComFarme_GetCom(COMFARME_HANDLE *handle)
{
struct sp_port **list_ptr = NULL;
enum sp_return sp_ret = SP_OK;
int i = 0, ret = 0;
/* COM口配置 */
if (!handle->config_ptr)
{
sp_ret = sp_new_config(&(handle->config_ptr));
if (sp_ret != SP_OK)
{
printf("sp_new_config error\n");
return -1;
}
sp_set_config_baudrate(handle->config_ptr, 115200);
sp_set_config_bits(handle->config_ptr, 8);
sp_set_config_parity(handle->config_ptr, SP_PARITY_NONE);
sp_set_config_stopbits(handle->config_ptr, 1);
}
/* 获取列表 */
sp_ret = sp_list_ports(&list_ptr);
if (sp_ret != SP_OK)
{
printf("sp_list_ports error @ret %d\n", sp_ret);
return -1;
}
for (i = 0; i < CONFARME_MAXCOM; i++)
{
if (list_ptr[i] == NULL)
{
break;
}
if (ComFarme_ListCompare(handle, list_ptr[i]) == 0 )
{
/* 增加串口 */
ret = ComFarme_ListAdd(handle, list_ptr[i]);
if (ret)
{
printf("ComFarme_ListAdd error @ret %d\n", ret);
sp_free_port_list(list_ptr);
return -1;
}
else
{
printf("ComFarme_ListAdd success @ret %d\n", ret);
}
}
}
/* 释放列表 */
sp_free_port_list(list_ptr);
return 0;
}
static void ComFarme_WaitList(COMFARME_LIST *data)
{
if (data == NULL)
{
return;
}
ComFarme_WaitList((COMFARME_LIST *)data->next);
uv_thread_join(&(data->thread_communicate));
}
static void ComFarme_CheckCom(void* arg)
{
COMFARME_HANDLE *handle = (COMFARME_HANDLE *)arg;
while (handle->comfarme_switch == COMFARME_RUN)
{
ComFarme_GetCom(handle);
sleep(COMFARME_LOOPTIME);
}
/* 等待结束 */
ComFarme_WaitList(handle->list);
}
int ComFarme_NewHandle(void **handle)
{
int ret = 0;
if (*handle != NULL)
{
return -1;
}
COMFARME_HANDLE *data = (COMFARME_HANDLE *)malloc(sizeof(COMFARME_HANDLE));
memset(data, 0, sizeof(COMFARME_HANDLE));
data->comfarme_switch = COMFARME_RUN;
pthread_mutex_init(&data->list_lock, NULL);
ret = uv_thread_create(&(data->thread_findcom), ComFarme_CheckCom, data);
if (ret)
{
free(data);
return -1;
}
*handle = data;
return 0;
}
void ComFarme_FreeHandle(void *handle)
{
COMFARME_HANDLE *data = (COMFARME_HANDLE *)handle;
data->comfarme_switch = COMFARME_STOP;
ComFarme_WaitHandle(handle);
pthread_mutex_destroy(&data->list_lock);
free(data);
}
int ComFarme_WaitHandle(void *handle)
{
COMFARME_HANDLE *data = (COMFARME_HANDLE *)handle;
return uv_thread_join(&(data->thread_findcom));
}
框架分点讲解:
框架入口:
【ComFarme_NewHandle】函数为框架入口,主要功能是启动监听服务器串口的线程【uv_thread_create(&(data->thread_findcom), ComFarme_CheckCom, data);】。
监听服务器串口:
【ComFarme_CheckCom】函数的功能定时监听服务器串口,每隔【COMFARME_LOOPTIME】秒监听一次;
【ComFarme_GetCom】函数的功能为检查是否有新增串口,【handle->config_ptr】为串口配置,可以在该函数中设置串口波特率、奇偶校验等参数,通过【sp_list_ports】函数获取当前服务器串口情况,并通过【ComFarme_ListCompare】函数判断当前串口是否已添加进链表,你可以在【ComFarme_ListCompare】函数过滤串口,比如本工程中在【ComFarme_ListCompare】函数中添加了下列代码,表示只对【ttyUSB*】提供串口读写服务,这块根据个人需求进行修改,如果你需要对所有串口提供服务,则可以删除该段:
/* 过滤COM口 */
if (memcmp(sp_get_port_name(list_ptr), CONFARME_ADAPTER, strlen(CONFARME_ADAPTER)) != 0)
{
//printf("@ComFarme_ListCompare -1 @len %d\n", strlen(CONFARME_ADAPTER));
return -1;
}
如果是新串口,则通过【ComFarme_ListAdd】函数添加进链表;
添加链表
【ComFarme_ListAdd】函数功能是将新串口添加进链表,并通过【ComFarm_EventAdd】函数,开启新的线程,该线程为新串口提供读写服务。
读写服务
【ComFarm_EventAdd】函数功能是为一个串口开启读写服务线程,【ComFarme_Communicate】函数主要功能是为串口提供读写服务;
【ComFarme_Communicate】函数,先通过【sp_open】函数将【ComFarme_ListAdd】函数传入的新串口打开,而后通过【sp_set_config】函数将串口波特率等参数进行设置,通过【sp_new_event_set】创建触发器,并通过【sp_add_port_events】函数将该串口的读写及插拔设置为触发事件,而后通过【sp_wait】函数进行阻塞,等待事件的发生。
当该串口数据传入服务器后,通过【sp_nonblocking_read】函数进行数据读取,通过【sp_blocking_write】函数进行数据回复。
当该串口被拔走后,则调用【ComFarme_ListDelete】函数,将该串口从链表中删除。
框架出口:
调用【ComFarme_FreeHandle】函数,可以结束该框架。
首先通过【data->comfarme_switch = COMFARME_STOP】语句将框架标志位设置为停止,然后等待【ComFarme_CheckCom】线程函数正常退出。
在【ComFarme_CheckCom】线程函数中【ComFarme_WaitList】函数的功能是等待链表中的串口服务线程【ComFarme_Communicate】正常退出。由于【ComFarme_Communicate】函数中【sp_wait】阻塞,因此只有串口发生通信或者串口被拔出后,【ComFarme_Communicate】函数才能正常退出。我在使用中,是先拔出串口再结束程序,如果该框架在你程序中只开启关闭一次,则可以在【ComFarme_CheckCom】线程函数删除下面这条语句:
/* 等待结束 */
ComFarme_WaitList(handle->list);