资源描述
基于ARM-Linux的网络通讯设计实验
——下位机实验部分
一. 实验目的
1. 了解ARM-Linux操作系统的基本构成。
2. 掌握ARM-Linux下的网络通讯程序的设计与编译。
3. 掌握ARM-Linux的文件系统的修改及构建方法。
二. 实验内容
下位机程序设计。在Linux环境下用C语言编写基于TCP协议的数据收发的下位机程序,作为服务器端,可实现从上位机(客户端)接收数据并回传给上位机。然后,对程序进行交叉编译,最后修改及构建ARM-Linux的文件系统,使之能在ARM模块上运行。
三. 预备知识
1. C语言。
2. TCP网络传输协议的原理及工作方式。
四. 实验设备与工具
硬件:测试技术与嵌入式系统综合实践平台,微型计算机。
软件:Windows操作系统,超级终端,虚拟机软件VMware(预装Linux操作系统Fedora 6及ARM-Linux开发环境)。
五. 实验原理概述
1. ARM实验模块
ARM(Advanced RISC Machines),既可以认为是一个公司的名字,也可以认为是对微处理器的通称,还可以认为是一种技术的名字。ARM处理器是一个32位元精简指令集(RISC)处理器架构,其广泛地使用在许多嵌入式系统设计。和Intel的x86为代表的复杂指令集(RISC)处理器相比,ARM的特点是:体积小、低功耗、低成本、高性能;支持Thumb(16位)/ARM(32位)双指令集,能很好的兼容8位/16位器件;大量使用寄存器,指令长度固定,指令执行速度快;大多数数据操作都在寄存器中完成;寻址方式灵活简单,执行效率高;外围接口丰富,包含有如UART、USB、MCI、SPI、以太网接口等。ARM处理器可以很好地支持Linux、VxWorks、PSOS等嵌入式操作系,成为嵌入式系统处理器的主流,广泛应用于平板电脑、智能手机、手持/便携式智能设备等。
本实验模块采用了ATMEL公司推出的工业级的ARM9处理器AT91RM9200,配置了16MB的SDRAM,4MB的FLASH,USB Host端口,RS232接口,以及以太网接口等。
实验模块的硬件框图如下:
其中,SDRAM提供ARM-Linux系统运行所需放入内存,FLASH作为非易失性的存储器,用于存放ARM-Linux系统的引导程序Uboot以及ARM-Linux的内核镜像和文件系统镜像。AT91RM9200自带了以太网接口的MAC层,设计者可根据具体的物理传输介质选用相应的物理层芯片,本实验模块选用的是针对双绞线传输介质的物理层芯片DM9161。AT91RM9200自带的UART(通用异步收发器)接口并不支持计算机的RS232电平,因此,本实验模块采用了MAX232芯片来实现UART接口到RS232接口的转换。另外,AT91RM9200自带了USB2.0全速(Full Speed)接口,可以直接引出。
ARM实验模块采用了四层PCB布版设计,实物图如下所示。
以太网接口(RJ45)
RS232
接口
USB Host
接口
FLASH
芯片
2×8MB
SDRAM
芯片
电源接口座
ARM处理器
以太网物理层芯片
复位按键
网络变压器
以太网时钟晶振
ARM时钟晶振
电源转换芯片
2. ARM-Linux交叉编译环境
在本实验中,我们需要设计程序并在ARM上运行,但由于受到ARM处理器资源(包括人机接口、存储器容量等)的限制,我们无法在ARM 上编写源程序以及将其编译成目标程序,因此,我们需要在PC机平台上编写并编译能在ARM上运行的目标程序。
在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码,这个编译过程就叫交叉编译。简单地说,就是在一个平台上生成另一个平台上的可执行代码。这里需要注意的是所谓平台,实际上包含两个概念:体系结构(Architecture)、操作系统(Operating System)。同一个体系结构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系结构上运行。举例来说,我们常说的x86 Linux平台实际上是Intel x86体系结构和Linux for x86操作系统的统称;而x86 WinNT平台实际上是Intel x86体系结构和Windows NT for x86操作系统的简称。
交叉编译这个概念的出现和流行是和嵌入式系统的广泛发展同步的。我们常用的计算机软件,都需要通过编译的方式,把使用高级计算机语言编写的代码(比如C代码)编译(compile)成计算机可以识别和执行的二进制代码。比如,我们在Windows平台上,可使用Visual C++开发环境,编写程序并编译成可执行程序。这种方式下,我们使用PC平台上的Windows工具开发针对Windows本身的可执行程序,这种编译过程称为Native Compilation,中文可理解为本机编译。然而,在进行嵌入式系统的开发时,运行程序的目标平台通常具有有限的存储空间和运算能力,比如常见的 ARM 平台,其一般的静态存储空间大概是16到32MB,而CPU的主频大概在100MHz到500MHz之间。这种情况下,在ARM平台上进行本机编译就不太可能了,这是因为一般的编译工具链(Compilation Tool Chain)需要很大的存储空间,并需要很强的CPU运算能力。为了解决这个问题,交叉编译工具就应运而生了。通过交叉编译工具,我们就可以在CPU能力很强、存储空间足够的主机平台上(比如PC上)编译出针对其他平台的可执行程序。
要进行交叉编译,我们需要在主机平台上安装对应的交叉编译工具链(Cross Compilation Tool Chain),然后用这个交叉编译工具链编译我们的源代码,最终生成可在目标平台上运行的代码。常见的交叉编译例子如下:
1)在Windows PC上,利用RVDS(ARM 开发环境),使用armcc编译器,则可编译出针对ARM CPU的可执行代码。
2)在Linux PC上,利用arm-linux-gcc编译器,可编译出针对Linux ARM平台的可执行代码。
3)在Windows PC上,利用cygwin环境,运行arm-elf-gcc编译器,可编译出针对ARM CPU的可执行代码。
3. 虚拟机VMware简介
通常,一台计算机安装一个常用的操作系统。而当我们需要使用多个操作系统,而硬件平台有限的时候,就希望在同一台计算机上使用多个操作系统。往往采用的方法是分别安装它们,然后采用“多启动”的方式,分别引导计算机进入所需的操作系统。尽管这种方式可以实现在同一台计算机上使用多个操作系统,但多启动系统在一个时刻只能运行一个操作系统,想要切换操作系统时必须重新启动机器,重新引导进入所需的操作系统,这给用户的使用带来了不便。
VMWare是一个“虚拟机”软件。它使用户可以在一台计算机上同时运行二个或更多的操作系统,如Windows2000、WindowsXP、DOS、LINUX等系统。与“多启动”系统相比,VMWare采用了完全不同的概念,是真正的“同时”运行,多个操作系统在主系统的平台上,就象Word / Excel那样,作为一个标准的Windows应用程序,可以方便地切换。
VMWare需要一个操作系统来作最基本的平台,其它系统在它上面运行。作平台的这个操作系统叫做Host OS,为了叙述方便,这里称为“主系统”。在主系统上运行虚拟机,在虚拟机内部运行的操作系统叫做Guest OS(“子系统”或“客户操作系统”)。由于Host OS必须要稳定,且应用程序有独立的内存空间,所以目前VMWare只支持Linux和Windows2000以上版本的操作系统来做主系统,而Windows9X就不行。
VMware工作站(VMware Workstation)是VMware公司销售的商业软件产品之一。该工作站软件包含一个用于英特尔x86兼容计算机的虚拟机套装,其允许多个x86虚拟机同时被创建和运行。每个虚拟机实例可以运行其自己的客户机操作系统,如(但不限于)Windows、Linux、BSD衍生版本。用简单术语来描述就是,VMware Workstation允许一台真实的计算机同时运行多个操作系统。它允许操作系统和应用程序在一台虚拟机内部运行。虚拟机是独立运行主机操作系统的离散环境。在VMware Workstation中,用户可以在一个窗口中加载一台虚拟机,它可以运行自己的操作系统和应用程序;可以在运行于桌面上的多台虚拟机之间切换,通过一个网络共享虚拟机(例如一个公司局域网),挂起和恢复虚拟机以及退出虚拟机。这一切不会影响主机操作和任何操作系统以及它正在运行的应用程序。
VMware Workstation用于许多不同的目的。它可以用于测试新的操作系统或者应用程序环境,进行跨平台的软件开发,等等。
在本实验中,我们需要在Linux操作系统下进行程序设计,同时需要使用Windows的一些应用(如Word、Powerpoint等),而不是频繁地启动和引导计算机。因此,虚拟机软件VMWare是我们最佳的选择,它可以让我们像使用一个Windows应用程序一样地使用另一个与Windows截然不同的操作系统。
4. 网络传输协议
众所周知,当今国际互联网Internet是建立在TCP/IP体系架构基础上的,尽管大名鼎鼎的OSI体系架构是ISO组织为取代TCP/IP而指定的,但由于TCP/IP协议体系出现得较早,并得到了市场的认可和商业上的驱动,应用非常广泛,目前已成为事实上的国际标准。
参照OSI体系架构的术语和概念,可以将TCP/IP体系分为四层结构,从上向下依次是:应用层、传输层、网络层和网络接口层。如下图所示。
应用层
传输层
网络层
网络接口层
其中,网络接口层根据不同的网络物理介质而不同,如同轴电缆、双绞线、光纤、无线等等,一般由硬件实现,并对上层是透明的,软件开发中一般无需考虑这一层。
TCP/IP体系结构中的网络层、传输层和应用层一般是由系统软件实现的。网络层只运行一个协议,即IP协议,主要是供传输层协议调用的,程序设计者不能直接调用,因此,一般也无需考虑这一层。
TCP/IP体系结构中的传输层运行了两个截然不同的协议,即TCP协议和UDP协议,它们是为不同的应用场合而设计的。为了方便应用程序的设计,大多数操作系统都提供了TCP和UDP的库函数,如Windows给出了相应的针对不同传输层协议的API函数,可供程序员选择以设计出满足特定需求的网络应用。这一层是软件开发中需要考虑的。
TCP/IP体系结构中的应用层协议就很多了,如DNS、TELNET、SMTP、HTTP、FTP等等,它们实际上也是通过调用传输层的TCP协议或UDP协议,为某些特定的通用性很强的应用而设计的。显然,调用应用层协议比调用传输层协议更简单、更有针对性。然而,应用层协议一般是针对通用性很强的应用。
因此,网络应用程序的开发只能通过调用传输层的TCP协议或UDP协议来实现系统的网络通信功能。那么,在测试系统的网络应用开发中,究竟是采用TCP协议还是UDP协议,这里需要进行具体的分析。
1)TCP协议的特点分析
TCP协议(即传输控制协议)是一个面向连接的,具有可靠传输特性的传输层协议,其主要特点有:端口功能和差错检测;差错检验范围是整个报文段;面向连接(需要建立连接及连接管理);有确认重传的可靠性机制,数据按序到达; 实现了流量控制和拥塞控制;TCP的首部长度不固定;支持全双工的可靠传输。可见,TCP协议比较复杂。其最大的好处就是数据传输的可靠性,尤其是在网络误码率较高的时候,TCP协议可以准确无误的把数据送达目的地。它适用于数据量较大、且对数据的正确性要求较高的场合,如文件传输、远程登录、电子邮件、网页浏览等。但TCP协议也因为它的优点而体现出了对应的缺点,那就是复杂的可靠机制降低了传输的效率,特别是连接管理和确认重传机制等措施。对于数据量较小、且对实时性要求较高的场合,TCP协议就不再适用了。这也是为什么TCP/IP协议体系需要两种传输层协议的原因。
2)UDP协议的特点分析
UDP协议(即用户数据报协议)和TCP协议相比就简单许多,它不需要建立连接,也不采用可靠的传输机制,其主要特点有:有端口功能和差错检测;差错检验范围是整个报文段;不建立连接、不确认,不重传,数据无序到达;不进行流量控制和拥塞控制;UDP的首部长度固定为简短的8个字节;在局域网内支持广播传输。可见,简单就意味着高效,UDP协议省去了复杂的连接管理和确认重传的可靠机制,并且其首部长度仅有8个字节,有效数据的占比得以大幅提高。这种简单的机制带来的最大好处就是数据传输的高效率,非常适用于对实时性要求较高、而对数据正确性要求不高的场合。然而,对于数据量较大、且对数据的正确性要求较高的场合,UDP协议的数据无序到达和不可靠传输等缺点,使得其无法胜任,尤其是网络状况较差的时候更是如此。
诚然,TCP和UDP是两个优缺点互补的传输层协议,UDP协议的优点恰恰就是TCP协议的缺点,TCP协议的优点就正是UDP的缺点。
对于测试系统而言,数据传输的有效性和正确性是首要的考虑因素,数据错了,传得再快也无济于事。因此,对于测试数据的传输,通常采用的传输协议是TCP。
5. Linux下的TCP网络编程
TCP协议采用的客户-服务器的通信模式。TCP服务器
socket ( )
bind ( )
listen ( )
accept ( )
一直阻塞到客户端完成连接为止
TCP客户端
socket ( )
connect ( )
send ( )
连接请求
三次握手
recv ( )
处理
数据
send ( )
recv ( )
数据
send ( )
recv ( )
结束通知
close ( )
close ( )
pthread_create ( )
执行
线程
函数
bind ( )
可省略,由内核自行绑定一个本地可用的IP地址和端口号
在Linux系统下进行TCP网络通讯程序设计的流程如下图所示。
TCP服务器端首先运行,并不断地监听本地socket(IP地址和端口号的组合),看是否有远程客户端发来的连接请求。如果有,则接受连接请求,完成三次握手(TCP协议的连接管理方式),然后创建一个新的线程来与该客户端进行交互,同时继续监听是否有其他的客户端发来连接请求。TCP服务器采用了多线程的方式,可以实现与多个客户端的并发交互。当TCP服务器为某客户端创建的线程执行完毕后,必须关闭线程,以释放其占用的资源。
TCP客户端相对简单一些,只需向TCP服务器的socket发出连接请求,一旦服务器端接受并连接成功后,即可与服务端进行交互,完成数据的收发。
在本实验中,ARM模块将作为TCP服务器端,PC将作为TCP客户端来访问ARM模块,并与之进行数据的传输。
六. 实验步骤
1. 在Windows操作系统下找到VMware应用程序图标,点击进入虚拟机,并启动标签名为“clone of 6”的虚拟机,即运行Linux操作系统Fedora 6。如果VMware窗口中没有标签名为“clone of 6”的虚拟机,则点击菜单项“文件”->“打开”,找到 C:\ clone of 6目录下的clone of f6.vmx文件,并打开之,即可加载该虚拟机。
2. 源程序的编写。本实验中,ARM模板将作为服务器端,ARM_Linux软件开发所需的文件有:
zImage 即ARM_Linux系统的内核映像Image的压缩文档。关于ARM_Linux系统内核的配置、裁剪和编译不是本实验的内容,zImage是现成可用的。
initrd.gz 即ARM_Linux系统的根文件系统initrd的压缩文档。initrd(即initial RAM disk的缩写),是Linux在系统引导过程中挂载的一个临时根文件系统,用来支持两阶段的引导过程。initrd文件中包含了各种可执行程序和驱动程序,它们可以用来挂载实际的根文件系统,然后再将这个initrd RAM disk卸载,并释放内存。但在嵌入式Linux系统中,initrd往往直接作为最终的根文件系统。在本实验中,需要将编译好的可执行程序加入到initrd中,需要对其进行修改和重新压缩。
server.c 未完成的ARM_Linux下的服务器实验程序,该程序已提供主体框架,实验中需要编写缺失的部分,以实现与上位机(PC机)客户端程序之间的数据通讯。
my_route.h server.c中网络参数配置所需的头文件,无需编辑。
Makefile 编译server.c的所需的编译信息文件(纯文本文件)。Makefile文件相当于编译行为的指导文件,即告诉编译器如何编译,其中描述了相应的C程序文件的编译、链接等规则,其中包括:哪些源文件需要编译以及如何编译,需要创建哪些库文件以及如何创建这些库文件,如何产生我们想要的可执行文件等等。
首先,在VMware中Fedora 6系统的/home目录下新建一个目录,并将Windows下的initrd.gz、server.c、my_route.h、Makefile四个文件拷贝到其中。
然后在Fedora 6系统下双击server.c,自动打开一个文本编辑器gedit,接着进行程序代码的编写(也可用Windows下的任意的文本编辑器来编写源程序(如下图),写好后再拷入Fedora 6系统下的相应目录中)。相关的Linux库函数用法可参见附录。
图 server.c程序编写
3. Makefile文件的编辑。鼠标双击打开Makefile文件,做如下修改:
EXEC = server
SOURCE = server.c
4. 源程序的编译。由于该程序运行的目标平台是ARM模板,而非PC机,因此这里需要使用交叉编译器(已经配置好了)。在VMware的Fedora 6系统下打开一个终端窗口,进行命令行的操作模式。
在光标后输入命令:
cd /home/新建的目录名 切换到新建的目录下
接着输入命令:
make 系统将按照当前目录下的Makefile中的信息对相应的源程序进行编译。
编译正确后,会在当前目录下生成一个可执行文件server。(注意:Linux的可执行文件没有后缀名)
5. ARM-Linux根文件系统initrd的修改与重压缩。
双击initrd的压缩文档initrd.gz,解压至当前目录。
在终端窗口下输入命令:
mount -o loop initrd /mnt/initrd/ 挂载根文件系统initrd至目录/mnt/initrd/下
在文件浏览器中将新生成的可执行文件server拷贝到/mnt/initrd/sbin 下
进入 /mnt/initrd/etc/init.d 目录,找启动脚本文件rcS,点击右键选择脚本编辑器KDevelop打开之,在最后追加如下两行命令,然后保存。这使得ARM_Linux系统启动后自动运行server程序文件。
cd /sbin
./server
在终端窗口下输入命令:
umount /mnt/initrd 卸载根文件系统initrd
在终端窗口下输入命令:
gzip -9 initrd 压缩根文件系统initrd为initrd.gz
至此,ARM-Linux的程序设计工作已完成,将新构建的initrd.gz拷贝到Windows下的zImage所在的调试目录下。
6. 上位机与下位机的TCP网络通讯测试。步骤如下:
(1)连接实验平台。用串口线和交叉网线将实验平台的ARM模块与PC机的对应接口连接,然后接通实验平台的电源线。
(2)打开超级终端,如下图所示。
【注】如果Windows系统中没有超级终端,则可使用“调试”文件夹里的hypertrm.exe 。
首次设置超级终端将会出现如下图所示的对话框,点击“确定”。
(3)设置超级终端。
首次设置超级终端将会出现如下图所示的对话框,输入任意的区号(如028),点击“确定”。
接着出现如下图所示的对话框,直接点击“确定”。
接着出现如下图所示的对话框,输入任意名称,如 9200 。点击“确定”。
接着出现如下图所示的对话框,选择串口(默认为COM1)。直接点击“确定”。
接着出现如下图所示的对话框,设置属性,请按下图设置为:115200,8,无,1,无。点击“确定”。
至此,超级终端的设置完毕。
【注】如果出现下图所示的警告,是因为虚拟机正在使用串口。
找到虚拟机窗口的右下角的串口图标,如下图所示。鼠标右键点击该图标,选择“断开连接”。
(4)实验平台通电或按下ARM板上的复位按钮,可以看到ARM板上的指示灯闪亮。此时,超级终端中将显示ARM的启动过程(说明:超级终端作为ARM的控制台,起到键盘和显示器的作用)。
在时间提示出现时马上按下回车键,进入Uboot命令行模式,如下所示。(说明:Uboot类似于PC机的BIOS程序,用于引导和启动ARM系统,并提供了一些简单的操作命令)
(5)Uboot网络参数的设置。注意:PC机必须和ARM模块在一个网段(IP地址的前三个数相同,最后一个数不同)
首先,PC机的IP地址可在Windows桌面的网上邻居的属性中设置。
然后,在超级终端窗口的Uboot命令行中输入如下命令:
printenv (查看当前的Uboot环境参数的设置情况)
setenv ipaddr IP地址 (设置ARM模块的Uboot系统的IP地址,要求与PC机在同一网段,且与server.c 中设置的IP地址不同)
setenv serverip 服务器IP地址 (服务器IP地址就是PC机本身的IP地址)
(6)在PC上运行一个TFTP服务器程序,以便用TFTP协议与ARM模块上的Uboot进行网络通讯,本实验采用的是TFTPD32。运行TFTPD32后如下图所示。
点击“Browse”设置当前目录为 文件zImage 和initrd.gz 所在的目录,“Server interfaces”为PC机与ARM模块连接的网口的IP。
(7)输入命令(区分大小写):
tftp 20008000 zImage ,传输Linux内核到SDRAM的20008000地址。
(8)输入命令:
tftp 20700000 initrd.gz ,传输Linux文件系统到SDRAM的20700000地址。
(9)输入命:
go 20008000 ,从内核的起始地址20008000开始运行ARM_Linux系统。
(10)运行上位机测试程序client.exe,输入ARM模板的IP地址(在ARM_Linux程序server.c中设置的),点击“连接”与ARM模块建立TCP连接,然后发送字符串,验证接收到的字符串是否正确,如下图所示。
同时,在超级终端中观察ARM模块显示的信息。
七. 思考题
1. 本实验中为什么要使用交叉网线,而不是通常所用的直连网线?
答:因为直连网线一般只在将交换机或HUB与路由器连接或计算机(包括服务器和工作站)与交换机或HUB连接时使用,而交叉网线主要用于同类端口之间的连接,比如, 两台PC直接相连(网卡对网卡)相连时使用。
2. 在编译server.c 时,除了用make命令,还可以用什么命令?
还可以使用 %cc server.c进行编译。
3. 是否可以发送中文?
不可以,arm中无法识别显示中文字符。附 录
实验相关的Linux库函数:
memset(将一段内存空间填入某值)
头文件
#include < string.h >
定义函数
void * memset ( void * s , int c, size_t n ) ;
函数说明
memset ( ) 会将参数s所指的内存区域前n个字节以参数c填入,然后返回指向s的指针。在编写程序时,若需要将某一数组作初始化,memset ( ) 会相当方便。
返回值
返回指向s的指针。
附加说明
参数c虽声明为int , 但必须是unsigned char ,所以范围在0到255之间。
范例
#include < string.h >
main ( )
{
char s[30];
memset ( s, 'A', sizeof ( s ) ) ;
s[30] = '\0';
printf ( "%s\n", s ) ;
}
执行
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
recv(经socket接收数据)
头文件
#include < sys/types.h >
#include < sys/socket.h >
定义函数
int recv ( int s, void * buf, int len, unsigned int flags ) ;
函数说明
recv ( ) 用来接收远端主机经s指定的socket传来的数据,并把数据存到由参数buf 指向的内存空间,参数s为已建立好连接的socket,参数len为可接收数据的最大长度。
参数
flags 一般设0,其它数值定义如下:
MSG_OOB 接收以out-of-band 送出的数据(接收带外数据)。
MSG_PEEK 返回来的数据并不会在系统内删除,如果再调用recv ( ) 会返回相同的数据内容。
MSG_WAITALL 强迫接收到len大小的数据后才能返回,除非有错误或信号产生。
MSG_NOSIGNAL 表示不愿被SIGPIPE信号中断,成功则返回接收到的字符数,失败返回 -1,错误原因存于errno中。
MSG_DONTWAIT 本操作设置为非阻塞方式
错误代码
EBADF 参数s非合法的socket处理代码
EFAULT 参数中有一指针指向无法存取的内存空间
ENOTSOCK 参数s为一文件描述词,非socket。
EINTR 被信号所中断
EAGAIN 此动作会令进程阻断,但参数s的socket为不可阻断
ENOBUFS 系统的缓冲内存不足。
ENOMEM 核心内存不足
EINVAL 传给系统调用的参数不正确。
范例
#include < sys/types.h >
#include < sys/socket.h >
#include < netinet/in.h >
#include < arpa/inet.h >
#include < unistd.h >
#define PORT 1234 //服务器的端口号
#define MAXSOCKFD 10 //最大连接数
main ( )
{
int sockfd, newsockfd, is_connected[MAXSOCKFD], fd;
struct sockaddr_in servaddr;
int addr_len = sizeof ( struct sockaddr_in ) ;
fd_set readfds;
char buffer[256];
char msg[ ] = "Welcome to server!";
if ( ( sockfd = socket ( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
{
perror ( “socket” ) ;
exit ( 1 ) ;
}
bzero ( & servaddr, sizeof (servaddr) ) ;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons ( PORT ) ;
servaddr.sin_addr.s_addr = htonl ( INADDR_ANY ) ; //INADDR_ANY表示本地任意可用的IP地址
if ( bind ( sockfd, & servaddr, sizeof (servaddr) ) < 0 )
{
perror ( "connect" ) ;
exit ( 1 ) ;
}
if ( listen ( sockfd, 3 ) < 0 )
{
perror ( "listen" ) ;
exit ( 1 ) ;
}
for ( fd = 0; fd < MAXSOCKFD; fd ++ ) //清除连接状态标志
is_connected[fd] = 0;
while ( 1 )
{
FD_ZERO ( &readfds ) ;
FD_SET ( sockfd, &readfds ) ;
for ( fd = 0; fd < MAXSOCKFD; fd ++ )
if ( is_connected[fd] )
FD_SET ( fd, &readfds ) ;
if ( !select ( MAXSOCKFD, &readfds, NULL, NULL, NULL ) ) continue;
for ( fd = 0; fd < MAXSOCKFD; fd ++ ) //判断是否有新连接或新信息进来
if ( FD_ISSET ( fd, &readfds ) )
{
if ( sockfd == fd )
{
if ( ( newsockfd = accept ( sockfd, &addr, &addr_len ) ) < 0 )
perror ( “accept failed!” ) ;
write ( newsockfd, msg, sizeof ( msg ) ) ;
is_connected[newsockfd] = 1;
printf ( “Connect from %s\n”, inet_ntoa ( addr.sin_addr ) ) ;
}
else
{
bzero ( buffer, sizeof ( buffer ) ) ;
if ( read ( fd, buffer, sizeof ( buffer ) ) < = 0 )
{
printf ( “connect closed.\n” ) ;
is_connected[fd] = 0;
close ( fd ) ;
}
else
printf ( “%s”, buffer ) ;
}
}
}
}
执行
$ ./listen
connect from 127.0.0.1
hi I am client
connected closed.
send(经socket传送数据)
头文件
#include < sys/types.h >
#include < sys/socket.h >
定义函数
int send ( int s, const void * msg, int len, unsigned int falgs ) ;
函数说明
send ( ) 用来将数据由指定的socket 传给对方主机,参数s为已建立好连接的socket,参数msg指向欲发送的数据内容,参数len则为数据长度。参数flags一般设0,其它数值定义如下:
MSG_OOB 传送的数据以out-of-band 送出(传输带外数据)
MSG_DONTROUTE 无需路由表查找(适用于本地局域网)
MSG_DONTWAIT 本操作设置为非阻塞方式
MSG_NOSIGNAL 表示不愿被SIGPIPE 信号中断
返回值
成功则返回实际传送出去的字符数,失败返回 -1。错误原因存于errno
错误代码
EBADF 参数s 非合法的socket处理代码。
EFAULT 参数中有一指针指向无法存取的内存空间
ENOTSOCK 参数s为一文件描述词,非socket。
EINTR 被信号所中断。
EAGAIN 此操作会令进程阻断,但参数s的socket为不可阻断。
ENOBUFS 系统的缓冲内存不足
ENOMEM 核心内存不足
EINVAL 传给系统调用的参数不正确。
范例
/* 利用socket的TCP client
此程序会连线TCP server,并将键盘输入的字符串传送给server。
TCP server范例请参考listen()。
*/
#include < sys/stat.h >
#include < fcntl.h >
#include < unistd.h >
#include < sys/types.h >
#include < sys/socket.h >
#include < netinet/in.h >
#include < arpa/inet.h >
#define PORT 1234 //服务器的端口号
#define SERVER_IP “127.0.0.1” //服务器的IP地址
main ( )
{
int s;
struct sockaddr_in servaddr;
char buffer[256];
if ( ( s = socket ( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
{
perror ( “socket failed!” ) ;
exit ( 1 ) ;
}
/* 填写sockaddr_in结构 */
bzero ( & servaddr, sizeof (servaddr) ) ;
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons ( PORT ) ;
servaddr.sin_addr.s_addr = inet_addr ( SERVER_IP ) ;
/* 尝试连线 */
if ( connect ( s, & servaddr, sizeof (servaddr) ) < 0 )
{
perror ( “connect failed!” ) ;
exit ( 1 ) ;
}
recv ( s, buffer, sizeof ( buffer ) , 0 ) ; //接收由服务器端传来的信息
printf ( “%s\n”, buffer ) ;
while ( 1 )
{
bzero ( buffer, sizeof ( buffer ) ) ;
read ( STDIN_FILENO, buffer, sizeof ( buffer ) ) ; //从标准输入设备取得字符串
if ( send ( s, buffer, sizeof ( buffer )
展开阅读全文