串口转以太客户端(增加uci、可连接多个服务器)
1、 进入barrier_breaker/package/utils文件夹,新建ttl_client
2、 该目录下的Makefile
#
# Copyright (C) 2009 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=ttl_client
PKG_RELEASE:=1
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define Package/ttl_client
SECTION:=utils
CATEGORY:=Utilities
TITLE:=serial to tcp
DEPENDS:=+libuci +libpthread
endef
define Package/ttl_client/description
A client of tcp to serial or serial to tcp
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Build/Configure
endef
define Build/Compile
$(MAKE) -C $(PKG_BUILD_DIR) \
CC="$(TARGET_CC)" \
CFLAGS="$(TARGET_CFLAGS) -Wall -I$(LINUX_DIR)/user_headers/include" \
LDFLAGS="$(TARGET_LDFLAGS)"
endef
define Package/ttl_client/install
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_DIR) $(1)/etc/config
$(CP) -rf ./files/ttl2tcp $(1)/etc/config/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ttl_client $(1)/usr/sbin/
endef
$(eval $(call BuildPackage,ttl_client))
3、 新建files文件夹,在该文件夹中新建ttl2tcp配置文件,内容如下
package ttl2tcp
config serial device
#option name ttyUSB0
#option name ttyS0
option name ttyATH0
option baudrate 115200
option data 8
option parity None
option stop 1
option enabled 1
config server
option ipaddr 172.16.1.165
option port 10001
config server
option ipaddr 172.16.1.139
option port 10001
config server
option ipaddr 172.16.1.235
option port 10001
4、 barrier_breaker/package/utils/ttl_client/src中的Makefile文件,内容如下
CC = gcc
CFLAGS = -Wall
OBJS = ttl_client.o
all: ttl_client
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
ttl_client: $(OBJS)
$(CC) -o $@ $(OBJS) -luci -lpthread
clean:
rm -f rbcfg *.o
5、 barrier_breaker/package/utils/ttl_client/src中的ttl_client.c
/*
* ttl_client
*
* tingpan<dktingpan@sina.cn> 2015-05-31
*
* this is a client of serial translate to tcp or tcp translate to serial.
* serial read overtime is 1s
* every server read overtime is 0.5s,and the most server number is 3.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <strings.h>
#include <time.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <uci.h>
#include <semaphore.h>
#define SER_MAXLINE 128
#define SOCK_MAXLINE 136//为了应对缓存溢出,多分配一个字节
#define SERVER_MAXNUM 3
struct argument
{
int fd;
int sockfd[SERVER_MAXNUM];
};
unsigned char on_max;
struct _options {
char name[10];
unsigned int baudrate;
//unsigned int data;
//unsigned int parity;
//unsigned int stop;
unsigned int enabled;
struct in_addr ipaddr[SERVER_MAXNUM];
unsigned int port[SERVER_MAXNUM];
};
struct _options opt;
//pthread_mutex_t socket_lock; //互斥锁
//为了保证用户输入的波特率是个正确的值,所以需要这两个数组验证,对于设置波特率时候,前面要加个B
int speed_arr[] = { B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300,
B115200, B57600, B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300,
115200, 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300, };
/*-----------------------------------------------------------------------------
函数名: set_speed
参数: int fd ,int speed
返回值: void
描述: 设置fd表述符的串口波特率
*-----------------------------------------------------------------------------*/
void set_speed(int fd ,int speed)
{
struct termios opt;
int i;
int status;
tcgetattr(fd,&opt);
for(i = 0;i < sizeof(speed_arr)/sizeof(int);i++)
{
if(speed == name_arr[i]) //找到标准的波特率与用户一致
{
tcflush(fd,TCIOFLUSH); //清除IO输入和输出缓存
cfsetispeed(&opt,speed_arr[i]); //设置串口输入波特率
cfsetospeed(&opt,speed_arr[i]); //设置串口输出波特率
status = tcsetattr(fd,TCSANOW,&opt); //将属性设置到opt的数据结构中,并且立即生效
if(status != 0)
perror("tcsetattr fd:"); //设置失败
return ;
}
tcflush(fd,TCIOFLUSH); //每次清除IO缓存
}
}
/*-----------------------------------------------------------------------------
函数名: set_parity
参数: int fd
返回值: int
描述: 设置fd表述符的奇偶校验
*-----------------------------------------------------------------------------*/
int set_parity(int fd)
{
struct termios opt;
if(tcgetattr(fd,&opt) != 0) //或许原先的配置信息
{
perror("Get opt in parity error:");
return -1;
}
/*通过设置opt数据结构,来配置相关功能,以下为八个数据位,不使能奇偶校验*/
opt.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
opt.c_oflag &= ~OPOST;
opt.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
opt.c_cflag &= ~(CSIZE | PARENB);
opt.c_cflag |= CS8;
tcflush(fd,TCIFLUSH); //清空输入缓存
if(tcsetattr(fd,TCSANOW,&opt) != 0)
{
perror("set attr parity error:");
return -1;
}
return 0;
}
/*-----------------------------------------------------------------------------
函数名: serial_init
参数: char *dev_path,int speed,int is_block
返回值: 初始化成功返回打开的文件描述符
描述: 串口初始化,根据串口文件路径名,串口的速度,和串口是否阻塞,block为1表示阻塞
*-----------------------------------------------------------------------------*/
int serial_init(char *dev_path,int speed,int is_block)
{
int fd;
int flag;
flag = 0;
flag |= O_RDWR; //设置为可读写的串口属性文件
if(is_block == 0)
flag |=O_NONBLOCK; //若为0则表示以非阻塞方式打开
fd = open(dev_path,flag); //打开设备文件
if(fd < 0)
{
perror("Open device file err:");
close(fd);
return -1;
}
/*打开设备文件后,下面开始设置波特率*/
set_speed(fd,speed); //考虑到波特率可能被单独设置,所以独立成函数
/*设置奇偶校验*/
if(set_parity(fd) != 0)
{
perror("set parity error:");
close(fd); //一定要关闭文件,否则文件一直为打开状态
return -1;
}
return fd;
}
/*-----------------------------------------------------------------------------
函数名: serial_send
参数: int fd,char *str,unsigned int len
返回值: 发送成功返回发送长度,否则返回小于0的值
描述: 向fd描述符的串口发送数据,长度为len,内容为str
*-----------------------------------------------------------------------------*/
int serial_send(int fd,char *str,unsigned int len)
{
int ret;
if(len > strlen(str)) //判断长度是否超过str的最大长度
len = strlen(str);
ret = write(fd,str,len);
if(ret < 0)
{
perror("serial send err:");
return -1;
}
return ret;
}
/*-----------------------------------------------------------------------------
函数名: serial_read
参数: int fd,char *str,unsigned int len,unsigned int timeout
返回值: 在规定的时间内读取数据,超时则退出,超时时间为ms级别
描述: 向fd描述符的串口接收数据,长度为len,存入str,timeout 为超时时间
*-----------------------------------------------------------------------------*/
int serial_read(int fd, char *str, unsigned int len, unsigned int timeout)
{
fd_set rfds;
struct timeval tv;
int ret; //每次读的结果
int sret; //select监控结果
int readlen = 0; //实际读到的字节数
char * ptr;
ptr = str; //读指针,每次移动,因为实际读出的长度和传入参数可能存在差异
FD_ZERO(&rfds); //清除文件描述符集合
FD_SET(fd,&rfds); //将fd加入fds文件描述符,以待下面用select方法监听
/*传入的timeout是ms级别的单位,这里需要转换为struct timeval 结构的*/
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout%1000)*1000;
/*防止读数据长度超过缓冲区*/
//if(sizeof(&str) < len)
// len = sizeof(str);
/*开始读*/
while(readlen < len)
{
sret = select(fd+1,&rfds,NULL,NULL,&tv); //检测串口是否可读
if(sret == -1) //检测失败
{
perror("select:");
break;
}
else if(sret > 0) //<SPAN style="WHITE-SPACE: pre"> </SPAN>//检测成功可读
{
ret = read(fd,ptr,1); //第三个参数为请求读取的字节数
if(ret < 0)
{
perror("read err:");
break;
}
else if(ret == 0)
break;
readlen += ret; //更新读的长度
ptr += ret; //更新读的位置
}
else //超时 sret == 0 ,没填满buf也退出循环
{
printf("timeout!\n");
break;
}
}
return readlen;
}
/**
* socket_read: 读取tcp数据
* @fd: socket文件描述符
* @str:将读到的数据存放在该地址
* @len:申请读取的字符长度
* @timeout:超时时间,单位ms
*/
int socket_read(int fd, char *str, unsigned int len, unsigned int timeout)
{
fd_set fdsr;
struct timeval tv;
int readlen = 0;
char * ptr;
int ret;
ptr = str;
// initialize file descriptor set
FD_ZERO(&fdsr);//每次循环都要清空
FD_SET(fd, &fdsr);
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout%1000)*1000;
while(readlen < len){
ret = select(fd + 1, &fdsr, NULL, NULL, &tv);
if (ret < 0) {
perror("select");
//break;
exit(-1);
} else if (ret == 0) {
printf("timeout\n");
break;
}
//每次申请读取8个字节,但事实上是按发送端每次发送的字符串长度来确定的,如果长度小于8,则每次读取实际长度,如果大于8,则读取8字节。
//recv多少就从缓冲区中删除多少,剩下的数据可以在下次recv时得到
//即使子线程挂起,也一直有数据可以读,数据不丢失,真正的接收数据是协议来完成的,存放在s的接收缓冲中。
ret = recv(fd, ptr, 8, 0);//申请8个字节
if (ret <= 0) {//如果连接已中止,返回0。如果发生错误,返回-1
printf("client close\n");
close(fd);
FD_CLR(fd, &fdsr);
fd = 0;
} else {
readlen +=ret;
ptr += ret;
//printf("the ret length is:%d\n",readlen);
}
}
return readlen;
}
/**
* read_config: 读取配置文件
* @popt: 配置信息保存的结构体
*/
static int read_config(struct _options *popt)
{
static struct uci_context *ctx;
struct uci_ptr ptr;
char a[32];
char i;
unsigned char server_num = 0;
ctx = uci_alloc_context();
//读取设备名称
//if ((strcpy(a, "ttl2tcp.device.name") == NULL)
if ((strncpy(a, "ttl2tcp.device.name", sizeof(a)) == NULL)
//if (!snprintf(a,sizeof(a),"ttl2tcp.%s.baudrate",SERNAME)
|| (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
printf("Read ttl2tcp.device.name failed! exit.\n");
exit(-1);
}
if (ptr.o) {
strncpy(popt->name, ptr.o->v.string, sizeof(popt->name));
} else {
printf("ttl2tcp.device.name Not found!\n");
}
//读取串口波特率
if ((strncpy(a, "ttl2tcp.device.baudrate", sizeof(a)) == NULL)
//if (!snprintf(a,sizeof(a),"ttl2tcp.%s.baudrate",SERNAME)
|| (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
printf("Read tttl2tcp.device.baudrate failed! exit.\n");
exit(-1);
}
if (ptr.o) {
popt->baudrate = strtol(ptr.o->v.string, NULL, 10);
} else {
printf("ttl2tcp.device.baudrate Not found!\n");
}
//是否使能
//if (!snprintf(a,sizeof(a),"ttl2tcp.%s.enabled",SERNAME)
if ((strncpy(a, "ttl2tcp.device.enabled", sizeof(a)) == NULL)
|| (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
printf("Read ttl2tcp.device.enabled failed! exit.\n");
exit(-1);
}
if (ptr.o) {
popt->enabled = strtol(ptr.o->v.string, NULL, 10);
} else {
printf("ttl2tcp.device.enabled Not found!\n");
}
for(i=0; i<SERVER_MAXNUM; i++){
//读取ip地址
//if ((strncpy(a, "ttl2tcp.@server[0].ipaddr",sizeof(a)) == NULL)
if (!snprintf(a,sizeof(a),"ttl2tcp.@server[%d].ipaddr",i)
|| (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
printf("Read ttl2tcp.@server[%d].ipaddr failed! exit.\n",i);
exit(-1);
}
if (ptr.o) {
unsigned int laddr;
laddr = inet_addr(ptr.o->v.string);//因为ipaddr为网络字节顺序,大端字节序,而输入的为主机字节序
memcpy(&popt->ipaddr[i], &laddr, 4);
} else {
printf("ttl2tcp.@server[%d].ipaddr Not found!\n",i);
}
//读取port
//if ((strcpy(a, "ttl2tcp.@server[0].port") == NULL)
if (!snprintf(a,sizeof(a),"ttl2tcp.@server[%d].port",i)
|| (uci_lookup_ptr(ctx, &ptr, a, true) != UCI_OK)) {
printf("Read ttl2tcp.@server[%d].port failed! exit.\n",i);
exit(-1);
}
if (ptr.o) {
popt->port[i] = strtol(ptr.o->v.string, NULL, 10);
server_num++;
} else {
printf("ttl2tcp.@server[%d].port Not found!\n",i);
}
}
uci_free_context(ctx);
return server_num;
}
/**
* conn_nonb: 非阻塞connect
* @sockfd: socket文件描述符
* @saptr:指向数据结构sockaddr的指针,其中包括目的端口和IP地址
* @salen:sockaddr的长度
* @nsec:超时时间,单位ms
*/
int conn_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen, int nsec)
{
int flags, n, error, code;
socklen_t len;
fd_set wset;
struct timeval tval;
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
error = 0;
if ((n = connect(sockfd, saptr, salen)) == 0) { //马上连接成功,可能性小
printf("conn_nonb success!\n");
goto done;
}else if (n < 0 && errno != EINPROGRESS){ //多次连接或服务端没开启,出错 ,第一次一般执行不到该处。
printf("conn_nonb error!\n");
return (-1);
}
/* Do whatever we want while the connect is taking place */
//连接建立已经启动,但是尚未完成
FD_ZERO(&wset);
FD_SET(sockfd, &wset);
tval.tv_sec = nsec;
tval.tv_usec = 0;
//printf("conn_nonb select start!\n");
if ((n = select(sockfd+1, NULL, &wset, //有连接要处理
NULL, nsec ? &tval : NULL)) == 0) {
close(sockfd); /* timeout */
errno = ETIMEDOUT;
printf("conn_nonb select timeout!\n");
return (-1);
}
if (FD_ISSET(sockfd, &wset)) {
len = sizeof(error);
code = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
/* 如果发生错误,Solaris实现的getsockopt返回-1,
* 把pending error设置给errno. Berkeley实现的
* getsockopt返回0, pending error返回给error.
* 我们需要处理这两种情况 */
if (code < 0 || error) {
close(sockfd);
if (error)
errno = error;
printf("conn_nonb getsockopt error!\n");
return (-1);
}
} else {
fprintf(stderr, "select error: sockfd not set");
exit(0);
}
done:
fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
return (0);
}
void *socket_thread(void *arg)
{
char sockbuf[SOCK_MAXLINE];
int sreadlen = 0;
int freadlen = 0;
unsigned char i;
struct argument thread_arg;
thread_arg = *(struct argument *)arg;
memset(sockbuf, 0, sizeof(sockbuf));
while(1)
{
for (i=0; i<on_max;i++)
{
//pthread_mutex_lock(&socket_lock);//lock
sreadlen = socket_read(thread_arg.sockfd[i], sockbuf, SOCK_MAXLINE-8, 500);//为了防止缓存溢出,少读取一个字节
printf("the sockbuf is:%s\n", sockbuf);//打印出数据
printf("the sockbuf length is:%d\n",sreadlen);
freadlen = serial_send(thread_arg.fd,sockbuf,sreadlen);
printf("send %d bytes!\n",freadlen);
memset(sockbuf, 0, sizeof(sockbuf));
//pthread_mutex_unlock(&socket_lock);//unlock
usleep(1);
}
}
}
int main(int argc, char** argv)
{
//串口变量定义
int fd;
char str[]="hello linux serial!"; //字符串初始化
char serbuf[SER_MAXLINE];
int readlen;
char dev_path[20];
// socket变量定义
int sockfd[SERVER_MAXNUM];//SERVER_MAXNUM
struct sockaddr_in servaddr[SERVER_MAXNUM];//SERVER_MAXNUM
unsigned char i;
unsigned char on_sockfd[SERVER_MAXNUM] = {0};
on_max = 0;//最大上线个数
//多线程
pthread_t thread;
int mret;
struct argument arg;
//读取配置文件
unsigned char server_num;//服务器个数初始化
server_num = read_config(&opt);
if (opt.enabled != 1)
{
printf("do not enable ttl_client!\n");
exit(-1);
}
//串口初始化
if (!snprintf(dev_path,sizeof(dev_path),"/dev/%s",opt.name)
|| (fd = serial_init(dev_path,opt.baudrate,1)) < 0)
{
perror("serial init err:");
return -1;
}
memset(serbuf, 0, sizeof(serbuf));
//socket始化
for (i=0; i<server_num; i++)
{
printf("socket init %d/%d\n",i,server_num);
if( (sockfd[i] = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("create socket2 error: %s(errno: %d)\n", strerror(errno),errno);
exit(0);
}
memset(&servaddr[i], 0, sizeof(servaddr[i]));
servaddr[i].sin_family = AF_INET;
servaddr[i].sin_port = htons(opt.port[i]);
servaddr[i].sin_addr = opt.ipaddr[i];//与配置文件有关,注意配置文件要正确
//if( connect(sockfd[i], (struct sockaddr*)&servaddr[i], sizeof(servaddr[i])) < 0)
//非阻塞连接10s,如果前一个sockfd没有connect成功,则下次将建立一样的文件描述符号
if( conn_nonb(sockfd[i], (struct sockaddr*)&servaddr[i], sizeof(servaddr[i]),10) < 0)
{
printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
continue;
//exit(0);
}
on_sockfd[on_max++] = i;
printf("send msg to server[%d]: %d\n",i,on_max-1);//on_max-1为上线客户端的新编号
//socket发送
if( send(sockfd[i], str, strlen(str), 0) < 0)
{
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
}
//如果没有一个服务器上线
if (on_max == 0)
{
exit(0);
}
//锁
//pthread_mutex_init(&socket_lock, NULL);
//创建多线程
arg.fd=fd;
for (i=0; i<on_max;i++)
{
arg.sockfd[i]=sockfd[on_sockfd[i]];
}
mret = pthread_create(&thread, NULL, socket_thread, (void *)(long)&arg);
if (mret != 0)
{
printf("Create thread failed\n");
exit(mret);
}
printf("Create thread\n");
//串口转tcp
while(1)
{
readlen = serial_read(fd,serbuf,SER_MAXLINE,1000);//1s内如果数据没有装满buf,则读取完毕。 如果数据量大,则读取的速度也越快。
printf("the serbuf is :%s\n",serbuf);
printf("the serbuf length is :%d\n",readlen);
for (i=0; i<on_max;i++)
{
printf("sockfd[%d]:%d\n",i,sockfd[on_sockfd[i]]);
if( send(sockfd[on_sockfd[i]], serbuf, readlen, 0) < 0)//serbuf中有数据可以发送才会执行这条语句
{
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);//服务端断掉,则发送失败。
exit(0);
}
}
memset(serbuf, 0, sizeof(serbuf));
sleep(1);
}
//退出
close(fd);
for (i=0; i<server_num;i++)
close(sockfd[i]);
exit(0);
}