基于Linux平台的网络聊天软件的设计与研究_第1页
基于Linux平台的网络聊天软件的设计与研究_第2页
基于Linux平台的网络聊天软件的设计与研究_第3页
基于Linux平台的网络聊天软件的设计与研究_第4页
基于Linux平台的网络聊天软件的设计与研究_第5页
已阅读5页,还剩49页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

南京工程学院毕业设计说明书(论文)南京工程学院毕业设计说明书(论文)院系:通信工程学院专业:通信工程(电力通信)题目:基于Linux平台的网络聊天软件的设计与研究指导者: 评阅者:2015年6月南京DesignandimplementationasoftwarewhichcancommunicateonLinuxADissertationSubmittedtoNanjingInstituteofTechnologyADissertationSubmittedtoNanjingInstituteofTechnologyFortheAcademicDegreeofBachelorofScienceSupervisedbyLecturerLeiTangCollegeofNanjingInstituteofTechnologyJune2015摘要Linuxkernel由于其代码的开源及获取方便等优点,越来越受到广大技术人员的青睐。Linux系统提供了完整的内核源代码,在此代码的基础上可以进行自定义,从而打造属于自己的“专属”系统。Socket通讯使用C/S模型,Socket上TCP/IP网络应用程序接口(API),提供了很多函数方开发人员利用。Socket通讯有两种传输协议:TCP协议和UDP协议,TCP协议是面向连接的,传输可靠的;UDP是无连接的,传输不可靠的,因此本设计中选用TCP。进程和线程是不同的,进程拥有自身的程序映像和地址空间,是内核调度的基本单元,每个单独的进程都有自己的代码段、数据段以及堆栈,它们使用自己的虚拟地址空间,多个进程间互不影响。而线程没有自己独立的地址空间,创建出来的新线程和创建它的进程共享一个虚拟地址空间。这些知识点将在论文中详细介绍。本设计主要对服务端和客户端程序进行编写并对其进行调试,从而实现客户端和服务端之间的通信。总的来讲,本设计使用C语言开发,通过Socket建立连接,并创建多个线程的实现多任务。关键词:Linux内核;socket;进程;线程;多任务AbstractBecauseofmeritinopeningofsourcecodeandgettingconvenient,linuxkernelbecomemoreandmorepopularamongtheprogrammers.LinuxprovidesintegratedsourcecodeofLinux-kernel,peoplecanmakespecialsystembelongstothemselvesbasedonthesourcecode. SocketcommunicationuseC/Smodel,andtheAPIofTCP/IPonitprovidesplentyoffunctionsforprogrammerstouse.Socketcommunicationhastwotransferprotocols:TCPandUDP;TCPisconnectionandreliable,butUDPisconnectionlessandunreliable.So,wechooseTCPinthisdesign. Processisdifferentfromthread.Processhasitproceduremapandaddressspace,it‘sbasicunitofthekernelscheduling,eachsingleprocesshasitsowntext、dataandstack,theyusetheirsvirtualaddressspaceanddon’taffecteachother.Butthreaddoesn’thaveindependentaddressspace,itsharesthevirtualaddressspacewiththeprocesswhocreatesit.Thisknowledgewillbeintroducedinmythesis. Thisdesignrealizescommunicationbetweenclientandserverbydebugginginclientandserver’scode.Ingeneral,thisdesignusesClanguage,connectsbysocketandcreatesmultiplethreadstoimplementmultitasking.Keywords:Linux-kernel;socket;process;thread;multitask目录第一章绪论 11.1设计背景 11.2应用概述 21.3 为什么选择Linux 41.4论文的主要工作 5第二章Linux编程常用工具 62.1嵌入式C语言 62.2GCC工具链 72.2.1GCC的用法 82.2.2调试和剖析选项 82.3GDB调试器 92.3.2GDB用法 92.3.1用gdb调试GCC程序 10第三章设计的实现及主要技术 133.1实现分析 133.1.1功能分析 133.1.2设计分析 133.1.3技术分析 143.3Socket(网络套接字) 183.3.1编程步骤 193.3.2地址及顺序地址 193.3.3链接函数说明 223.4进程 253.4.1进程的概念 253.4.2进程的内存布局 263.4.3进程生命周期 273.5多线程 283.5.1线程的概念 283.5.2进程和线程的关系及优缺点 283.5.3数据类型及其用法 303.6线程同步 313.6.1概念 313.6.2线程同步的几种方法:信号量、自旋锁、互斥量 323.6.3互斥量的使用 323.7ncurrses字符界面 333.7.1ncurses简介 333.7.2ncurses常用函数介绍 34第四章设计的编程实现 364.1聊天室Socket编程连接 364.2聊天室程序设计及界面设计 384.2.1服务器 384.2.2客户端 384.2.3客户端主体界面及功能展示 41第五章总结与展望 445.1全篇总结 445.2设计中遇到的问题及解决办法 445.3设计有待改进的地方 46致谢 47参考文献 48第48页第一章绪论1.1设计背景 Linux是兼容POSIX的U-like操作系统,大多数重要的UNIX工具、应用程序和网络协议都能在其上运行。Linux系统目前已经得到了很广泛的应用。它是由芬兰人LinuxTorvalds开发的。 Linux最初是为intel_x86平台开发的免费操作系统,从那时开始Linux就被陆续移植到其他硬件工作平台。在服务器方面Linux操作系统一直处于领先地位,比如大型机和超级计算机,但在桌面用户市场只占到了1.5%。Linux也可以运行在嵌入式设备之中,这些设备主要包括移动电话、平板电脑、网络路由、自动控制设备和电视机等等。Android是Linux操作系统在平板和智能电话中用的最为广泛的一款操作系统构建与LinuxKernel上层。 基于代码开源的优势,linux具有强大市场竞争力。只要想获得源码,便可从官方网站上获取LinuxKernel源代码,有了源代码之后就可以自定义操作系统了。在此基础上安装自己的应用软件,至此一个完整的属于自己的操作系统就诞生了。通常将这样安装所获得的系统称为Linux系统,这是因为它包含的不止一个内核。 提及Linux,那就得提到GNU和Unix。1983年9月27号,RichardStallman希望开发一套完整的开放源码操作系统以取代Unix,从而诞生了GNU。1985年,发表GNU宣言;1989年,发表GNU(GNUGneralPublicLicense,GPL);GPL的开源思想使得我们的学习之路顺畅了许多。GPL保障了Linux不仅仅是当下自由可用,而且今后经过任何修改后还是自由可用的,这点充分体现了Stallman的初衷。 Unix是于1969年在美国AT&T公司贝尔实验室开发出来的具有多任务,多用户的操作系统。Unix的前身叫做Multics,由BELLLabs参与研发,可是由于进度缓慢,BellLabs不得不放弃了这个计划。在这之后就由贝尔实验室的职员Kenthompson,DennisRitchie等继续自行开发。在此之后的10年里,Unix在大型企业和学术界得到了普遍的应用,其不断扩大的影响力终究还是引起了AT&T的关注,就这样一场持续许久的版权官司开始了,直到AT&T将自己的贝尔实验室卖给Novell接手。最初的UNIX是用PDP-7汇编语言别写的,但因为其在系统编程方面没有太大优势,于是Thompson和Ritchie两人对其加以改造,并于1971年共同发明了C语言。1973年thompson和Ritchie用C语言重写了UNIX,UNIX第三版就这样问世了。在当时,系统程序大多是由汇编语言编写,thompson和Ritchie此举是非常具有革新意义的。用C语言编写的Unix代码紧凑简洁、易读、易修改,为此后Unix的进一步发展奠定了扎实的基础。1.2应用概述 嵌入式Linux是指运行在分布式嵌入式设备上的Linux操作系统,尽管“embedded”这一词经常用来讨论内核方面的知识,但嵌入式应用上的Linuxkernel并没有什么特别之处。Linux内核都是一样的,只不过在不同的嵌入式设备上需要进行不同的编译步骤,从而让Linux操作系统能够在嵌入式设备上跑起来。嵌入式操作系统就是将Linux内核和其他各种软件编译成的能够运行在嵌入式分布式设备中的系统。你得通过付款的方式获得这特定的编译工具,这些编译工具通常指:交叉编译器、调试器、项目管理软件、引导镜像等等,从而得到完整的一个运行在嵌入式设备上的操作系统。随着网络的不断发展,网络在嵌入式系统中应用十分广泛,越来越多的嵌入式设备均采用Linux操作系统。传统的嵌入式系统设备如:空间站、自动系统、消费电子系统、电话等等都使用Linux系统,但这些嵌入式Linux系统大体上都相同,没有什么新颖的特色。而且这些都没有提及到系统的结构,Linux系统的结构真正信息包括:大小、实时、网络能力、和用户的交互能力。Linux系统的选型非常重要,针对不同用户的需求不一,Linux有很多发行版,在这之中又分很多类:桌面版、服务器版、企业版。桌面版针对的大多是普通用户,有很好的桌面环境,比较适合于新手;服务器版多倾向与终端界面,没有较好的桌面环境,在服务器开发方面有很大优势;企业版则面向的大多是大型企业,对信息安全,网络稳定方面都很高。Linux由于其公开的源码及免费的操作系统,在Linuxkernel的代码获取及相关构建工具没有任何限制。因此对基于Linux平台的Socket网络编程的研究就显得很重要,下图1.1为其C/S简易模式图和图1.2详细模式图:图1.1C/S简易模式图图1.2C/S详细模式图编写Socket的server和client端程序,双方通过socket建立连接,从而完成进一步的通信。为什么选择Linux1.代码的质量和可依赖性。质量和可依赖性是衡量代码优劣的重要标准。尽管在“qualitycode”众说纷纭,但是大多数编程人员所期望的都有如下几点:(1)模块化:每一个功能都写成一个模块,这样不仅易于读写,更加方便以后的移植;(2)可读性:一段代码应该能让别人看的下去,这里包括变量的定义,尽量取有意义的变量名,避免inti,j这样的定义;一段杂乱的让人看一眼就不想往下看的代码一定是不好的,不管功能有多牛。;(3)可扩展性:在增加新的功能时,不要对原来的代码进行大篇幅的修改,而是只要添加对应的功能即可;(4)可配置性:可以选择对应的功能特色进行编译,不要的就不编译,从而生成的程序适合自己的使用。但是配置的过程应该尽量简单人性化。2.TCP/IP协议由网络层的IP协议和传输层的TCP协议组成,是Internet最基本的协议、是Internet国际互联网络的基础。Socket通信所采用的协议分为面向连接(TCP)和非面向连接(UDP)的两种,但由于UDP效率较高但是传输不可靠,不能胜任复杂的网络环境,不得不通过超时和重传等手段来实现较高的可靠性;然而TCP在数据传输方面提供了完全的可靠性,因此选用TCP/IP协议更加可靠些。3.代码的可获得性。Linuxkernel源码及所有的开发和编译工具是很方便从网络上获取的。Linux中最重要的部分Linuxkernel分布在GPL下。其他的代码也分布在相同的证书下,如BSD等。构成Linux的大部分代码都是没有限制的。当源码的访问受限时,开放自由软件组织就会寻找新版本的源码包代替原有的代码。正是由于这一特性,Linux受到很多电话制造商的青睐,他们将其移植到自己产品中,稍作修改优化使其适合自己的产品。4.硬件支持。Linux支持不同种类的硬件平台和设备,尽管有些驱动暂不支持Linux系统,但是对此有很大的期待。因为很多驱动都由Linuxshequ维护着,你可以毫无顾忌的使用这些驱动。各种硬件协同工作,使你的工作更加顺畅。你希望在你自己电脑上编写的程序能够在另一架构的操作系统上正常运行,甚至有不同的设备驱动运行在不同的系统架构上。5.通信协议和软件标准。Linux提供了广泛的通信协议和标准的软件支持。这使得整合Frameworks及相关软件到Linux上变得更加容易。同时,Linux是U-like的,可以方便的将UNIX程序移植到Linux上。事实上,许多应用被绑定在一些商用Unixes中,继而被移植到Linux中。1.4论文的主要工作第一章:绪论,大体介绍Linux操作系统发展、应用的现状以及我们选择Linux系统的原因;第二章:介绍Linux软件开发中使用的编译工具和调试工具及其简单的使用方法;第三章:介绍设计是怎样实现的以及在实现的过程中用到了哪些关键技术并对这些关键技术作简要分析;第四章:介绍设计相关的主要代码的实现过程,依次对服务器和客户端主要流程进行介绍,必要的功能展示;第五章:设计总结与展望第二章Linux编程常用工具2.1嵌入式C语言 C语言最初是由贝尔实验室的职员Thompson和Ritchie于1971年共同开发。其应用场景很多,如:操作系统:Linux;微控制器:汽车和航天飞机;嵌入式处理器:电话和便携式电子设备等;DSPProcesser:数字音频图像处理和TV系统等。程序的生成过程如图2.1所示:图2.1程序的生成过程C语言能够在编程开发领域受到如此高的青睐,主要有以下几个方面的原因:1.通用性。因而在跨平台开发时非常方便,C语言的这一特性吸引着广大编程爱好者;2.执行速度快。可以指定编译选项从而省去中间过程,以进一步提升程序的执行效率。3.可移植性。由于各个平台的差异,当一个程序到不同平台上,只需要从新编译一下源代码即可使用;4.发展快速。C语言发展较快。在上世纪80年代末期由AmericanNationalInstitude发布的ANSIC的C语言标准奠定了其发展的基石。2.2GCC工具链 GNU/Linux操作系统上往往使用gcc作为编译工具。其不是一个单独的程序,而是多个程序的组合,因而通常称为toolchain(工具链)。GCC的全称是GNUComplierCollection,是由GNUProject提供的支持多种编程语言的编译器。GCC是GNUtoolchain中的重要组成部分,GCC作为一个工具和实例,在自由软件的成长过程中扮演着重要的作用。同年12月,GCC扩展到能够编译C++,不久之后能够支持的语言更多,如Objective-C、Objective-C++、Fortran、Java和Ada等等。 GCC被移植到多种架构的处理器,并作为一种专业软件开发工具配置于系统中。GCC同时也集成于大多数嵌入式平台,包括Symbian,AMCC等。作为GNU操作系统官方指定编译器,GCC被其他U-like操作系统吸收采用,包括Linux和BSD家族。程序的编译过程如图2.2所示: 图2.2源码到可执行文件的过程2.2.1GCC的用法 对于GCC的编译选项只要掌握一些常用的选项就可以,其他多大100多种选项有些工程师可能一辈子也不会用到。 gcc基本用法: gcc[-Wall][-O1..3][-g][-oname]file... -Wall:打开所有警告项-O:设置优化级别,O0表示关闭优化功能-g:将调试信息编译到目标文件中-oname:指定输出文件的名称是namefile:被编译(链接)的文件2.2.2调试和剖析选项 gcc–g:以本机格式(stabs,COFF,XCOFF,orDWARF2)生成调试信息供gdb使用。 在大多数系统上,“-g”产生的调试信息只能供GDB使用,这种调试信息在gdb中能很好的工作,而在其他调试器上不是读取不了就是调试器异常崩溃。如果你想指定调试信息的格式,可通过如下方式: -gstabs+、-gstabs、-gxcoff+、-gxcoff、-gvms gcc允许同时使用‘-g’、‘–o’,这种使用方法将会产生令你意想不到的效果:你定义的一些变量可能不存在、程序会跑飞掉、一些语句不会被执行。不管是否提供可能的优化输出,但可以肯定的是,这将会产生一些bug。 但是这种方法使得你能够在和最终产品尽可能相同的情况下对代码进行调试。应当注意,如果你同时使用这两个编译选项,必须清楚所写的某些有关代码已经在优化时被GCC作了些改变。2.3GDB调试器GDB(GNUDebuger),是GNU操作系统的标注调试器;。GDB作为GNU系统的一部分于1986年由RichardStallman编写,是GPL下的自由软件。使用者可以监视和修改程序的内部变量,甚至可以独立地调用程式正常行为的函数。 GDB能对多种不同处理器架构上运行出错的应用软件排错,这些处理器架构包括:Alpha、ARM、AVR、H8/300、AlteraNios/NiosII、System/370、System390、X86及其64位扩展。GDB明显的限制是在他的运用方面,没有较亲和的图形界面,预设只有命令行界面可用。 Gdb是为了让你能够“看到”另一程序执行时具体做了什么或者是另一程序崩溃时发生了什么 GDB可以做四件事情帮助你找到程序中的bug:启动你的程序,指定任何可能影响其行为的变量或函数等;使你的程序暂停(设置断点)或者指定特定的状态;当你的程序终止时,检测发生了什么;改变你程序中的事情,这样你就可以尝试纠正一个bug的影响以便继续往下找另一个bug。2.3.2GDB用法 gdb的基本命令: gdb提供的各种各样的命令有着不同的功能。从简单到复杂,以下列出了一些常用的gdb调试命令: (1)filefilename:在gdb模式中无需退出即可通过file命令装在你要调试的文件(gdb)filedavidReadingsymbolsfrom/home/david/Graduation_project/david...(nodebuggingsymbolsfound)...done.(2)kill:通过kill命令可以终止当前正在调试的程序(gdb)killKilltheprogrambeingdebugged?(yorn)y还有一些常用的如:n(next)、s(step)、run和q(quit)将在下节例程中详细说明。2.3.1用gdb调试GCC程序 在终端输入gdb回车后,会出现如下一长串内容: david@zdz:Graduation_project$gdbGNUgdb(Ubuntu/Linaro7.4-2012.04-0ubuntu2.1)7.4-2012.04Copyright(C)2012FreeSoftwareFoundation,Inc.LicenseGPLv3+:GNUGPLversion3orlater</licenses/gpl.html>Thisisfreesoftware:youarefreetochangeandredistributeit.ThereisNOWARRANTY,totheextentpermittedbylaw.Type"showcopying"and"showwarranty"fordetails.ThisGDBwasconfiguredas"x86_64-linux-gnu".Forbugreportinginstructions,pleasesee:</gdb-linaro/>.(gdb)Gdb的一些使用命令如果记不住的话可以查看官方手册,没必要硬记,准其自然,每次不会的时候翻翻手册,时间长了自然就记住了下面只介绍一些常用的几个命令:比方说,现在有一个通过gcc编译好的可执行文件david,在程序中我定义了两个变量x,y赋初值为5,6终端执行david@zdz:Graduation_project$./davidTheresultis:x+yis11x*yis30x/yis0那么通过gdb来看看发生了什么:david@zdz:Graduation_project$gdbdavid(gdb)我们现在告诉gdb使用窄于通常的显示宽度:(gdb)setwidth70下面我要知到david这个程序怎样工作的,通过阅读源程序我大体知道工作流程,所以我可以设置断点: (gdb)breakcountBreakpoint1at0x400579接下来运行程序:(gdb)runStartingprogram:/home/david/Graduation_project/davidTheresultis:Breakpoint1,0x0000000000400579incount()可以发现程序停在count()函数这个地方,Theresultis:这句话显示count()函数之前都已运行完毕。现在我可以通过n(next)让程序执行到下一行(gdb)nSinglesteppinguntilexitfromfunctioncount,whichhasnolinenumberinformation.x+yis11x*yis30x/yis00x0000000000400573inmain()还可用backtrace命令查看在栈中的什么位置,显示每一个激活的子函数的栈结构:(gdb)backtrace#00x0000000000400573inmain() 通过输入q(quit)退出gdb模式: (gdb)qAdebuggingsessionisactive. Inferior1[process3423]willbekilled.Quitanyway?(yorn)ydavid@zdz:Graduation_project$第三章设计的实现及主要技术3.1实现分析 本节主要对设计的功能和设计思路进行简单的介绍3.1.1功能分析 客户端的点点通信及文件的传送。主要包括Server端和Client端: 1.Server端:主要负责处理用户发送过来的消息,对用户的系列动作进行管理(登陆、注册、私聊、群聊)和对用户的数据进行管理(查看用户的账户信息)。 2.Client端:主要可以和其他用户私聊及群聊,还可以向其他用户发送文件。3.1.2设计分析 服务器对Socket的初始化:网络协议的指定、端口的指定,具体步骤将在Socket中进行仔细介绍,等待用户连接。Client通过socket()初始化一个socket并向server发送请求连接。当服务器接收到用户的连接请求时,将为其开辟一个单独的新的线程,这条线程就为此用户服务。只有当用户退出时才将其释放。而服务器的主线程仍在等待其他用户的连接。系统设计大体框图如图3.1所示:3.1系统设计框图3.1.3技术分析 基于以上简单的分析,Socket是实现C/S通信的接口,且使用TCP/IP协议。TCP协议是面向连接的、传输可靠的,且对网络的适应能力较强,股本设计选用TCP,而不选用UDP。服务器要“同时”处理多个客户的请求,就得实现多任务并发。对于单个处理器来讲并发仅仅是概念上的,不是真正地同一时间处理多个任务,而是多个任务轮流依次执行,留给用户的感觉就好像在同时处理多个任务;而对于多喝CPU,则是真正意义上的并发执行,效率总的来说也有图单核CPU。 线程并发的使用往往会牵涉到同步的问题,mutex能解决此类问题,当然最主要的还是Scoket函数调用。 Ncurse终端字符界面大部分是由C++编写,使用它只要将其安装在本机上然后通过调用它的库函数即可。3.2TCP/IP通信TCP/IP协议不同于OS模型,TCP/IP协议主要就以下几个部分:应用层、传输层、数据链路层和网络接口层。各层示意图如图3.2所示:图3.2TCP各层示意图对于我们编程来说,没有必要完全掌握每一层的细节,只要知道:(1)每一层协议都是为下层向上层作转换(2)没有必要知道TCP的具体操作细节,也不要知道IP及数据链路层的具体操作细节(3)从应用程序的角度来看,仅仅把它们看成是SocketAPI就行了应用到应用之间的通信过程如图3.3所示:图3.3TCP/IP通信示意应用层:应用层的协议被大多数为用户提供服务的应用所使用,通过网络向低层交换应用数据。但这得包含一些基本的网络支持服务,如许多路由协议和主机配置协议。例如,应用层常包含HTTP、FTP、SMTP、DHCP等协议。而且TCP/IP区分用户协议和支持协议;支持协议提供系统服务,而用户协议提供用户级应用程序,比方说FTP就是用户协议,DNS是系统协议。传输控制层:TCP/IP协议族中有两个传输层协议:UDP和TCP。UserDatagramProtocol(UDP)是通过数据报的方式传输,TransmissionControlProtocol(TCP)是通过流的形式传输。 数据链路层:从应用角度来看,我们通常可以忽略数据链接层,因为所有通信细节在驱动程序和硬件接口中处理。数据链路层(Data-LinkLayer)最重要的一个特性是最大传输单元Maximumtransmissionunit(MTU)。 网络层:主要包括一下任务:=1\*GB3①将数据分成足够小的片段以便能够通过数据链路层进行传输(如果需要的话)=2\*GB3②通过Internet指定路径发送数据=3\*GB3③为传输层提供服务=4\*GB3④IPv4和IPv6=5\*GB3⑤IP以数据包的形式传送数据=6\*GB3⑥IP是无连接的和不可依赖的。为了建立连接,TCP常通过三次握手,三次握手示意图如图3.3.2:在客户机试图连接服务器时,服务器必须首先必须打开一个端口并对其进行绑定并监听;这通常叫做被动连接,一旦被动打开建立成功,客户机就可以初始化一个主动连接。为了建立一个连接,三次握手机制就起作用了:SYN:Client向Server发送主动连接请求。Client设置序列号为随机值xSYN_ACK:回应请求,server发送SYN-ACK。序列号被设置成+1,eg:x+1;此时server产生另外一个随机值y;ACK:最终,client向服务器发送回应,接收到的序列号已经被设置成了x+1,server产生的序列号被设置成+1,eg:y+1。Client和Server都接收到了连接确认包。(1)(2)两步为单向(去)连接产生一个可识别的连接参数;(2)(3)是为其他方向(回)产生一个可识别的连接参数。通过这些步骤,一个全双工的通信就建立成功了。示意图如图3.4所示: 图3.4三次握手示意图3.3Socket(网络套接字)SocketAPI,提供了许多例程和函数供程序员使用,以此开发TCP/IP网络应用程序。就像pipe,Socket用文件描述符来表示,但是区别于Pipe的是,Socket支持在两个进程之间甚至同一网络中的不同机器上进行通信。Socket基本上可以理解为和其他机器进行通信;telnet,rlogin,ftp,talk等其他相似的网络程序都是用sockets。但并不是所有机器都支持Socket。在GNU库中,“sys/socket.h”存在于大多操作系统中,socket函数通常也都存在,但如果系统真的不支持socket,那么这些函数也就不会起作用了。当创建socket时,你必须指定使用那种通信类型及该使用那种协议去实现它。不同的通信类型定义了发送和接收数据的用户级别,选择一个通信类型你得考虑一下几个方面:(1)传输的数据形式:bytes、packets(2)数据在传输过程中是否可以丢失:数据丢失程度不一样对最终的结果影响是不一样的;如果传输的数据不能丢失,那就得选用较可靠的协议。(3)通信是否是双向的:就像打电话和发邮件的区别。在两个程序,或许是不同的PC上进行数据传输得知道一下几点:(1)为了在两个socket间进行通信,这两个socket必须制定相同的协议(2)每个协议都有自己的专属定义,不能用其他协议的定义。(3)每个协议族都有一个默认的协议,你可以通过指定0作为协议号。3.3.1编程步骤 Server端:socket()、bind()、listen()、accept()、send()、recv()、close(); Server端的编程步骤大体如上,建立连接后通过不断地创建线程为Client提供服务。Clent端:socket()、connect()、send()、recv();Client端成功连接服务器后,创建一个新的接收线程从服务器不断接收数据显示在不同的界面。3.3.2地址及顺序地址 1、地址结构相关处理 (1)数据结构介绍sockaddr结构体有两个成员:shortintsa_family、charsa_data[14];其原型如下: structsockaddr{unsignedshortsa_family;/*地址族*/charsa_data[14];//socket地址数据的真正长度};sockaddr_in结构体有三个成员:sin_family、sin_addr、sin_port;其原型如下:structsockaddr_in{sa_family_tsin_family;/*addressfamily*/in_port_tsin_port;/*portnumber*/structin_addrsin_addr;/*internetaddress*/};一般情况下,sockaddr_in结构体使用起来更加方便。 (2)结构字段 下表3.1列出了sa_family字段的常用值。 表3.1sa_family字段结构定义头文件#include<netinet/in.h>Sa_familyAF_INET:IPv4InternetprotocolsAF_INET6:IPv6InternetprotocolsAF_LOCAL:Localcommunication2、数据存储优先级(1)函数说明 不同的计算机为一个word的存储顺序使用不同的约定。有些计算机将最重要的字节放到一个字的最开始(大端),而有些则是放到一个字的最后(小端)。网络协议为传输的数据规定了字节顺序,这就是大家所熟知的网络字节序。因此有时候就需要对其进行转化。 用htons和ntohs为sin_port进行转化;用htonl和ntohl为sin_addr进行IPv4地址的转化。其中各个头字母的含义如下: h:host;n:network;s:short;l:long (2)函数格式说明 下表3.2列出了这四个函数的语法格式: 表3.2htons等函数定义所需头文件#include<arpa/inet.h>#include<netinet/in.h>(somesysytem)函数原型uint32_thtonl(uint32_thostlong);uint16_thtons(uint16_thostshort);uint32_tntohl(uint32_tnetlong);uint16_tntohs(uint16_tnetshort);函数参数hostlong:主机字节序的32位数据hostshort:主机字节序的16位数据netlong:网络字节序的32位数据netshort:网络字节序的16位为数据函数返回值Success:返回要转换的字节序Error:-13、地址格式转化(1)函数说明 用户习惯性输入的ip形式为:xxxx.xxxx.xxxx.xxxx或xxxx:xxxx:xxxx:xxxx,这两种格式虽能被用户识别,但是机器却无法识别,故只有将其转化成机器能识别的二进制格式。经常使用的有inet_aton、inet_addr、inet_pton、inet_ntop,其中后两个兼容IPV6。inet_pton:将以.形式表示的十进制转换为为二进制;inet_ntop:将二进制转换为以.形式表示的十进制。(2)函数格式表3.3列出了inet_ntoa的语法要点:表3.3inet_ntoa定义所需头文件#inclide<arpa/inet.h>函数原型char*inet_ntoa(structin_addrin);函数参数in:网络字节序的地址函数返回值Success:返回地址字符串表3.4列出了inet_addr的语法要点:表3.4inet_addr语法要点所需头文件#inclide<arpa/inet.h>函数原型in_addr_tinet_addr(constchar*cp);函数参数要转换的字符串地址函数返回值Success:返回转换后的二进制地址3.3.3链接函数说明 1、C/S连接示意图C/S模式描述了应用程序相互协作的关系。Server为一个或多个Client提供一个功能或服务。Server通常按其服务进行分类归档。比方说webserver提供网页支持二fileserver则提供文件方面的服务。一个PC机扮演的角色是客户机还是服务机还是两者都是,通常取决于其应用的需求。一个机器可以同时作为服务机和客户机;比方说,当单机运行webserver和fileserver同时服务于不同的请求;与此同时,客户机上的软件能够和同一台计算机上的服务机软件通讯。示意图如图3.5所示: 图3.5Socket编程示意图2、函数格式(1)socket()相关定义如下表3.5所示:表3.5socket函数定义原型intsocket(intdomains,inttype,intprotocol);函数说明创建一个通信socket并返回一个描述符参数说明domains指定通信地址族,即选择什么协议用来通信。常用的几个有AF_UNIX,AF_LOCALLocalcommunicationAF_INETIPv4InternetprotocolsAF_INET6IPv6Internetprotocolstype规定使用那种通信数据类型SOCK_STREAM提供有序、可靠、双向,基于连接的字节流SOCK_DGRAM不可靠,无连接的数据包protocol指定domain地址族中某个特定的协议,通常情况下可以指定为0(2)bind()相关定义如下表3.6所示:表3.6bind函数定义原型intbind(intsock_fd,conststructsockaddr*addr,socklen_taddr_len)函数说明给指定sock_fd的socket分配内存地址空间参数说明sock_fd创建socket时返回的文件描述符addr结构体,用于存放ip和端口号addr_len结构体addr的大小(3)listen()相关定义如下表3.7所示:表3.7listen函数定义原型intlisten(intsock_fd,intlen)函数说明监听client连接的信号,和accept()合同参数说明sock_fd创建socket时返回的文件描述符len指定等待队列的最大长度(4)accept()相关定义如下表3.8所示:表3.8accept函数定义原型intaccept(intsock_fd,structsockaddr*addr,socklen_t*addr_len)函数说明响应连接等待队列的队头请求,并创建新的socket,返回其文件描述符,新创建的socket并不会影响原先的socket参数说明sock_fd创建socket时返回的文件描述符addr结构体,用于存放ip和端口号addr_len结构体addr的大小(5)connect()相关定义如下表3.9所示:表3.9connect函数定义原型intconnect(intsock_fd,conststructsockaddr*addr,socklen_taddr_len)函数说明通过指定的sock_fd与socket连接进而与其地址连接参数说明sock_fd创建socket时返回的文件描述符addr结构体,用于存放ip和端口号addr_len结构体addr的大小(6)send()相关定义如下表3.10所示:表3.10send函数定义原型ssize_tsend(intsock_fd,constvoid*buf,size_tbuf_len,intflags)函数说明往指定sock_fd的socket发送消息参数说明buf发送数据存放的暂存区buf_len暂存区的大小(7)recv()相关定义如下表3.11所示:表3.11recv函数定义原型ssize_trecv(intsock_fd,void*buf,size_tbuf_len,intflags)函数说明从指定sock_fd的socket中读取数据参数说明buf发送数据暂时存放的区域buf_len暂存区的大小(8)close()格式:close(fd)3.4进程3.4.1进程的概念(1)在说明进程前,先看一下什么是程序?程序就是一个文件,该文件包含了一系列信息,这些信息描述了如何在运行时构造一个进程。(2)Linux平台中可执行程序类型=1\*GB3①可执行目标文件经链接器链接后可直接执行的文件。内核通常支持几种特定形式的可执行文件。ELF格式是Linux系统中普遍使用的一种标准的可执行文件格式。=2\*GB3②可执行脚本Bash就是shell或者说是命令行语言解释器。GNU操作系统提供不同的shell,其中包含csh、ksh,但是bash是默认的bsh。和其他GNU软件一样,bash是可移植的,其目前运行在几乎所有版本的Unix及其他的操作系统上,像MS-DOS、os/2和微软平台。当Shell读到输入时,展开一系列的操作。如果检测到有注释时会自动忽略以‘#’开头这一行。然后,shell将读取到的内容分成各个部分,再将这些解析成命令和其他结构,去掉一些特定的字或者字符,执行特定的命令。下面是其执行流程:1)从文件中读取输入2)将读取到的内容按引用准则分成字和运算符。这些标号被通配符分隔3)将标号解析成单一的或复合的命令4)执行各种shell展开5)执行一些必要的重定向并将这些操作符及操作数从参数列表中移除6)执行命令7)选择性的等待命令执行完成并记录退出状态。(3)什么是进程 进程的专业定义,IEEEStd1003.1:“Anaddressspacewithoneormorethreadsexecutingwithinthataddressspace,andtherequiredsystemresourcesforthosethreads”。通俗的讲,就是一个正在运行的程序,每个程序都有自己的执行状态和地址空间,每个进程相互独立,除了两个进程发生通信,否则两个进程没有多大关系。(4)不同进程的区分方法(内核)每个进程都有一个属于自己的id号叫做PID,就像我们每个人都有一个自己的身份证号一样,PID标识着这个进程的存在。内核通过这个PID来管理各个进程的状态和资源。一个内核产生新的PID时,内核为这个PID分配系统资源,这样一个进程也就产生了。3.4.2进程的内存布局 进程内存分布图如图3.6所示:从低地址到高地址依次分布图3.6进程内存分布3.4.3进程生命周期 =1\*GB3①创建:进程的创建是由其父进程通过folk来创建的。下图描述了进程的创建过程:=2\*GB3②运行:同一系统中可以有多个进程同时运行,并且能够互相通信。试想一下,一个系统上同一时刻只能运行一个进程,要开其他进程必须得先将本进程关闭掉,那是得有多么麻烦。 =3\*GB3③终止结束一个进程的运行。3.5多线程3.5.1线程的概念 线程是系统最小的调度单元,是操作系统的重要组成部分。操作系统中,线程和进程的实现是不同的,单线程通常是进程的一部分。多个线程可以在同一个进程中执行,和进程共享内存资源,这是不同进程之间所不能做到的。与进程相比,线程没有自己独立的地址空间,创建出来的新线程将和创建它的进程(或线程)共享一个虚拟地址空间,它们被置于同一个线程组中。每个线程都有自己独立的栈空间。不管是服务器还是客户机都包含信息的一收一发两个过程。同时使用主线程去收发信息是不可取的,因为收信息是被动的,当没有信息可收时主线程就会阻塞,从而影响信息的发送。而创建两个进程的方法在这里也是是不可取的,因为不同的进程对应不同的端口号,但是信息的发送和接收必须使用同一端口。因此必须创建多个线程来完成不同的收发任务。3.5.2进程和线程的关系及优缺点线程在进程中的分布如图3.7所示:图3.7进程中的线程 使用线程的优点: =1\*GB3①较快的执行(Fasterexecution):在多核CPU上执行明显快于单核CPU,且上下文状态的切换比进程快;=2\*GB3②响应迅速(Responsiveness):当主线程阻塞时,其他线程仍可和用户进行交互; =3\*GB3③对资源的需求小(Lowerresourceconsumption):用较少的系统资源服务于更多的用户; =4\*GB3④更好的系统利用(Bettersystemutilization):能更好的利用内存的资源;=5\*GB3⑤简单的分享和通信(Simplifiedsharingandcommunication);=6\*GB3⑥并行化(Parallelization):多个线程同时对一个任务进行操作。但是线程也有一些缺点:=1\*GB3①同步(\o"Synchronization(computerscience)"Synchronization):由于共用相同的地址空间,那么就要很好的解决竞争和其他的非正常行为。如果处理不好,会造成死锁的情况,那么进程就会变为僵死进程;=2\*GB3②在进程中崩溃(Crashinprocess):线程的非法操作从而造成整个进程的退出。3.5.3数据类型及其用法1、函数简要说明如下表3.12所示:表3.12线程函数定义函数功能typedefunsignedlongintpthread_t/usr/include/bits/pthreadtypes.hpthread_create()创建一个线程pthread_exit()终止当前线程pthread_join()阻塞当前线程,直到另一线程退出2、线程的创建与结束(1)新线程的创建可通过pthread_create(),定义及说明如下表3.13所示:表3.13pthread_create函数定义原型intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*args);参数说明thread指向一个pthread_t型变量,用于返回线程IDattr指定各种不同属性,若设为NULL,表示默认属性start_routine新线程的工作函数args传递给执行函数的参数返回值0表示执行成功,出错返回错误编号(EAGAIN、EINVAL)(2)pthread_join(),等待一个线程执行结束(与waitpid()类似),相关定义如下表3.14所示:表3.13pthread_join函数定义原型intpthread_join(pthread_tthread,void**value_ptr)参数说明thread指定要等待哪个线程value_ptr是指向(void*)型数据的指针,用于获得线程的退出状态(thevaluethatwasspecifiedwhenthethreadperformedareturnorcalledpthread_exit()),如果不需要退出状态则可将其设为NULL返回值成功返回0,错误返回一个非负数(3)pthread_exit()voidpthread_exit(void*value_ptr);该函数终止当前线程(callingthread),并且指定一个返回值,返回值可以在其他线程中通过调用pthread_join()获得。综上所述,线程在一下情况下退出:=1\*GB3①程序调用pthread_exit()=2\*GB3②调用pthread_cancle()=3\*GB3③从回调函数start_routine中返回=4\*GB3④调用exit(),或者主线程退出(终止进程中所有的线程)3.6线程同步3.6.1概念 线程同步,顾名思义就是对某一块内存的操作的先后顺序。当这块内存中的数据是只读的,那么其他线程都可以对其进行读取,那就没什么问题;或者说这块内存中的数据不会被其他线程读取或修改,那么也不存在同步的问题。只有当多个线程同时对某一块内存中的数据进行修改时,多个线程都要对其进行修改,那么到底是哪一个线程先修改呢,此时就涉及到同步的问题。3.6.2线程同步的几种方法:信号量、自旋锁、互斥量 信号量:操作系统之中,信号量通常是一个多个进程或线程用于控制访问权限的变量或抽象数据类型。信号量可被增加或减少,但将保证对其关键的是原子操作,即有多个线程试图改变一个信号量的值时,系统将保证所有操作依次进行。自旋锁:线程为了获得自旋锁,通过不断循环检查该锁是否可获得。这个锁是出于激活状态的,但是对当前任务并不可用,这就有点类似于忙时等待的情况。一旦获得,当前线程将会执行,同时该锁也会被处理直到被释放,在一些实现中可能会自动释放。 互斥量(mutex)从概念上讲有点类似于一个初始值为1二进制信号量。互斥量被获取之后就不能再被获取,因此对互斥体的获取和释放操作常常称为加锁和解锁操作。互斥量只能由获取它的线程进行释放,如果违反这一原则,则结果是未定义的。 互斥量就是当前线程对其正在访问的共享资源上锁,等其用完了,再对其进行解锁,让其他等待的线程使用。当一个共享资源被mutex上锁后,其他任何线程都无法对这块共享资源进行修改。3.6.3互斥量的使用互斥量用一个pthread_mutex_t型的变量表示。其定义如下表3.15所示:表3.15pthread_mutex_init函数定义原型intpthread_mutex_init(pthread_mutex_t*mutex,constpthread_mutexattr_t*attr);函数说明对mutex进行初始化参数说明mutex指向要初始化的互斥量attr参数可以为NULL,表示使用默认属性互斥量不用以后,应该使用下面的函数进行销毁:intpthread_mutex_destroy(pthread_mutex_t*mutex);互斥量的主要操作函数如下表3.16所示:表3.16互斥量主要函数说明函数原型简要说明intpthread_mutex_lock(pthread_mutex_t*mutex)上锁intpthread_mutex_trylock(pthread_mutex_t*mutex)上锁intpthread_mutex_unlock(pthread_mutex_t*mutex)解锁 各函数详细说明:pthread_mutex_lock():将其指向的资源加锁。如果这时互已经被锁,则调用这个函数的线程将被阻塞,直到其成为未锁状态。函数返回时,表示这个互斥量已经变为已锁状态,同时,函数的调用者成为这个互斥量的拥有者。pthread_mutex_trylock():用于对参数指向的互斥量进行加锁。try表明试试的意思,即就是试探一下是否可获得锁,如果不可获得那就一错误状态返回,该线程不阻塞。pthread_mutex_unlock():将参数指向的互斥量解锁。解锁的对象必须是当前线程所拥有的。如果这时互斥量是未锁状态或不是当前线程所拥有的,则结果未定义。3.7ncurrses字符界面3.7.1ncurses简介 一个提供了供编程人员开发字符终端界面的接口的程序库。是一个开发运行在终端模拟器上的类似于“GUI”的应用软件。为了减少使用远程终端的延迟,其对屏幕的改变进行了优化。 发展史:从curses到pcurses再到ncurses Curses:第一版是在加利福尼亚伯克利分校开发的。一开始使用的是termcap库,这个库通常用在其他程序中,如vi编辑器; Pcurses:PavelCurtis克隆了BellLabscurses,在1986年期间由很多人维护着; Ncurses:1991年后期pcurses库由ZeydBen-Halim接手开发,到1993年11月ncurses1.8.1版本发布了。由EricSRaymod加入了JuergenPfeifr编写的菜单库。自从1996年,ncurses被ThomasE.Dickey维护着。3.7.2ncurses常用函数介绍1. 初始化ncurses:WINDOW*initscr(void);函数说明:initscr()务必在其他任何影响窗口结构的函数之前调用。它捕捉terminal的特性并初始化所有curses相关的数据结构。即将屏幕刷新一下;返回值:返回一个WINDOW类型的结构体指针;Success:返回一个指向stdscr(代表默认窗口)的指针;Error:输出标准错误信息然后退出。2. 输出格式串函数:intprintw(constchar*fmt,...);//和printf函数用法一样。3. 刷新屏幕内容:intrefresh(void);函数描述:当一个窗口中的结构被改变时,必须调用refresh刷新一下新的内容才能显示出来。4. 输入一个字符:intgetch(void);函数描述:获取用户输入的一个字符;函数返回值:成功返回用户按下的键值,失败返回error。5. 离开ncurses:intendwin(void);函数描述:退出ncurses时将会被调用,退回到普通模式。6. 创建窗口类函数:WINDOW*newwin(intlines,intcols,intbegin_y,intbegin_x);函数说明:创建并返回一个指向特定行数和列数的指针,开始的位置由begin_y和begin_x两个参数决定。7. 属性设置:intattr_on(attr_tattrs,void*opt);函数说明:attr_on/attr_off是打开或者关闭特定的属性而不影响其他的属性。倘若用attrset的话,前面所有的设置将会被覆盖。8. 画线类,方框类:intmvwvline(WINDOW*win,inty,intx,chtypech,intn);9. 输出控制函数:intscrollok(WINDOW*win,boolbf);函数说明:允许指定的窗口滚动,就上当显示到窗口最下面一行时,可以下上滚动显示。10. 更新窗口类函数:inttouchwin(WINDOW*win);函数说明:使整个窗口看起来被重绘了,在接下来调用refresh()或者wrefresh()时重绘整个窗口。第四章设计的编程实现4.1聊天室Socket编程连接 1、监听连接过程 socket函数创建Socket、bind函数对创建的Socket进行绑定、listen函数对其进行监听。Socket()初始化socket,创建新的sockfdsockfd=socket(AF_INET,SOCK_STREAM,0);此过程涉及到ip地址的处理 saddr.sin_addr.s_addr=htonl(INADDR_ANY);bind()绑定端口号和ip地址saddr.sin_family=AF_INET;saddr.sin_port=htons(PORT);bind(sockfd,(structsockaddr*)&addr,len);listen()监听listen(sockfd,BACKLOG); 2、发送请求 (1)获取主机信息 (2)对socket端口初始化 socket(AF_INET,SOCK_STREAM,0) addr.sin_family=AF_INET; addr.sin_port=htons(8010);//指定一个端口号,大于3000就行,因为3000一下的端口供系统使用inet_aton("",&addr.sin_addr); (3)调用connect()请求Server连接,等待服务器响应 connect(fd,(structsockaddr*)&addr,socket_len) 3、server接受请求,进行数据通信 (1)server通过accept()接收Client的请求; (2)匹配用户操作 1).登陆,创建登陆线程 2).注册,创建注册线程 (3)登陆成功,接受返回信息,一系列操作后退出连接 (4)将客户端口socket关闭 上述简要的步骤是server端应用程序监听请求,在此过程中,服务器线程一直处于休眠状态,直到有client向其发送连接请求。当server收到连接请求时,server主线程调用accept()接收请求,同时为client创建一个新的线程为其服务。然后server主线程继续等待新用户的连接。这样的系列步骤也就实现了socket通讯,如图4.1所示图4.1单个client和server之间Socket连接4.2聊天室程序设计及界面设计4.2.1服务器 服务器端的程序写好后,需要进行联编,可以通过写makefile,也可以写一个shell脚本,将要执行的文件名按终端命令的模式写进脚本。 本设计是通过写shell脚本,只要在终端执行脚本即可,命令如下: ./server.sh 编译成功后会生成一个可执行文件server,如果出错按照错误提示排错即可。 在终端执行./server,服务器启动,一直等待用户连接并为其服务,直到退出为止,启动界面如图4.2所示:图4.2服务端启动界面4.2.2客户端 1、进入客户端主界面 客户端的编译过程和server端一样,同样是通过shell脚本,在终端执行./client.sh编译成功后会生成一个可执行文件logo,终端执行:[zdz@david]$./logo客户端启动,启动界面如图4.3所示:图4.3客户端启动界面刚开始的界面上有登陆、注册、帮助、退出四个选项=1\*GB3①Login:用户输入自己的账号和密码,如图4.4所示:图4.4登陆窗口界面=2\*GB3②Register:填写名字、密码、验证信息及验证答案,如图4.5所示:图4.5注册界面=3\*GB3③Help:显示帮助信心,如图4.6所示:图4.6帮助界面=4\*GB3④Exit:退出客户端。登陆窗口的创建过程:=1\*GB3①初始化ncurses,即刷新终端,初始化终端背景=2\*GB3②通过调用newwin(LINES/3,COLS/2,LINES/4,COLS/4)函数创建登陆注册窗口:LINES为整个终端的行数,COLS为整个终端的

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论