《嵌入式课程设计(共40页).doc》由会员分享,可在线阅读,更多相关《嵌入式课程设计(共40页).doc(40页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、精选优质文档-倾情为你奉上linux TCP服务器/客户端通信程序摘要:随着计算机网络的不断发展,网络编程变得越来越重要,除了简单的WEB编程外,还包括利用套接字(Socket)进行客户/服务器应用程序的设计。本文先对与套接字相关的概念和函数作了一般性介绍,并提出多线程的编程方法和设计流程,也就具体的工程实例进行了流程分析。本文中,对计算机的网络模型进行了简要的分析,并对TCP的握手模型进行了概述;在多线程编程中,本文详细分析了多线程的互斥模型,讲解了多种线程之间的同步方法,并在程序设计中得到体现,详细讲述了Linux中的TCP服务器/客户端通信程序,并对结果进行了验证。关键字:网络编程 ;多
2、线程;套接字目 录专心-专注-专业绪论Linux经历了20多年的发展,已经成为了一个功能强大而稳定的操作系统,在嵌入式系统中也得到广泛的运用,伴随着物联网技术的普及,网络通信在嵌入式系统中扮演着举足轻重的作用。1. 课程背景随着时代的发展,网络通信在我们的生活中愈来愈重要,在互联网技术基础上延伸和扩展来的物联网技术,正逐渐改变着我们的世界。互联网在现实生活中的应用很广泛,互联网给我们的现实生活带来了很大的方便;互联网是全球性的,这也就意味着我们能够打破时空的界限,通过互联网接触到世界的每一个角落;因为互联网的强大力量,这个时代的文明发展得到极大地提高。2. 选题的目的和意义由于互联网超乎寻常的
3、强大力量,改变了这个时代的交流方式,改变着人们的生活,未来,我们还将在互联网领域得到更多的进步,会影响生活中的方方面面。3. 国内外研究现状互联网从诞生至今,让人类文明得到巨大的推动,伴随着互联网的发展,各种依托互联网的技术得到迅速发展,Linux操作系统依据其优良的性能和网络功能,在各个领域都得到极大的普及。21世纪,是互联网发展的有一个阶段,我们国家已经将互联网的发展提升到了战略高度,明确表示要建成互联网强国,我国到目前为止,已经诞生了一大批优秀的互联网企业,全世界都将在互联网的推动下,进入一个全新的时代。4. 主要研究内容设计TCP服务器程序,使用多线程实现”生产者-消费者“模型,建立T
4、CP服务器,响应客户端请求,发送客户端指定的请求数据。主要包括以下内容:(1) 创建线程持续产生数据,数据包含(学号,姓名(拼音),年龄,身高,体重,当前系统时间(纳秒数)使用gettimeofday(),可使用队列/多维数组存储数据;(2) 创建TCP服务器线程,响应多个客户端的连接,读取队列/数组,向客户端发送指定“学号”的数据。设计TCP服务器程序;(3) 创建TCP客户端接收线程,连接服务器并请求指定“学号”的数据,接收数据并存储在文件中。要求存储有意义的数据,由于TCP是基于字节流的特征,需要做组包处理;第1章 需求分析1.1 设计目的通过对专业知识的熟练运用,理解Linux网络编程
5、的流程,了解互联网的基本架构,熟悉多线程编程的思想。同时,通过本课程设计,可以培养以下能力:(1) 独立工作能力与创造力;(2) 综合运用专业及基础知识的能力;(3) 解决实际工程技术问题的能力;(4) 查阅图书资料、产品手册和各种工具书的能力;(5) 书写技术报告和编制技术资料的能力。1.2 课题要求使用多线程实现”生产者-消费者“模型,建立TCP服务器,响应客户端请求,发送客户端指定的请求数据。1.3 任务分析创建线程持续产生数据,数据包含(学号,姓名(拼音),年龄,身高,体重,当前系统时间(纳秒数)使用gettimeofday),可使用队列/多维数组存储数据。理解常用的数据结构,熟练掌握
6、C编程语言。创建TCP服务器线程,响应多个客户端的连接,读取队列/数组,向客户端发送指定“学号”的数据,设计TCP服务器程序,掌握网络编程中服务器端的编程流程。创建TCP客户端接收线程,连接服务器并请求指定“学号”的数据,接收数据并存储在文件中。要求存储有意义的数据,由于TCP是基于字节流的特征,需要做组包处理。掌握网络编程中客户端的编程流程。最终的目的是熟练掌握网络编程的编程方法,理解常用的数据结构的基本思想,掌握编程语言,理解多线程编程在实际工程中的应用。第2章 环境搭建2.1 Ubuntu系统安装考虑到Windows系统的普及程度,本课程实际将利用虚拟机来进行开发,首先我们需要搭建虚拟机
7、开发环境。1. 创建虚拟机图2.1.12. 选择操作系统 图2.1.23.配置处理器和内存图2.1.3图2.1.44. 安装系统图2.1.55. 安装成功界面图2.1.62.2 开发环境搭建2.2.1 NFS环境介绍NFS(Network File System)即,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源。在NFS的应用中,本地NFS的客户端应用可以透明地读写位于远端NFS服务器上的文件,就像访问本地文件一样。2.2.2 NFS安装1.NFS是网络文件系统系统的缩写,可以用于Linux和Linux之间传递文件,实现数据共享。安装命令如下:
8、apt-get install nfs-kernel-server2.修改配置文件打开/etc/exports文件,增加mount -t nfs/NFS (rw,sync,no_root_squash,no_subtree_check)开发板和其他 Linux 主机可以通过网络访问/NFS 目录。3.启动NFSsudo service rpcbind start sudo service nfs-kernel-server start2.2.3 挂载NFS文件系统mount -t nfs -o intr,nolock,rsize=1024,wsize=1024 192.168.1.86:/op
9、t/ /mnt2.2.4 交叉工具安装1.在/usr/local/下建立交叉编译器的安装目录arm:sudo mkdir /usr/local/arm2.将下载的交叉编译器包解压到/usr/local/arm目录下:sudo tar jxvf cross-4.2.2-eabi.tar.bz2 -C /usr/local/arm/3.解压成功后,修改PATH环境变量:sudo vim /etc/profile在文件为加入交叉编译器arm-linux-所在的路径:export PATH=$PATH:/usr/local/arm/4.2.2-eabi/usr/bin4.更新一下配置文件/etc/pr
10、ofile:source /etc/profile第3章 软件设计3.1 TCP/IP协议3.1.1 网络模型 图 3-1-1 网络模型如图3-1-1所示,在TCP/IP协议中,将互联网划分成为应用层、传输层、网络层、网络接口层,其中网络接口层的主要功能是提供二进制传输和介质访问的功能;网络层负责IP寻址和路由,其中要考虑路由算法,拥塞控制等问题;传输层负责应用程序之间的连接;3.1.2 TCP连接TCP IP一般通过internet串行线路协议SLIP或点对点协议PPP在串行线上进行数据传送。TCP/IP协议的基本传输单位是数据包 (datagram)。TCP协议负责把数据分成若干个数据包/
11、段,并给每个数据包加上包头,IP协议在每个包头上再加上接收端主机地址,这样数据找到自己要去的地方。如果传输过程中出现数据丢失、数据失真等情况,TCP协议会自动要求数据重新传输并重新组包。TCP协议保证数据传输的质量,IP协议保证数据的传输。数据在传输时每通过一层就要在数据上加个包头,其中数据供接收端同一层协议使用,而在接收端每经过一层要把用过的包头去掉,这样来保证传输数据的格式完全一致。TCP/IP协议需要针对不同的网络进行不同的设置,且每个节点一般需要一个“IP地址”、一个“子网掩码”、一个“默认网关”。不过可以通过动态主机配置协议(DHCP),给客户端自动分配一个IP地址,这样避免了出错也
12、简化了TCP/IP协议的设置。如图3-1-2所示,TCP是通过3次握手建立的:1. 客户端给服务器发送SYN(syn = j)包,进入SYN_SEND状态。2. 服务器接收到SYNC包,确认客户的SYN(ack = j+1),同时自己也发送一个SYN包(syn = k),把它俩都发送出去,服务器进入SYN_SEND状态。3. 客户端收到服务器的SYN+ACK包,向服务器发送ACK(ack = k+1),客户端和服务器都进入ESTABLISHED状态。此时,连接已经建立完毕,可以相互发送发送消息。图 3-1-2 三次握手示意图3.2 多线程编程进程是系统中程序执行和资源分配的基本单位。每个进程都
13、拥有自己的数据段、代码段和堆栈段,这就造成了进程在进行切换等操作时都需要有比较复杂的上下文切换等动作。为了进一步减少处理机的空转时间,支持多处理器以及减少上下文切换开销,进程在演化中出现了另一个概念线程。它是进程内独立的一条运行路线,处理器调度的最小单元,也可以称为轻量级进程。线程可以对进程的内存空间和资源进行访问,并与同一进程中的其他线程共享。因此,线程的上下文切换的开销比创建进程小很多。同进程一样,线程也将相关的执行状态和存储变量放在线程控制表内。一个进程可以有多个线程,也就是有多个线程控制表及堆栈寄存器,但却共享一个用户地址空间。要注意的是,由于线程共享了进程的资源和地址空间,因此,任何
14、线程对系统资源的操作都会给其他线程带来影响。由此可知,多线程中的同步是非常重要的问题,以下是线程同步用到的一些方法:1. 互斥锁互斥锁是用一种简单的加锁方法来控制对共享资源的原子操作。这个互斥锁只有两种状态,也就是上锁和解锁,可以把互斥锁看作某种意义上的全局变量。在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。可以说,这把互斥锁保证让每个线程对共享资源按顺序进行原子操作。互斥锁可以分为快速互斥锁、递归互斥锁和检错互斥锁。这三种锁的区别主要在于其他未占有互斥锁的线程在希望
15、得到互斥锁时是否需要阻塞等待。快速锁是指调用线程会阻塞直至拥有互斥锁的线程解锁为止。递归互斥锁能够成功地返回,并且增加调用线程在互斥上加锁的次数,而检错互斥锁则为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。互斥锁机制主要包括下面的基本函数:1) 互斥锁初始化:pthread_mutex_init()2) 互斥锁上锁:pthread_mutex_lock()3) 互斥锁判断上锁:pthread_mutex_trylock()4) 互斥锁解锁:pthread_mutex_unlock()5) 消除互斥锁:pthread_mutex_destroy()2. 信号量信号量也就是操作系统中所
16、用到的PV原子操作,它广泛用于进程或线程间的同步与互斥。信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。PV原子操作是对整数计数器信号量sem的操作。一次P操作使sem减一,而一次V操作使sem加一。进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限。当信号量sem的值大于等于零时,该进程(或线程)具有公共资源的访问权限;相反,当信号量sem的值小于零时,该进程(或线程)就将阻塞直到信号量sem的值大于等于0为止。信号量机制主要包括下面的基本函数:1) 创建信号量: sem_init()2) 等待信号量:sem_wait()和sem_trywait()3) 唤醒进程
17、:sem_post()4) 获取信号量: sem_getvalue()5) 删除信号量: sem_destroy()3.3 Socket网络编程模型在Linux中的网络编程是通过socket接口来进行的。socket是一种特殊的I/O接口,它也是一种文件描述符。它是一种常用的进程之间通信机制,通过它不仅能实现本地机器上的进程之间的通信,而且通过网络能够在不同机器上的进程之间进行通信。源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务,它是网络通信过程中端点的抽象表示,包含进行网络通信必需的五种信息:连接使用的协议,本地主机的IP地址,本地进程的
18、协议端口,远地主机的IP地址,远地进程的协议端口。3.3.1 TCP Server编程模型设置为监听状态进行版本协商创建套接字接受客户端的连接请求接收或者发送数据关闭套接字图 3-2-1 Server编程模型1) 进行版本协商(WSAStartup)2) 创建一个套接字(socket()3) 将套接字设为监听状态(listen()4) 接受客户端的连接请求(accept()5) 发送或者接收数据(send()/recv()6) 关闭套接字(close()3.3.2 TCP Client编程模型连接到服务器进行版本协商创建套接字接收或者发送数据关闭套接字图 3-2-2 Client编程模型1)
19、进行版本协商(WSAStartup)2) 创建一个套接字(socket()3) 连接到服务器(connect()4) 发送或者接收函数(send()/recv()5) 关闭套接字(close()3.4 程序设计3.4.1 主要内容设计TCP服务器程序,使用多线程实现”生产者-消费者“模型,建立TCP服务器,响应客户端请求,发送客户端指定的请求数据。1.创建线程持续产生数据,数据包含(学号,姓名(拼音),年龄,身高,体重,当前系统时间(纳秒数)使用gettimeofday),可使用队列/多维数组存储数据。2.创建TCP服务器线程,响应多个客户端的连接,读取队列/数组,向客户端发送指定“学号”的数
20、据。设计TCP服务器程序。3.创建TCP客户端接收线程,连接服务器并请求指定“学号”的数据,接收数据并存储在文件中。要求存储有意义的数据,由于TCP是基于字节流的特征,需要做组包处理3.4.2 服务器端程序设计以下是服务器端程序的主函数部分,在主函数中,首先创建了一个新的线程,然后按照网络编程模型中服务器端的编程方法进行了编程,详细程序设计请参考附件1.void main(void)/*子线程相关*/pthread_t reader = -1; /read进程的进程号pthread_mutex_init(&mutex,NULL); /初始化 互斥锁/*初始化数据*/int i = 0;for(
21、i=0; i5; i+)informationi.name = NAMEi;informationi.age = AGEi;informationi.number = NUMi;informationi.high = HIGHi;informationi.weigh = WIGHTi; /*创建线程,产生数据*/pthread_create(&reader,NULL,(void*)&writer_function,NULL); /*主线程相关*/创建socketnListenSock =socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);if(nListenSock
22、0)printf(create listen socket errorn);return;/设置socket选项int nValue=1;if(setsockopt(nListenSock,SOL_SOCKET,SO_REUSEADDR,(char*)&nValue,sizeof(int)0)printf(set option SO_REUSEADDR fail!n);close(nListenSock);return;/绑定struct sockaddr_in localAddr;memset(&localAddr,0x0,sizeof(localAddr);localAddr.sin_fa
23、mily =AF_INET;localAddr.sin_addr.s_addr =htonl(INADDR_ANY);localAddr.sin_port =htons(10000);if(bind(nListenSock,(struct sockaddr*)&localAddr,sizeof(struct sockaddr)0)printf(bind liste sock fail!n);close(nListenSock);return;/监听if(listen(nListenSock,5)0)printf(listen error!n);close(nListenSock);return
24、;/创建客户端的线程int nSrvThreadId =1;nThreadFlag =1;if(pthread_create(pthread_t*)&nSrvThreadId,NULL,(void*)&serverThreadProc,NULL)0)printf(create server thread fail!n);close(nListenSock);return;/等待退出while(1)if(pthread_join(nSrvThreadId,NULL)!=0)return;3.4.3 客户端程序设计以下是客户端的程序设计,代码片段太过冗长,详细的程序设计请参考附件2。void ma
25、in(int argc, char* argv)/创建套接字int sockfd=-1;sockfd =socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);if(sockfd0)printf(socket create errorn);return;./建立连接while(1) /建立链接if(connect(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr)=0)printf(connectedn);break;sleep(1);./创建交互进程pthread_t ipt_id;if(pthread_cr
26、eate(pthread_t *)&ipt_id,NULL,(void*)&input,NULL)0)printf(create input thread fail!n);close(ipt_id);return;.第4章 综合测试4.1 功能测试 1.运行服务器端程序,处于监听状态,等待客户端来连接,当有客户端连接上,输出连接的客户端的信息。图 4-1-1 服务器运行2.运行客户端程序,等待用户输入要从数据库中读取的信息编号,当用户输入要读取的信息标号的时候,服务器响应客户端的请求,回复信息。图 4-1-2 客户端运行3.在当前目录下生成一个.txt文件,内部包含客户端请求的信息。图 4-1
27、-3 生成文件4.打开文件,内部为请求的信息。图 4-1-4 文件内容第5章 结论本次课程设计的内容是基于Linux操作系统的多线程网络编程,实现的功能是“生产者”,“消费者”模型,建立TCP服务器,响应客户端请求,并发送客户端请求的数据。在程序的设计过程中采用了多线程的编程方式,显著提高了程序运行的效率。客户端与服务器通过TCP方式建立连接,使用的通讯函数接口为套接字,套接字在网络编程中有着举足轻重的地位。通过本次课程设计,我们创建TCP服务器线程,响应多个客户端的连接,读取队列/数组,向客户端发送指定“学号”的数据;创建TCP客户端接收线程,连接服务器并请求指定“学号”的数据,接收数据并存
28、储在文件中;在实现的这个过程中,利用数据结构中的队列构造了数据表,方便程序访问,同时,也方便服务器端对数据的管理。通过本次课程设计,掌握了Linux下的编程模式和编程方法,熟悉了Linux的基本操作;同时,掌握了开发环境的搭建,常用的软件服务的安装,锻炼了实际的工程能力;通过多线程编程方法,理解了线程和进程的区别和联系,掌握了创建线程和注销线程的方法;通过对套接字的使用,掌握了在Linux下基于套接字的网络编程,理解了Linux下套接字编程在服务器端和客户端的编程流程,了解了网络模型,提高了解决问题的能力。参考文献1 范展源.深度实践嵌入式Linux系统移植.北京:机械工业出版社,2015.5
29、2 宋宝华.Linux设备驱动开发详解.北京:机械工业出版社,2015.73 谭浩强.C程序设计.北京:清华大学出版社,2010.64 陈文智.嵌入式系统设计与原理.北京:清华大学出版社,2011.55 宋敬彬.Liunx网络编程.北京:清华大学出版社,2010.6附录一 服务器端程序/*文件名: server.c文件描述: 嵌入式课程设计程序完成日期:2017年9月8日作者:陈凯联系方式:*/#include #include #include #include #include #include #include #include/*函数申明*/void writer_function(v
30、oid);void serverThreadProc(void *);/链表结点结构体typedef struct _CLIENT_INFO_charszClientIp16;int nClientPort;intnClientSock;struct_CLIENT_INFO_ *pNext;struct_CLIENT_INFO_ *pPre;CLIENT_INFO;/*数据类型*/typedef struct datatype unsigned char number;unsigned char age;unsigned char high;unsigned char weigh;long t
31、ime;char *name;Datatype;/*协议包结构*/typedef struct pro_packagechar head; /开始标志为 设定为0x7E 1int lenth; /包的数据部分长度 4unsigned char flag; /0 : cmd 1:data 1unsigned char stop; /0 stop 1:send 1unsigned char num; /标明发送信息 1unsigned char data200; /数据部分 Pro_package;/*创建缓存区*/typedef struct queueDatatype buffer5; int
32、 b_tail;int b_head;queue; /*初始化队列*/queue Queue = .b_tail = 0,.b_head = 0,;/*数据初始化*/char *NAME5 = wuhao, chenkai,liumenglin,liujin,liufeng;unsigned char AGE5 = 20,21,22,23,24;unsigned char NUM5 = 1,2,3,4,5;unsigned char HIGH5 = 175,174,173,175,165;unsigned char WIGHT5 = 58, 55, 62, 63, 50;Datatype in
33、formation5;/*全局变量*/int nListenSock =-1; /server lisen socketint nThreadFlag =0; / thread start/stop flagCLIENT_INFO *pClientHead=NULL; /client list headerstruct timeval sys_time;pthread_mutex_t mutex;int buffer_has_item=0;/主函数void main(void)/*子线程相关*/pthread_t reader = -1; /read进程的进程号pthread_mutex_in
34、it(&mutex,NULL); /初始化 互斥锁/*初始化数据*/int i = 0;for(i=0; i5; i+)informationi.name = NAMEi;informationi.age = AGEi;informationi.number = NUMi;informationi.high = HIGHi;informationi.weigh = WIGHTi; /*创建线程,产生数据*/pthread_create(&reader,NULL,(void*)&writer_function,NULL); /*主线程相关*/创建socketnListenSock =socket
35、(AF_INET,SOCK_STREAM,IPPROTO_TCP);if(nListenSock0)printf(create listen socket errorn);return;/设置socket选项int nValue=1;if(setsockopt(nListenSock,SOL_SOCKET,SO_REUSEADDR,(char*)&nValue,sizeof(int)0)printf(set option SO_REUSEADDR fail!n);close(nListenSock);return;/绑定struct sockaddr_in localAddr;memset(&
36、localAddr,0x0,sizeof(localAddr);localAddr.sin_family =AF_INET;localAddr.sin_addr.s_addr =htonl(INADDR_ANY);localAddr.sin_port =htons(10000);if(bind(nListenSock,(struct sockaddr*)&localAddr,sizeof(struct sockaddr)0)printf(bind liste sock fail!n);close(nListenSock);return;/监听if(listen(nListenSock,5)0)
37、printf(listen error!n);close(nListenSock);return;/创建客户端的线程int nSrvThreadId =1;nThreadFlag =1;if(pthread_create(pthread_t*)&nSrvThreadId,NULL,(void*)&serverThreadProc,NULL)nClientSock =nConnectSock;pTcpClient-nClientPort =ntohs(clientAddr.sin_port);strcpy(pTcpClient-szClientIp,inet_ntoa(clientAddr.si
38、n_addr);pTcpClient-pNext =NULL;pTcpClient-pPre =NULL;printf(client connected,ip:%s,port:%dn,pTcpClient-szClientIp,pTcpClient-nClientPort);if(pClientHead=NULL)pClientHead= pTcpClient;elsepTempClient =pClientHead;while(pTempClient-pNext!=NULL)pTempClient =pTempClient-pNext;pTempClient-pNext =pTcpClient;pTcpClient-pPre =pTempClient;/update fd setFD_SET(nConnectSock,&allset);if(nConnectSocknMaxFd)nMaxFd =nConnectSock;else /对客户端的服务/find the client pTcpClient =pClientHead;while(pTcpClie