zoukankan      html  css  js  c++  java
  • linux下编程epoll实现将GPS定位信息上报到服务器

    操作系统:CentOS

    开发板:fl2440

    开发模块:A7(GPS/GPRS),RT3070(无线网卡)

    ****************************************************************************************************************************************************************************************

    前言:本博文实现的功能是:fl2440开发板运行客户端程序,将GPS的定位信息通过串口读取出来,然后将定位信息发送到服务器上。

    (当然服务器上跑的是自己编写的服务器端程序)这个就有点类似共享单车上面装的GPS定位系统,然后公司就可以根据其共享单车的地理位置信息进行定位管理。不过我这个只是一个小程序,功能比较单一,只是初步学习网络socket编程,不敢妄加定论,如有不对的地方,谢请指正。

    对于初次学习网络socket编程的人来说,了解epoll的原理是必要的,网上有大量文章介绍epoll的原理以及它的用法,所以本文不做过多的赘述,只是简单的分析。

    1.什么是epoll?

          我的理解是:epoll是linux网络编程多路复用中的一种新的事件触发机制,相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率,不过实现的原理大致相同,都是通过监听客户端套接字fd,也就是如果有多个客户端程序连接服务器,然后服务器端就将监听到的套接字fd存放在一个集合里,如果发现客户端套接字fd发生可读,可写,以及错误事件时,服务器端就进行相应的处理。不过epoll与poll及select不同的是,epoll采用的是基于事件的就绪通知方式。

      在select/poll中,进程只有在调用一定的函数后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

    epoll实现一共就三个函数:

    (1). int epoll_create(int size);

    创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

    (2).int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:

    EPOLL_CTL_ADD:注册新的fd到epfd中;

    EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

    EPOLL_CTL_DEL:从epfd中删除一个fd;

    第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事.

    struct epoll_event结构如下:

    typedef union epoll_data {

       void *ptr;

       int fd;

        __uint32_t u32;

        __uint64_t u64;

    } epoll_data_t;

    struct epoll_event {

        __uint32_t events; /* Epoll events */

        epoll_data_t data; /* User data variable */

    };

    events可以是以下几个宏的集合:

    EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

    EPOLLOUT:表示对应的文件描述符可以写;

    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

    EPOLLERR:表示对应的文件描述符发生错误;

    EPOLLHUP:表示对应的文件描述符被挂断;

    EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

    EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里.

    (3).int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

       等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

    2.如何使用epoll?

    通过在包含一个头文件#include <sys/epoll.h> 以及几个简单的API将可以大大的提高你的网络服务器的支持人数。

    首先通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为你epoll所支持的最大句柄数。

    这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。

    之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读

    哪一个可以写了。基本的语法为:

    nfds = epoll_wait(kdpfd, events, maxevents, -1);

    其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写

    件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件

    围,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则返回。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主

    辑在同一个线程的话,则可以用0来保证主循环的效率。epoll_wait返回之后应该是一个循环,遍利所有的事件。

    epoll简单分析之后,进入正题:

    一.前期准备
      (1).在测试程序之前我们得保证我们的开发板能够成功上网,当然我是直接在我的开发板上插上一块无线网卡,通过相应的命令连接路由器上网,前提是开发板要使能相应的驱动,具体怎么使能,及相应的操作命令是啥?我的这篇博客有总结,可以参考一下:点击打开链接
      (2).由于我的A7模块是同时具有GPS与GPRS功能,所以打开GPS需要通过AT+GPS=1命令开启,使用microcom命令监听串口,查看输入AT命令之后,串口是否会有OK回显,具体操作我的博客有总结,参考这篇博客:点击打开链接
      (3).硬件连线我使用的是两根USB转串口线,一根连接开发板,通过串口方式来连接开发板,一根连接GPS模块,当然也可以通过ssh代理远程登陆开发板,不过开发板能够与PC通信(两种方法:1.网线有线连接使其开发板与PC相连。2.插上无线网卡,开发板能上网,无线连接开发板)
    二.网络编程
    客户端代码:
    client.c:
    1. /*********************************************************************************
    2. * Copyright: (C) 2017 zoulei
    3. * All rights reserved.
    4. *
    5. * Filename: client.c
    6. * Description: This file
    7. *
    8. * Version: 1.0.0(2017年06月21日)
    9. * Author: zoulei <zoulei121@gmail.com>
    10. * ChangeLog: 1, Release initial version on "2017年06月21日 19时17分40秒"
    11. *
    12. ********************************************************************************/
    13. #include <sys/types.h>
    14. #include <sys/stat.h>
    15. #include <fcntl.h>
    16. #include <stdio.h>
    17. #include <string.h>
    18. #include <sys/types.h>
    19. #include <sys/socket.h>
    20. #include <arpa/inet.h>
    21. #include <unistd.h>
    22. #include <netinet/in.h>
    23. #include <errno.h>
    24. #include <stdlib.h>
    25. #include "gps.h"
    26. #define GPS_LEN 1024
    27. #define PORT 9997
    28. int set_serial(int fd,int nSpeed, int nBits, char nEvent, int nStop);
    29.  
    30. int main (int argc, char **argv)
    31. {
    32. int fd=0;
    33. int n=0;
    34. int i=0;
    35. int sockfd;
    36. int rec_len;
    37. GPRMC gprmc;
    38. char sendbuf[1024] ;
    39. char buff[GPS_LEN];
    40. char *str=NULL;
    41. char *dev_name="/dev/ttyUSB0";
    42. struct sockaddr_in servaddr;
    43. /*打开"/dev/ttyUSB0"设备*/
    44. if((fd=open(dev_name,O_RDWR|O_NOCTTY|O_NDELAY))<0)
    45. {
    46. perror("Can't Open the ttyUSB0 Serial Port");
    47. return -1;
    48. }
    49. set_serial( fd,9600,8,'N',1);//串口配置函数
    50. /* 判断命令端输入的参数是否正确 */
    51. if( argc != 2)
    52. {
    53. printf("usage: ./client <ipaddress> ");
    54. exit(0);
    55. }
    56. /* 创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/
    57. if(( sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
    58. {
    59. perror("socket");
    60. exit(0);
    61. }
    62. /* 初始化 */
    63. memset(&servaddr,0,sizeof(servaddr)); /* 数据初始化-清零 */
    64. servaddr.sin_family = AF_INET; /* 设置IPv4通信 */
    65. servaddr.sin_port = htons(PORT);/* 设置服务器端口号 */
    66. /* IP地址转换函数,将点分十进制转换为二进制 */
    67. if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
    68. {
    69. printf("inet_pton error for %s ",argv[1]);
    70. exit(0);
    71. }
    72. /* 将套接字绑定到服务器的网络地址上*/
    73. if( connect( sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))<0)
    74. {
    75. perror("connected failed");
    76. exit(0);
    77. }
    78. while(1)
    79. {
    80. sleep(2);
    81. /*读串口设备获取GPS定位信息*/
    82. if((n=read(fd,buff,sizeof(buff)))<0)
    83. {
    84. perror("read error");
    85. return -1;
    86. }
    87. /*将GPS定位信息发送到服务器端*/
    88. if(send(sockfd,buff,strlen(buff),0)< 0 )
    89. {
    90. printf("send the gps datas error:%s(errno: %d) ", strerror(errno), errno);
    91. exit(0);
    92. }
    93.  
    94. printf("read buff:%s ",buff);
    95.  
    96. }
    97. close(sockfd);
    98. close(fd);
    99. return 0;
    100. }
    串口配置程序及gps.h文件,在我上篇博客:点击打开链接
    makefile:
    1. CC=/opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc
    2.  
    3. objs=uart1.o client.o
    4. srcs=uart1.c client.c
    5.  
    6. client_test: $(objs)
    7. $(CC) -o client_test $(objs)
    8. @make clean
    9.  
    10. client.o: $(srcs) gps.h
    11. $(CC) -c $(srcs)
    12.  
    13. uart1.o: uart1.c
    14. $(CC) -c uart1.c
    15.  
    16. clean:
    17. rm *.o
    make编译之后生成client_test可执行文件,然后将其烧录到开发板。赋予可执行,可读,可写权限。
    服务器代码:
    sev.c:
    1. /*********************************************************************************
    2. * Copyright: (C) 2017 zoulei.
    3. * All rights reserved.
    4. *
    5. * Filename: sev.c
    6. * Description: This file
    7. *
    8. * Version: 1.0.0(06/22/2017)
    9. * Author: zoulei <zoulei121@gmail.com>
    10. * ChangeLog: 1, Release initial version on "06/22/2017 11:25:16 AM"
    11. *
    12. ********************************************************************************/
    13. #include <string.h>
    14. #include <stdio.h>
    15. #include <stdlib.h>
    16. #include <unistd.h>
    17. #include <sys/select.h>
    18. #include <sys/time.h>
    19. #include <sys/socket.h>
    20. #include <netinet/in.h>
    21. #include <arpa/inet.h>
    22. #include <sys/epoll.h>
    23. #include <errno.h>
    24. #define OPEN_MAX 100
    25.  
    26. typedef unsigned int UINT;
    27. typedef int BYTE;
    28.  
    29. typedef struct __gprmc__
    30. {
    31. UINT time;/* gps定位时间 */
    32. char pos_state;/*gps状态位*/
    33. float latitude;/*纬度 */
    34. float longitude;/* 经度 */
    35. float speed; /* 速度 */
    36. float direction;/*航向 */
    37. UINT date; /*日期 */
    38. float declination; /* 磁偏角 */
    39. char dd;
    40. char mode;/* GPS模式位 */
    41.  
    42. }GPRMC;
    43.  
    44. int gps_analyse (char *buff,GPRMC *gps_data)
    45. {
    46. char *ptr=NULL;
    47. if(gps_data==NULL)
    48. {
    49. return -1;
    50. }
    51. if(strlen(buff)<10)
    52. {
    53. return -1;
    54. }
    55. if(NULL==(ptr=strstr(buff,"$GPRMC")))
    56. {
    57. return -1;
    58. }
    59. sscanf(ptr,"$GPRMC,%d.000,%c,%f,N,%f,E,%f,%f,%d,,,%c*",&(gps_data->time),&(gps_data->pos_state),&(gps_data->latitude),&(gps_data->longitude),&(gps_data->speed),&(gps_data->direction),&(gps_data->date),&(gps_data->mode));
    60. return 0;
    61. }
    62.  
    63. int print_gps (GPRMC *gps_data)
    64. {
    65. printf(" ");
    66. printf(" ");
    67. printf("=========================================================== ");
    68. printf("== 全球GPS定位导航模块 == ");
    69. printf("== Author:zoulei == ");
    70. printf("== Email:zoulei121@gmail.com == ");
    71. printf("== Platform:fl2440 == ");
    72. printf("=========================================================== ");
    73. printf(" ");
    74. printf("=========================================================== ");
    75. printf("== GPS state bit : %c [A:有效状态 V:无效状态] ",gps_data->pos_state);
    76. printf("== GPS mode bit : %c [A:自主定位 D:差分定位] ", gps_data->mode);
    77. printf("== Date : 20%02d-%02d-%02d ",gps_data->date%100,(gps_data->date%10000)/100,gps_data->date/10000);
    78. printf("== Time : %02d:%02d:%02d ",(gps_data->time/10000+8)%24,(gps_data->time%10000)/100,gps_data->time%100);
    79. printf("== 纬度 : 北纬:%d度%d分%d秒 ", ((int)gps_data->latitude) / 100, (int)(gps_data->latitude - ((int)gps_data->latitude / 100 * 100)), (int)(((gps_data->latitude - ((int)gps_data->latitude / 100 * 100)) - ((int)gps_data->latitude - ((int)gps_data->latitude / 100 * 100))) * 60.0));
    80. printf("== 经度 : 东经:%d度%d分%d秒 ", ((int)gps_data->longitude) / 100, (int)(gps_data->longitude - ((int)gps_data->longitude / 100 * 100)), (int)(((gps_data->longitude - ((int)gps_data->longitude / 100 * 100)) - ((int)gps_data->longitude - ((int)gps_data->longitude / 100 * 100))) * 60.0));
    81. printf("== 速度 : %.3f m/s ",gps_data->speed);
    82. printf("== ");
    83. printf("============================================================ ");
    84.  
    85. return 0;
    86. }
    87. int main(int argc, char *argv[])
    88. { int max = 0 ;
    89. int i = 0 ;
    90. int len = 0 ;
    91. int sockfd ;
    92. int epfd ;
    93. int connfd ;
    94. int ret ;
    95. int fd[OPEN_MAX];
    96. char buff[512];
    97. GPRMC gprmc;
    98. struct epoll_event event; // 告诉内核要监听什么事件
    99. struct epoll_event wait_event; //内核监听完的结果
    100. struct sockaddr_in server_addr;
    101.  
    102. /* AF_INET 表示采用TCP/IP协议族 SOCK_STREAM 表示采用TCP协议 */
    103. if(( sockfd = socket(AF_INET, SOCK_STREAM, 0))<0)
    104. {
    105. perror("creat socket error");
    106. return -1;
    107. }
    108. memset(&server_addr,0,sizeof(server_addr));
    109. server_addr.sin_family = AF_INET;
    110. server_addr.sin_port = htons(9997);
    111. server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    112. /* 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程) */
    113. if(( bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)))<0)
    114. {
    115. perror("bind error");
    116. return -1;
    117. }
    118. /* 将socket设置为监听模式,10表示等待连接队列的最大长度 */
    119. if( listen(sockfd, 10) < 0)
    120. {
    121. perror("listen error");
    122. return -1;
    123. }
    124.  
    125. memset(fd,-1, sizeof(fd));
    126. fd[0] = sockfd;
    127. epfd = epoll_create(10); // 创建一个 epoll 的句柄,参数要大于 0, 不然没有太大意义
    128. if( -1 == epfd )
    129. {
    130. perror ("epoll_create error");
    131. return -1;
    132. }
    133.  
    134. event.data.fd = sockfd; //监听套接字
    135. event.events = EPOLLIN; // 表示对应的文件描述符可以读
    136. /*事件注册函数,将监听套接字描述符 sockfd 加入监听事件 */
    137. if(( ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event)) == -1)
    138. {
    139. perror("epoll_ctl");
    140. return -1;
    141. }
    142.  
    143. while(1)
    144. {
    145. /* 监视并等待多个文件描述符的属性变化(是否可读)
    146. 没有属性变化,这个函数会阻塞,直到有变化才往下执行,这里没有设置超时.*/
    147. ret = epoll_wait(epfd, &wait_event, max+1, -1);
    148. /*监测sockfd(监听套接字)是否存在连接 */
    149. if(( sockfd == wait_event.data.fd ) && ( EPOLLIN == wait_event.events & EPOLLIN ))
    150. {
    151. struct sockaddr_in cli_addr;
    152. int clilen = sizeof(cli_addr);
    153. /* 从tcp完成连接中提取客户端*/
    154. if(( connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen)) < 0)
    155. {
    156. perror("accept faild");
    157. return -1;
    158. }
    159. /* 将提取到的connfd放入fd数组中,以便下面轮询客户端套接字 */
    160. for(i=1; i<OPEN_MAX; i++)
    161. {
    162. if(fd[i] < 0)
    163. {
    164. fd[i] = connfd;
    165. event.data.fd = connfd; //监听套接字
    166. event.events = EPOLLIN; // 表示对应的文件描述符可以读
    167.  
    168. /* 事件注册函数,将监听套接字描述符 connfd 加入监听事件 */
    169. ret = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &event);
    170. if(-1 == ret)
    171. {
    172. perror("epoll_ctl");
    173. return -1;
    174. }
    175. break;
    176. }
    177. }
    178. /* max更新 */
    179. if(i > max)
    180. max = i;
    181. /* 如果没有就绪的描述符,就继续epoll监测,否则继续向下看*/
    182. if(--ret <= 0)
    183. continue;
    184.  
    185. }
    186. for(i=1; i<=max; i++)
    187. {
    188. if(fd[i] < 0)
    189. continue;
    190.  
    191. if(( fd[i] == wait_event.data.fd ) && ( EPOLLIN == wait_event.events & (EPOLLIN|EPOLLERR) ))
    192. {
    193. /*接受客户端数据 */
    194. if((len = recv(fd[i], buff, sizeof(buff), 0)) < 0)
    195. {
    196. if(errno == ECONNRESET)//tcp连接超时、RST
    197. {
    198. close(fd[i]);
    199. fd[i] = -1;
    200. }
    201. else
    202. perror("read error:");
    203. }
    204. else if(len == 0)//客户端关闭连接
    205. {
    206. close(fd[i]);
    207. fd[i] = -1;
    208. }
    209. else //正常接收到客户端的数据
    210. buff[len]='';
    211. printf("receive the data:%s ",buff);
    212. memset(&gprmc, 0 , sizeof(gprmc));
    213. gps_analyse(buff,&gprmc);
    214. print_gps(&gprmc);
    215. /*所有的就绪描述符处理完了,就退出当前的for循环,继续epoll监测 */
    216. if(--ret <= 0)
    217. break;
    218. }
    219. }
    220. }
    221. close(sockfd);
    222. close(epfd);
    223. return 0;
    224. }
    测试结果:
    客户端(开发板):
    服务器端:

  • 相关阅读:
    js之展开收缩菜单,用到window.onload ,onclick,
    根据python上下文管理,写一个在读文件内容前后自动打开关闭文件的程序
    自定义高级版python线程池
    htmlParser的使用-链接
    shell单例-处理方案
    shell脚本默认变量值
    shell文件相关指令
    linux常用端口
    hbase离线定时入库shell脚本-小栗子
    shell 查看系统有关信息
  • 原文地址:https://www.cnblogs.com/wanghuaijun/p/9338348.html
Copyright © 2011-2022 走看看