综合资讯 技术文章 原文阅读 在线商城 下载专区 DATASHEET 技术论坛 商务频道

电子技术 | 技术资料 | 嵌入式系统 | 单片机专题 | DSP专题
EDA/PLD专题 | 电源技术专题 | 电子制作专题 | 其他综合 | 芯片选型

所在的位置:首页在线阅读嵌入式系统ARM应用系统开发详解正文
 
第7章 嵌入式uClinux及其应用开发(2)

 

7.3.2  uClinux针对硬件的改动

目前,uClinux已被成功移植到S3C4510B及其他多款ARM芯片上,但由于嵌入式操作系统的运行是与嵌入式系统的硬件密切相关的,而硬件的设计则会因为使用场合的不同而千差万别,因此,在uClinux内核源代码中和硬件紧密相关的部分就应该针对特定的硬件作出适当的修改,由于uClinux内核源代码包含很大一部分的硬件驱动程序,不可能一一列举,在此,就基于S3C4510B的最小系统的设计与运行相关的部分作简单的介绍,希望对读者有所启发。

uClinux内核源代码中对S3C4510B片内特殊功能寄存器以及其他相关硬件信息的定义位于uClinux-Samsung\Linux-2.4.x\include\asm-armnommu\arch-samsung\hardware.h文件中,其中有几个地方值得注意:

/*

 * define S3C4510b CPU master clock

 */

#define MHz       1000000

#define fMCLK_MHz (50 * MHz)

#define fMCLK     (fMCLK_MHz / MHz)

#define MCLK2     (fMCLK_MHz / 2)

以上定义了系统工作的主时钟频率为50MHz,若用户系统的工作频率不同,应在此处修改,若串行口采用内部时钟信号用于波特率生成,该频率同时还与串行通信波特率有关。

/**********************************/

/* System Memory Control Register */

/**********************************/

#define DSR0      (2<<0)  /* ROM Bank0 */

#define DSR1      (0<<2)  /* 0: Disable, 1: Byte, 2: Half-Word, 3: Word */

#define DSR2      (0<<4)

#define DSR3      (0<<6)

#define DSR4      (0<<8)

#define DSR5      (0<<10)

#define DSD0      (2<<12) /* RAM Bank0 */

#define DSD1      (0<<14)

#define DSD2      (0<<16)

#define DSD3      (0<<18)

#define DSX0      (0<<20) /* EXTIO0 */

#define DSX1      (0<<22)

#define DSX2      (0<<24)

#define DSX3      (0<<26)

#define rEXTDBWTH (DSR0|DSR1|DSR2|DSR3|DSR4|DSR5 | DSD0|DSD1|DSD2|DSD3 | DSX0|DSX1|DSX2|DSX3)

以上定义了系统存储器控制寄存器,按照以上定义,ROM/SRAM/FLASH Bank0定义为16位数据宽度(事实上,ROM/SRAM/FLASH Bank0的数据宽度由B0SIZE[1:0]的状态决定),而ROM/SRAM/FLASH Bank1ROM/SRAM/FLASH Bank5禁用;DRAM/SDRAM Bank0定义为16位数据宽度,DRAM/SDRAM Bank1DRAM/SDRAM Bank3禁用;外部I/O组全部禁用;若用户系统的存储器系统配置不同,应在此处修改。

之后还做了其他一些改动,包括对ROM/SRAM/FLASH Bank0控制寄存器的设置,Flash容量的设置,DRAM/SDRAM Bank0控制寄存器的设置,SDRAM容量的设置等,这些设置均应该与用户系统对应。

7.3.3  编译uClinux内核

作为操作系统的核心,uClinux内核负责管理系统的进程、内存、设备驱动程序、文件系统和网络系统,决定着系统的各种性能。uClinux内核的源代码是完全公开的,任何人只要遵循GPL,就可以对内核加以修改并发布给他人使用,因此,在广大编程人员的支持下,uClinux的内核版本不断更新,新的内核修改了旧的内核的缺陷,并增加了许多新的特性,用户如果想在自己的系统中使用这些新的特性,或想根据自己的系统量身定制更高效、更稳定可靠的内核,就需要重新编译内核。一般说来,更新的内核版本会支持更多的硬件,具有更好的进程管理能力,运行速度会更快、更稳定,并且一般都会修复旧版本中已发现的缺陷等,因此,经常选择升级更新的系统内核是必要的。

uClinux内核采用模块化的组织结构,通过增减内核模块的方式来增减系统的功能,因此,正确合理的设置内核的功能模块,从而只编译系统所需功能的代码,会对系统的运行进行如下几个方面的优化:

                     用户根据自身硬件系统的实际情况定制编译的内核因为具有更少的代码,一般会获得更高的运行速度。

                     由于内核代码在系统运行时会常驻内存,因此,更短小的内核会获得更多的用户内存空间。

                     减少内核中不必要的功能模块,可以减少系统的漏洞,从而增加系统的稳定性和安全性。

uClinux的内核源代码可以从许多网站上免费下载,内核的发布一般有两种形式,一种是完整的内核版本,完整的内核版本一般是.tar.gz文件,使用时需要解压。另一种是通过对旧的版本发布补丁(patch),达到升级的效果。

本例所采用的在Linux下使用的交叉编译器和uClinux-Samsung-20020318.tar.gz源码均来自网站http://mac.os.nctu.edu.tw

在准备好uClinux的内核源代码后,利用交叉编译器就可以编译生成运行在硬件目标板上的uClinux内核。

http://mac.os.nctu.edu.tw上下载uClinux内核源代码uClinux-Samsung-20020318.tar.gz,保存到宿主机的用户目录。运行解压命令:

tar xzvf  uClinux-Samsung-250020318.tar.gz

解压完毕后,就会在用户目录下生成uClinux-Samsung目录,以下命令进入到该目录中:

$ cd uClinux-Samsung

1.     键入命令:

make menuconfig

内核配置。该命令执行完毕后生成文件.config,它保存这个配置信息。下一次再做make menuconfig的时候将产生新的.config文件,原来的.config被改名为.config.old

此时会出现菜单配置对话框,要求进行目标平台的选择,如图7.6所示,输入回车后,出现供选择的具体的供应商和产品列表,在这里我们选择:Samsung/4510B,如图7.7所示,在库的选择上,我们选择uC-libc,其他选项暂时不用修改,保存好设置后,存盘退出。

7.6  目标平台配置

7.7  选择合适的产品类型

2.键入命令:make dep

该命令用于寻找依存关系。

3.  键入命令:make clean

该命令清除以前构造内核时生成的所有目标文件,模块文件和一些临时文件。

1.  键入命令:make lib_only

该命令编译库文件。

2.  键入命令:make user_only

该命令编译用户应用程序文件。

3.  键入命令:make romfs

该命令生成romfs文件系统。

4.  键入命令:make image

注意做到这一步的时候可能会出现错误的信息提示,类似于:

arm-elf-objcopy: /home/nie/uClinux-Samsung/linux-2.4.x/linux: No such file or directory

make[1]: *** [image] Error 1

make[1]: Leaving directory `/home/nie/uClinux-Samsung/vendors/Samsung/4510B'

make: *** [image] Error 2

这是因为第一次编译时还没有romfs.o,所以出错,等romfs.o编译好了以后,如果再进行内核的编译,就不会出现这个错误信息了。它完全不影响内核的编译,可以完全不必理会这个错误信息。继续进行编译工作。

5.  键入命令:make

通过各个目录的Makefile文件进行,会在各目录下生成一大堆目标文件。

上述步骤完成后,就完成了对uClinux源码的编译工作。整个编译过程视计算机运行速度而定,大约需要十几分钟左右。

在编译内核的时,建议在Linux平台下进行。

7.3.4  内核的加载运行

当内核的编译工作完成之后,会在/ uClinux-Samsung/images目录下看到两个内核文件:image.ramimage.rom,其中,可将image.rom烧写入ROM/SRAM/FLASH Bank0对应的Flash存储器中,当系统复位或上电时,内核自解压到SDRAM,并开始运行。

image.ram可直接在系统的SDRAM中运行,使用ADS(或SDT)集成开发环境将系统的SDRAM映射到起始地址为0x0处,并将image.ram载入从0x8000开始的SDRAM中,加载完毕后,修改PC指针寄存器的值为0x8000并执行。

注意该内核默认串行口COM1为输入输出控制台,波特率为192008个数据位,1个停止位,无校验。

7.4 uClinux下开发应用程序

当完成了上述所有工作后,一个嵌入式应用开发平台就已经搭建好了,在这个平台之上,就可以根据不同需要开发嵌入式应用了。图7.8所示为一个基于uClinux 的嵌入式系统典型框架结构,下面将向读者介绍如何将自己开发的应用程序添加到目标板上运行。

 

7.8  基于uClinux嵌入式系统框图

基于uClinux系统的应用程序的开发通常是在标准Linux平台上(本书已经介绍了适用于Windows环境的交叉编译器,所以也可以在Windows平台)用交叉编译工具来完成。由于uClinux是为没有内存管理单元(MMU)的处理器和控制器而设计的,并做了较大幅度的精简,所以可能出现这样的情况:在标准Linux下可以使用的某些函数在uClinux下却用不了,这个时候,就需要用户编写相应的库函数了。当然绝大多数的函数它们都还是通用的。除此以外,在x86版本的gcc编译器下编译通过的软件,通常不需要做太大的改动就可以用刚才我们建立的交叉编译工具编译成可以在uClinux上运行的文件格式。因此开发在uClinux 下运行的程序,基本上就和开发在Linux下运行的程序是一样的,关于Linux下的编程,读者可以参考其他更详细的资料,以下就一个简单的例子,描述其基本开发过程。

考虑一个定时中断的例子,文件名为lednxy.c,其源代码如下:

/*******************************************************

 * Institute of Automation, Chinese Academy of Sciences

 * File Name    lednxy.c

 * Description  timing interrupt    

 * Author       Xueyuan Nie

 * Date    

 *******************************************************/

#include <signal.h>

#include <unistd.h>

#define IOPMOD   (*(volatile unsigned *)0x3ff5000)

#define IOPDATA  (*(volatile unsigned *)0x3ff5008)

int i=0;

static void sig_alarm(int signumber)

{

   if(i==3) i=0;

   IOPDATA=i++;

   alarm(2);

}

 

int main(void)

{

  IOPMOD=0xff;

  if(signal(SIGALRM,sig_alarm)==SIG_ERR)

  {

      printf(“some error occurs\n”);

        return  1;

  }

  alarm(2);

  while(1);

  return 0;

}

在代码中,SIGALRM为系统定义的信号的名字,在头文件里被定义为一个正整数,用户自定义函数sig_alarm()为信号处理函数, 系统函数alarm()用来设定一个2秒的定时器,当定时器时间片终止的时候,进程将会产生SIGALRM信号,在程序中用函数signal()实现了信号SIGALRM和信号处理函数sig_alarm()的连接,这样,当用alarm()函数设置时钟的时间段终止时,就会有SIGALRM信号产生,程序就会转而执行函数sig_alarm(),从而实现每隔2秒钟,I/O口数据寄存器的值发生一次变化,达到控制LED等的目的。有关signal()函数和alarm()函数的使用,读者可以查阅有关在Linux上的C编程方面的内容,本书在此不作详述。

该程序达到的效果就是,让目标硬件上的P0P1口的两个LED显示器按照P0亮,P1亮,P0P1全亮的顺序,每隔2秒实现其中的一种状态。

在装有标准Linux的宿主机(或装有CygwinwindowsPC)上,用前面已经建立好的交叉编译工具编译源文件,在该程序所在的目录下键入如下命令:

arm-elf-gcc –Wall –O2 –Wl,-elf2flt –o lednxy  lednxy.c

仍然在该目录下,键入命令:

ls

可以查看到在该目录下生成了文件名为lednxy的文件。

在键入的编译命令中,选项

-Wall  指定产生全部的警告信息;

-O2   是一个二级优化选项,它表示告诉编译器产生尽可能小和尽可能快的代码;

-Wl   的一般用法是’’-Wl,option’’ 就是把它后面的选项传递给链接器,在本命令中就是把’’- elf2flt ‘’传给链接器;

-elf2flt  指定自动调用elf转换flat格式的工具;之所以要使用该选项是因为,由于GNU工具本身并不支持flat格式的二进制文件,然而,uClinux目前只支持flat格式的可执行文件,因此必须使用相应的二进制工具进行格式转换。flat格式是对elf格式的很大的文件头和一些段信息做了简化的文件格式。

编译成功后得到的lednxy就可以在uClinux环境上运行了。关于如何将生成的可执行代码加入到uClinux,将在后面的章节讲述。

除了以命令行的形式进行代码编译外,我们还可以利用前面提到的makefile的知识,用makefile文件实现代码编译的功能。

下面给出本例相应的makefile文件(该文件名为makefile)

CFLAGS  = -Wall –Os –Dlinux –D__linux__ -Dunix –D__uClinux__ -DEMBED

LDFLAGS = -Wl,-elf2flt

CC       = arm-elf-gcc

LD       = arm-elf-gcc

TARGT   = lednxy

OBJ      = $(TARGT).o

SRC      = $(TARGT).c

all: $(TARGT)

%.o : %.c

        $(CC) $(CFLAGS) –c $< -o $@

$(TARGT): $(OBJ)

        $(CC) $(CFLAGS) $(LDFLAGS) –o $@ $(OBJ)

整个编译过程如下:

[nie@uClinux usr]$ make   

arm-elf-gcc –Wall –Os –Dlinux –D__linux__ -Dunix –D__uClinux__ -DEMBED  -c lednxy.c –o lednxy.o

arm-elf-gcc –Wall –Os –Dlinux –D__linux__ -Dunix –D__uClinux__ -DEMBED  -Wl,-elf2flt –o lednxy lednxy.o

可以用工具arm-elf-flthdr查看生成的lednxy的格式,它是一个能够操作和显示flat格式文件的头信息的可执行程序。在生成lednxy的当前路径下键入命令:

arm-elf-flthdr lednxy

后,可以看到以下对该文件头描述的信息,

lednxy

Magic:        bFLT

Rev:          4

Build Date:    Thu Jun 19 10:31:14 2003

Entry:         0x50

Data Start:     0x1c80

Data End:      0x2010

BSS End:      0x22a0

Stack Size:     0x1000

Reloc Start:    0x2010

Reloc Count:   0x4f

Flags:         0x1 ( Load-to-Ram )

从显示的信息,可以看出文件lednxy的确是一个flat格式的文件,是可以在uClinux环境下运行的。

7.4.1  串行通信

所谓串行通信就是在传输数据的时候每次只传输一位,其传输的速率通常用“位/秒”来表示,即通常所说的“波特率”。

Linux对所有各类设备文件的输入输出操作,看上去就像对普通文件的输入输出一样,所以Linux对串口的操作,也是通过设备文件访问的。为了访问串口,只需要打开相应的设备文件即可。设备文件/dev/ttyS*是用于挂起Linux终端的文件。默认地,在Linux下,串行口COM1COM2对应的设备分别为/dev/ttyS0/dev/ttyS1

在程序中,很容易配置串口的属性,这些属性定义在结构体struct termios中。为在程序中使用该结构体,需要包含文件<termbits.h>,该头文件定义了结构体struct termios

#define NCCS 19

struct termios {

        tcflag_t c_iflag;               /* input mode flags */

        tcflag_t c_oflag;               /* output mode flags */

        tcflag_t c_cflag;               /* control mode flags */

        tcflag_t c_lflag;               /* local mode flags */

        cc_t c_line;                    /* line discipline */

        cc_t c_cc[NCCS];                /* control characters */

};

下面对结构体中的各个成员做一个简单介绍。

c_iflag中的输入模式标志符控制所有的输入处理过程,就是说,从设备发送的字符在被read函数读取之前要经过处理。类似的,成员c_oflag控制输出处理过程,c_cflag包含对端口的设置,如,波特率,字符位数,停止位等。存储在成员c_lflag的本地模式标志符决定是否显示字符,是否发送信号到应用程序等。数组c_cc包含了控制字符的定义和超时参数。成员c_linePOSIX(Portable Operating System Interface for UNIX)系统中不使用。

下面结合一个简单的实例,说明如何对串口进行读写操作。

/*******************************************************

 * Institute of Automation, Chinese Academy of Sciences

 * File Name    serialcomm.c

 * Descriptioncommunication with serial

 * Author       Xueyuan Nie

 * Date    

 *******************************************************/

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <termios.h>

#include <stdio.h>

#define BAUDRATE B19200          

#define SERIALDEVICE "/dev/ttyS0"

int main()

{

          int fd,ncount;

          struct termios oldtio,newtio;

          char buf[]="This is a simple application for serial communication\r\n";

fd = open(SERIALDEVICE, O_RDWR | O_NOCTTY );

         if (fd <0)

           {

perror(SERIALDEVICE);

exit(-1);

 }

         tcgetattr(fd,&oldtio);

         bzero(&newtio, sizeof(newtio));

         newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;

         newtio.c_iflag = IGNPAR | ICRNL;

         newtio.c_oflag = 0;

         newtio.c_lflag = ICANON;

         tcflush(fd, TCIFLUSH);

        fcntl(fd,F_SETFL,0);

         tcsetattr(fd,TCSANOW,&newtio);

         ncount=write(fd,buf,sizeof(buf));

        printf("the bytes written to serial is %d\n",ncount);

printf("character to send is: %s\n",buf);

        perror("write");

        tcsetattr(fd,TCSANOW,&oldtio);

         close (fd);

        return 0;

}

程序首先为波特率常数定义了宏值,为设备文件定义了设备名常数。有关波特率常数的定义可参见<termbits.h>(该头文件包含在termios.h)

对于普通用户而言,是不允许访问设备文件的,如果要访问,要么以root账号登录,要么需要改变文件的访问属性。假定设备文件是可以访问的,用open函数打开设备文件,返回一个文件描述符(file descriptors,fd),通过文件描述符来访问文件。O_RDWR标志表示对该文件可读可写,O_NOCTTY表示该程序不会成为控制终端,这样就避免了当在键盘输入类似ctrl+c的命令后,终止程序的运行。

然后用tcgetattr保存串口的当前设置,给端口设置新的属性,通过对c_cflag的赋值,设置波特率,字符大小(CS8表示8位数据位,1位停止位,没有奇偶校验位),使能本地连接,使能串行口驱动读取输入数据。

通过设置c_iflag ,控制端口对字符的输入处理过程,IGNPAR符号常量表示忽略奇偶性错误的字节,并不对输入数据进行任何校验,ICRNL 将回车符映射为换行符。

设置原始数据输出,使能规范输入。

在对struct termios结构体的各个成员赋值完毕后,调用tcsetattr函数选择新的设置,常数TCSANOW表示新设置立即生效。

调用write函数往串口发送数据,此时如果打开超级终端应该可以看到写入的字符串。对串口操作结束后,恢复原有的端口设置,关闭打开的设备文件。

以上是一个简单的对串口进行写操作的程序,因为通过超级终端来显示,所以没有调用read函数,如果接收数据的一端是其他设备的话,有可能需要读者再编写一个接收数据的程序,运行发送和接收程序的两台设备通过串行口进行连接。也可以将接收和发送的程序在同一台设备上运行,通过一根交叉线(就是将 TXD-数据传输信号和另一个端口的 RXD -接收数据信号相连起来)将两个串口接在一起。

下面就针对上述提到的情况,再举一个有关接收和发送数据的程序,通过串口交叉线的连接运行在不同设备(也可以是同一台设备)上的例子。

假设接收程序readtest.c运行在装有标准LinuxPC机上,发送程序writetest.c运行在目标板S3C4510B上,两台设备的串口通过交叉线连接在一起。

接收程序readtest.c的源码如下:

/*******************************************************

 * Institute of Automation, Chinese Academy of Sciences

 * File Name    readtest.c

 * Description  receive data from the serial

 * Author       Xueyuan Nie

 * Date    

 *******************************************************/

#include <stdio.h>

#include <string.h>

#include <malloc.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <termios.h>

#include "math.h"

 

int spfd;

int main()

{

 char fname[16],hd[16],*rbuf;

 int  retv,i,ncount=0;

 struct termios oldtio;

 int realdata=0;

 

spfd=open("/dev/ttyS1",O_RDWR|O_NOCTTY);

 perror("open /dev/ttyS1");

 if(spfd<0) return -1;

 

 tcgetattr(spfd,&oldtio);

 cfmakeraw(&oldtio);

 cfsetispeed(&oldtio,B19200);

 cfsetospeed(&oldtio,B19200);

 tcsetattr(spfd,TCSANOW,&oldtio);

 rbuf=hd;

printf("ready for receiving data...\n");

 

 retv=read(spfd,rbuf,1);

 if(retv==-1) perror("read");

  while(*rbuf!='\0')

  {

    ncount+=1;

    rbuf++;

    retv=read(spfd,rbuf,1);

    printf("the number received is %d\n",retv);

    if(retv==-1) perror("read");

   

  }

 for(i=0;i<ncount;i++)

 {

   realdata+=(hd[i]-48)*pow(10,ncount-i-1);

 }

 printf("complete receiving the data %d\n",realdata);

 

 close(spfd);

 return 0;

}

发送程序writetest.c的源码如下:

/*******************************************************

 * Institute of Automation, Chinese Academy of Sciences

 * File Name    writetest.c

 * Description  send data to serial

 * Author       Xueyuan Nie

 * Date    

 *******************************************************/

#include <stdio.h>

#include <string.h>

#include <malloc.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <termios.h>

int spfd;

 

int main(int argc, char *argv[])

{

  char fname[16],*sbuf;

  int sfd,retv,i;

  struct termios oldtio;

 

 spfd=open("/dev/ttyS1",O_RDWR|O_NOCTTY);

if(spfd<0)

{

perror("open /dev/ttyS1");

return -1;

 }

 printf("ready for sending data...\n");

 tcgetattr(spfd,&oldtio);

 cfmakeraw(&oldtio);

 cfsetispeed(&oldtio,B19200);

 cfsetospeed(&oldtio,B19200);

 tcsetattr(spfd,TCSANOW,&oldtio);

 

fname[0]='1';

fname[1]='2';

 fname[2]='3';

 fname[3]='\0';

 

sbuf=(char *)malloc(4);

 strncpy(sbuf,fname,4);

 retv=write(spfd,sbuf,4);

 if(retv==-1) perror("write");

 

 printf("the number of char sent is %d\n",retv);

 

close(spfd);

 return 0;

}

本例程实现:在发送端发送数字123,在接收端接收并显示接收到的数据。

这里请读者注意的是,发送方按字符发送数据,接收方将接收的字符相应的ascii值与字符0所对应的ascii值相减,最终得到实际的十进制数值。

按照前面介绍的方法编译程序,有关如何将可执行文件添加到目标板的方法将在下一小节介绍。

开始运行程序。先在装有Linux PC上运行接收程序,然后在S3C4510B上运行发送程序,整个运行的过程如下所示:

LinuxPC上:

root@uClinux nie]# ./recvtest &

[1] 2171

[root@uClinux nie]# open /dev/ttyS1: Success

ready for receiving data...

[root@uClinux nie]# the number received is 1

the number received is 1

the number received is 1

complete receiving the data 123

 

[1]+  Done                    ./recvtest

在目标板上:

/var/tmp>  ./writetest

ready for sending data...

the number of char sent is 4

这里所举的例子比较简单,旨在为读者介绍最基本的串行通信的步骤,读者可以此为基础,开发出满足自己需求的应用程序来。

7.4.2  socket编程

uClinux本身就是一个网络的产物,它可以从网上供人们自由免费的下载,正是通过很多爱好者利用网络修改,改善Linux,才得到我们现在的uClinux,所以没有网络可以说就看不到今天的uClinux。因此,在学习uClinux的时候,就不能不涉及到网络,而要掌握在uClinux下设计用户应用程序,就必须要学习有关uClinux下的网络编程。本节主要讲述当前在网络编程中被广泛使用的socket

socket一般被翻译为“套接字”,简而言之就是网络进程中的ID

其实网络通信,本质就是进程间的通信,在网络中,每个节点都有唯一一个网络地址,即通常说的IP地址,两个进程在通信的时候,必须首先要确定通信双方的网络地址。但是网络地址只能确定进程所在的PC机,然而同一台PC可能有好几个网络进程,只有网络地址是不能够确定到底是哪个进程,所以套接字还需要提供其他信息,那就是端口号,同一台PC机,一个端口号只能分配给一个进程。所以,网络地址和端口号结合在一起,才可以共同确定整个Internet中的一个网络进程。

套接字最常用的有两种:流式套接字(Stream Socket)和数据报套接字(Datagram Socket)。在Linux中,分别称为”SOCK_STREAM””SOCK_DGRAM”

这两种套接字的区别在于它们使用不同的协议。流式套接字使用TCP协议,数据报套接字使用的是UDP协议。

TCP(Transmission Control Protocol)传输控制协议,是TCP/IP体系中的运输层协议,是面向连接的,因而可提供可靠的,按序传送数据流,它的可靠是因为它使用三段握手协议来传输数据,并且采用“重发机制”确保数据的正确发送,接收端收到数据后要发出一个肯定确认,而发送端必须接收到接收端的确认信息后,否则发送端会重发数据。同时TCP是无错误传递的,有自己的检错和纠错机制,使用TCP协议的套接字是属于流式套接字。大家熟知的telnet就是使用的流式套接字。

UDP(User Datagram Protocol)用户数据报协议提供无连接的不可靠的服务,在传送数据之前不需要建立连接。远地主机在接收接收到UDP数据报后,不需要给出任何应答,这样的话,如果发送一个数据报,可能到达也可能丢失。如果发送多个包,到达接收端的次序可能是颠倒的。数据报套接字有时候也称为“无连接套接字”,大家熟悉的TFTPNFS使用的就是该协议。

大多数情况下,如果只是将数据包发送给给定地址的机器,是不能够确定到底把数据包发送给机器哪一个进程的,端口号的指定才能够更明确的指明。适用于通信的用户应用程序可以使用从165535的任何一个端口号,并将它分配给端口。这些号通常分成以下几个范围段:

端口0,不使用。如果传递的端口号是0,就会为进程分配一个10245000之间的一个没有使用的端口。

端口1255,保留给特定的服务,如FTP,远程网,FINGER等。

端口2561023,保留给别的一般服务如Routing function(路由函数)

端口10244999,可以被任意的客户机端口所使用,客户机套接字通常会使用这个范围段的端口。

端口500065535,为用户定义的服务器端口所使用。如果一个客户机需要事先知道服务器的端口,那么服务器套接字就应该使用这个范围的端口值。

下面结合一个具体的服务器端的例子,使读者熟悉socket编程的方法。

/*******************************************************

 * Institute of Automation, Chinese Academy of Sciences

 * File Name    comsamp.c

 * Descriptioncommunication with socket

 * Author       Xueyuan Nie

 * Date    

 *******************************************************/

 

#include <float.h>

#include <stdio.h>

#include <memory.h>

#include <stdlib.h>

#include <string.h>

#include <signal.h>   

#include <unistd.h>  

#include <netinet/in.h>

#include <netdb.h>

#include <sys/time.h> 

#include <sys/types.h>

#include <sys/socket.h>

 

/*=========*

 * Defines *

 *=========*/

#ifndef TRUE

#define FALSE 0

#define TRUE  1

#endif

 

#ifndef EXIT_FAILURE

#define EXIT_FAILURE  1

#endif

#ifndef EXIT_SUCCESS

#define EXIT_SUCCESS  0

#endif

 

#ifndef EXT_NO_ERROR

#define EXT_NO_ERROR 0

#endif

#ifndef EXT_ERROR

#define EXT_ERROR    1

#endif

 

#ifndef INVALID_SOCKET

#define INVALID_SOCKET -1

#endif

 

#ifndef SOCK_ERR

#define SOCK_ERR -1

#endif

/*==================================*

 * Global data local to this module *

 *==================================*/

typedef int SOCKET;

typedef struct ConnectData_tag {

    int     port;

  int     waitForStart;

    SOCKET    sFd;   /* socket to listen/accept on         */

    SOCKET    msgFd; /* socket to send/receive messages    */

} ConnectData;

 

ConnectData *CD;

int          i=0;

int          connectionMade = 0;

 

/*=================*

 * Local functions *

 *=================*/

void prompt_info(int signumber)  

{

  char src[]="this is a test for socket\n";

  int nBytesToSet=strlen(src);

   send(CD->msgFd, src, nBytesToSet, 0);

}

 

void init_sigaction(void)

{

   struct sigaction act;

   act.sa_handler=prompt_info;

   act.sa_flags=0;

   sigemptyset(&act.sa_mask); 

   sigaction(SIGPROF,&act,NULL);

}

 

void init_time(double t_usec)

{

    struct itimerval value;

    int int_usec;

    int_usec=(int)(t_usec*1000000);

    value.it_value.tv_sec=0;      

    value.it_value.tv_usec=int_usec;       value.it_interval=value.it_value;

    setitimer(ITIMER_PROF,&value,NULL);

}

 

int ModeInit(void)

{

    int error = EXT_NO_ERROR;