《计算机操作系统教程》课件-第9章_第1页
《计算机操作系统教程》课件-第9章_第2页
《计算机操作系统教程》课件-第9章_第3页
《计算机操作系统教程》课件-第9章_第4页
《计算机操作系统教程》课件-第9章_第5页
已阅读5页,还剩90页未读 继续免费阅读

下载本文档

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

文档简介

9.1应用程序编程接口概述9.2进程间通讯实现方法与实例9.3线程编程及实现方法应用程序编程接口(ApplicationProgrammingInterface,API)可定义为:由操作系统所支持的函数定义、参数定义以及消息格式的集合。应用程序可使用API函数的处理系统所提供的各项功能。例如,WindowsAPI函数提供了Windows所支持的所有系统服务的功能。9.1应用程序编程接口概述

Windows发展的早期阶段,编程工具还不像现在这样完善和方便。那时,Windows程序员可以使用的编程工具只有API函数,程序员的工作就是利用这些API函数,像“搭积木”一样构造出符合自己要求的程序。随着软件技术的不断发展,在Windows平台上出现了许多优秀的可视化编程环境或工具,例如,VisualC++、VisualBasic、VisualFoxPro、Delphi、C++Builder,等等。这些开发工具提供了大量的类库和控件,程序员可以采用“所见即所得”的编程方式来开发具有界面精美、功能完善的应用程序。这样一来,仿佛API的功能正在逐渐削弱。实际上则不然,这些类库和控件都是在WindowsAPI的基础上所构建的。类库的封装和控件的开发都可以屏蔽底层的技术细节,减少程序员的工作量和降低程序开发的复杂程度;但是它们只能提供Windows的一般功能,对于比较复杂和功能特殊的程序来说,单纯使用类库和控件是难以实现的,还必须直接使用API函数来编写。

Microsoft32位Windows平台上的API称为Win32API,它提供了相当丰富的函数,所有在32位Windows平台上运行的程序都可以调用这些函数。但是由于各平台功能的差异,有些函数只能用于特定的平台,例如安全函数只能在WindowsNT平台上使用。标准的Win32API函数总体上可以分成七类,分别为:

(1)窗口管理类。它为应用程序提供了创建和管理用户界面的方法,提供用户、键盘、鼠标等输入消息的处理手段。

(2)窗口通用控制类。通用控制库是conctl32.dll(通用控制库)支持的一个控制窗口集,应用程序通过调用这些控制,可以使用系统Shell提供的控制功能。

(3)Shell特性类。它为应用程序增强系统Shell功能提供了相应的手段。

(4)图形设备接口类。它为应用程序能够在显示器、打印机或其它图形设备上生成图形化的输出结果提供了必要的手段。

(5)系统服务类。它为应用程序提供了访问计算机资源以及底层操作系统特性的手段。例如访问内存、文件系统、设备、进程和线程。

(6)国际特性类。它包括输入方法编辑器函数(IME)、国家语言支持函数(NLS)以及Unicode和字符集支持函数。

(7)网络服务类。它用于网络上不同计算机之间的通信。传统的程序大都是运行在一台主机上的,它可以通过全局变量或者函数调用和本机上的其它模块或其它应用程序进行通讯。但是,这种方法对于网络上运行的程序或者开发分布式应用就无能为力了,此时必须使用进程间通讯(Interprocess

Communications,简称IPC)技术。

9.2.1管道

简单说来,管道就是连接一个程序的输出和另外一个程序的输入的单向通道,它是UNIX中一种古老的进程间通讯机制。9.2进程间通讯实现方法与实例了解管道的一个直观的例子是在命令中使用管道符,例如命令:

ls-1|greplinux|more

就使用了两个管道符,grep的输入来自于ls的输出,而grep的输出又作为more命令的输入使用。该命令的含义是在当前目录中查找名字中包含“linux”的目录或者文件,然后分屏显示。

管道可以分成两类:无名管道(简称管道)和FIFO(也称为命名管道)。二者之间主要的区别在于无名管道只能用于父、子进程之间的通讯,而FIFO则可以用于任何进程之间的通讯。

1.无名管道

所有的UNIX系统都支持管道的通讯机制。管道有两种限制:

(1)管道是半双工的,数据只能单向流动;

(2)管道只能在父子进程之间使用。

管道由系统调用pipe()产生,该函数的语法为

intpipe(intfd[2])

参数fd用来存放pipe()创建的管道的句柄。fd[0]用于读,fd[1]用于写。管道通常由父进程创建。父进程创建管道之后再fork一个进程,该管道就可用于这两个父子进程之间的通讯。父进程在fork一个子进程之后,该子进程就自动继承了父进程打开的文件句柄。因此,原则上来说,父子进程都可以读写该管道,可以进行双工通讯,但是由于没有提供锁定的保护机制,实际上数据只能单向传递,程序员需要决定数据的流向,从而在父进程和子进程两方分别关闭不需要的句柄。例如数据从父进程流向子进程的情况,管道如图9.1所示。图9.1父进程向子进程传递信息的管道图9.1中父进程和子进程中的fd[0]和fd[1]是相同的,本例中,父进程需要关闭读句柄fd[0],子进程需要关闭写句柄fd[1]。

管道建立之后,我们就可以像普通文件一样使用write()、read()函数对其进行读写操作了。下面是一个使用管道实现子进程向父进程传递信息的例子:

#include<stdio.h>

#include<unistd.h>

#include<sys/types.h>

intmain(void)

{

intfd[2],nbytes;

pid_tpid;

charstring[]=“Hello,world!\n”;

charreadbuffer[80];

pipe(fd);/*创建管道*/

if((pid=fork())==-1) /*创建子进程*/

{

perror(“fork”);

exit(1);

}

if(pid==0) /*子进程*/

{

close(fd[0]); /*关闭管道读句柄*/

write(fd[1],string,strlen(string));

/*向管道中写入数据*/

_exit(0);

}

else /*父进程*/

{

close(fd[1]); /*关闭管道写句柄*/

nbytes=read(fd[0],readbuffer,sizeof(readbuffer));

/*从管道中读取数据*/

printf(“ReceivedStrings:%s\n”,readbuffer);

}

return(0);

}本例中程序员必须自行关闭管道不需要的句柄。但是,因为管道最常见的使用方式是用于两个进程(比如s4hell命令)之间信息的通讯,所以标准I/O库(stdio.h)中为了实现这些操作而提供了两个函数popen和pclose,使用这两个函数,程序员就可以不必处理这样的细节了。popen函数原型为

FILE*popen(constchar*cmdstring,constchar*type)

该函数首先创建一个管道,再执行fork创建一个子进程,然后调用exec执行cmdstring指定的命令,并返回一个标准的文件指针。该文件指针根据type值进行定位:如果type是“r”,则链接到cmdstring的标准输出上;如果type是“w”,则链接到cmdstring的标准输入上。下面的程序实现ls|more的功能:

#include<stdio.h>

intmain(void)

{

FILE*in_fp,*out_fp;

charreadbuffer[80];

if((in_fp=popen(“ls”,“r”)==NULL))

{

perror(“popen”);

exit(1);

}

if((out_fp=popen(“more”,“w”)==NULL))

{

perror(“popen”);

exit(1);

}

while(fgets(readbuf,80,in_fp))

fputs(readbuf,out_fp);

pclose(in_fp);

pclose(out_fp);

return(0);

}我们已经看到,使用管道实现进程间通讯是通过内核进行交互的,因此速度受到一定的限制。无名管道只能用于具有父子关系的进程之间的通讯,为了摆脱这种限制,必须使用其它的通讯方法。

2.FIFO

实际上,无名管道在系统内部是以Inode节点的方式来存放的,但是对于外部用户来说,它是不可见的,因此不能创建新的文件句柄对其进行访问。而命名管道则不同,它是在文件系统中确实存在的一个特殊文件,具有普通文件的优点,可以方便地进行读写,因此就可以方便地实现任意两个进程之间的通讯。

在系统shell中,我们可以使用下面的命令来创建命名管道:

mknodFIFOnamep

mkfifo-m0666FIFOname

而在C语言中,我们可以使用mknod函数或者makefifo函数创建命名管道,函数原型如下:

intmknod(char*pathname,mode_tmode,dev_tdev)

intmkfifo(constchar*pathname,mode_tmode)

命名管道一旦创建,在使用前必须使用fopen函数打开,然后就可以像普通文件一样进行读写操作了。命名管道支持自动阻塞,也就是说,为只读打开的命名管道要一直阻塞到有其它进程为写打开之时,反之亦然。如果需要,可以在打开命名管道时使用O_NONBLOCK标志来关闭其自动阻塞的特性。通过读/写公开的FIFO,进程之间就可以实现通讯,从而也可以开发C/S模式的应用程序。下面的程序片断说明了FIFO的使用:

服务器端程序:

FILE*in_file;

charbuffer[80];

in_file=fopen(“FIFOname”,“r”);

if(in_file==NULL)

{

perror(“fopen”);

exit(1);

}

fread(buffer,1,80,in_file);

printf(“DatarecievedfromFIFO:%s\n”,buffer);

fclose(in_file);客户端程序:

FILE*out_file;

charbuffer[80];

out_file=fopen(“FIFOname”,“w”);

if(out_file==NULL)

{

perror(“fopen”);

exit(1);

}

sprintf(buffer,“Testdatafornamedpipe!\n”);

fwrite(buffer,1,80,out_file);

printf(“DatarecievedfromFIFO:%s\n”,buffer);

fclose(out_file);

命名管道只能用于同种文件系统中的进程之间的通讯,在使用的过程中要注意其独立性问题,也就是说操作不能被其它进程打断。一次独立操作可以传送的最大字节数在POSIX标准中是在/usr/include/bits/posix1_lim.h中定义的:

#define_POSIX_PIPE_BUF512在Linux中,由/usr/include/linux/limits.h定义:

#definePIPE_BUF4096

如果进行通讯的进程需要写入管道的数据超过这个限制,就必须将其分割成几个独立的操作过程。在C/S模式的应用程序中,多个客户端都可以对命名管道进行读写,服务器端必须能够正确区分这些客户端的操作。9.2.2SystemVIPC机制

UnixSystemV中引入了三种新的IPC机制:消息队列(MessageQueue)、信号量(Semaphores)和共享内存(SharedMemory)。这三种IPC机制有很多相似之处,你可以使用ipcs命令来查看当前系统中这三种IPC的使用情况。

消息队列、信号量和共享内存在系统中都有一个惟一的标志符(标志符是一个非负、递增的数字,达到最大值时再从0开始计数),内核根据这个标志符来区分这些IPC对象。在程序中,我们需要使用关键字(key)来访问它们。关键字可以使用ftok系统调用来生成,函数原型如下:

key_tftok(char*pathname,charproj)进行通讯的进程必须使用同一个关键字,只要二者运行的目录(由参数pathname指定)是相同的,就可以保证ftok产生的关键字相同。

这三种IPC对象和普通文件不同,不能按照普通文件进行读写或者设置权限。系统为每个IPC对象都设置了一个ipc_perm结构,该结构说明了这个IPC对象的权限和属主信息。该结构创建之后,对于这三种IPC对象,可以分别使用msgctl、segctl和shmctl对该结构进行修改。ipc_perm结构的定义在linux/ipc.h中,如下所示:

structipc_perm

{

__kernel_key_tkey;

__kernel_uid_tuid;

__kernel_gid_tgid;

__kernel_uid_tcuid;

__kernel_gid_tcgid;

__kernel_mode_tmode;

unsignedshortseq;

}

1.消息队列

消息队列把进程之间传递的消息(structmsg结构的数据)以链表的形式组织在一起,进行通讯的写方进程经过权限认定之后,把需要传递的数据追加在队列的尾部,读方进程就可以从该队列中读取需要的数据,从而实现多个进程之间的通讯。每个消息队列都有一个structmsqid_ds(在linux/msg.h中定义)的结构和它相关,该结构反映消息队列的当前状态:

structmsqid_ds{

structipc_permmsg_perm;

structmsg*msg_first;/*firstmessageonqueue,unused*/

structmsg*msg_last;/*lastmessageinqueue,unused*/

__kernel_time_tmsg_stime;/*lastmsgsndtime*/

__kernel_time_tmsg_rtime;/*lastmsgrcvtime*/

__kernel_time_tmsg_ctime;/*lastchangetime*/

unsignedlongmsg_lcbytes;/*Reusejunkfieldsfor32bit*/

unsignedlongmsg_lqbytes;/*ditto*/

unsignedshortmsg_cbytes;/*currentnumberofbytesonqueue*/

unsignedshortmsg_qnum;/*numberofmessagesinqueue*/

unsignedshortmsg_qbytes;/*maxnumberofbytesonqueue*/

__kernel_ipc_pid_tmsg_lspid;/*pidoflastmsgsnd*/

__kernel_ipc_pid_tmsg_lrpid;/*lastreceivepid*/

}和消息队列的使用有关的系统调用有四个:msgget、msgsnd、msgrcv和msgctl,其函数原型如下:

intmsgget(key_tkey,intmsgflg)

intmsgsnd(intmsqid,structmsgbuf*msgp,intmsgsz,intmsgflg)

intmsgrcv(intmsqid,structmsgbuf*msgp,intmsgsz,longmsgtyp,intmsgflg)

intmsgctl(intmsqid,intcmd,structmsqid_ds*buf)

msgget用于打开或者创建一个消息队列;msgsnd把msgp中的数据发送到队列中;msgrcv从队列中读取数据;msgctl对消息队列执行cmd指定的操作。使用消息队列我们可以传递自定义的数据类型。下面是linux/msg.h中对消息类型的定义:

structmsgbuf{

longmtype; /*消息类型*/

charmtext[1]; /*消息内容*/

}该结构中的mtype和msgrcv中的msgtype参数意义相同,用来定义消息的类型。通过指定msgtype参数,msgrcv就可以接收指定类型的消息;也就是说,消息队列并不是先进先出的。另外,我们也可以自行定义数据类型。下面的例子简单说明了消息队列的用法:

#include<stdio.h>

#include<stdlib.h>

#include<ctype.h>

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

structmymsgbuf{

longmtype;

charmtext[80];

};

voidsend_message(intqid,structmymsgbuf*qbuf,longtype,char*text);

voidread_message(intqid,structmymsgbuf*qbuf,longtype);

voidremove_queue(intqid);

voidchange_queue_mode(intqid,char*mode);

voidusage(void);

intmain(intargc,char*argv[])

{

key_tkey;

intmsgqueue_id;

structmymsgbufqbuf;

if(argc==1)

usage();

key=ftok(“.”,′m′);/*创建关键字*/

if((msgqueue_id=msgget(key,IPC_CREAT|0660))==-1){

/*打开队列*/

perror(“msgget”);

exit(1);

}

switch(tolower(argv[1][0]))

{case′s′:send_message(msgqueue_id,(structmymsgbuf*)&qbuf,atol(argv[2]),argv[3]);

break;

case′r′:read_message(msgqueue_id,&qbuf,atol(argv[2]));

break;

case′d′:remove_queue(msgqueue_id);

break;

case′m′:change_queue_mode(msgqueue_id,argv[2]);

break;

default:usage();

}

return(0);

}

voidsend_message(intqid,structmymsgbuf*qbuf,longtype,char*text)

{

printf(“Sendingamessage...\n”);

qbuf->mtype=type;

strcpy(qbuf->mtext,text);

if((msgsnd(qid,(structmsgbuf*)qbuf,strlen(qbuf->mtext)+1,0))==-1) /*把消息追加到队列中*/

{

perror(“msgsnd”);

exit(1);

}

}

voidread_message(intqid,structmymsgbuf*qbuf,longtype)

{

printf(“Readingamessage...\n”);

qbuf->mtype=type;

msgrcv(qid,(structmsgbuf*)qbuf,MAX_SEND_SIZE,type,0); /*从队列中接收消息*/

printf(“Type:%ldText:%s\n”,qbuf->mtype,qbuf->mtext);

}

voidremove_queue(intqid)

{

msgctl(qid,IPC_RMID,0);/*把消息从队列中清除*/

}

voidchange_queue_mode(intqid,char*mode)

{

structmsqid_dsmyqueue_ds;

msgctl(qid,IPC_STAT,&myqueue_ds);

sscanf(mode,“%ho”,&myqueue_ds.msg_perm.mode);

msgctl(qid,IPC_SET,&myqueue_ds);

}

voidusage(void)

{

fprintf(stderr,“msgtool-Autilityfortinkeringwithmsgqueues\n”);

fprintf(stderr,“\nUSAGE:msgtool(s)end<type><messagetext>\n”);

fprintf(stderr,“(r)ecv<type>\n”);

fprintf(stderr,“(d)elete\n”);

fprintf(stderr,“(m)ode<octalmode>\n”);

exit(1);

}

2.信号量

信号量实际上是一个计数器,用来控制多个进程对共享资源的存取。一个信号量的初值可以设置为可用资源的个数,进程申请n个资源就将其减去n;等进程运行完毕释放资源时,就对其加n;当前可用的资源数如果是0,则申请资源的进程就挂起等待。要注意的是,在SystemV中的信号量实际上是一个信号量集,个数由semid_ds.sem_nsems定义。系统在linux/sem.h中为每一个信号量都保留了一个结构:

structsemid_ds{

structipc_permsem_perm; /*permissions..seeipc.h*/

__kernel_time_tsem_otime; /*lastsemoptime*/

__kernel_time_tsem_ctime; /*lastchangetime*/

structsem*sem_base;

/*ptrtofirstsemaphoreinarray*/

structsem_queue*sem_pending;/*pendingoperationstobeprocessed*/

structsem_queue**sem_pending_last;/*lastpendingoperation*/

structsem_undo*undo; /*undorequestsonthisarray*/

unsignedshortsem_nsems; /*no.ofsemaphoresinarray*/

}和信号量有关的系统调用有:

intsemget(key_tkey,intnsems,intsemflg)

intsemop(intsemid,structsembuf*sops,unsignednsops)

intsemctl(intsemid,intsemnum,intcmd,unionsemunarg)

semget用来打开或者创建一个信号量集;semctl对信号量执行cmd指定的操作;semop中的参数sops是一个structsembuf类型的指针,该结构在linux/sem.h中定义:

structsembuf{

unsignedshortsem_num;/*semaphoreindexinarray*/

shortsem_op; /*semaphoreoperation*/

shortsem_flg; /*operationflags*/

}

sem_op如果为正数就表示释放资源,为负数则代表申请资源。

有关信号量的操作的例子我们在下一节中和共享内存一起给出。

3.共享内存

共享内存是进程间通讯最快的方法。它就是由一个进程申请一段内存区域,其它进程也能够访问这段内存区域,从而实现进程间的通讯。使用共享内存的方式来实现进程间的通讯时,必须解决读写进程的原子操作,也就是说,在写操作的进程完成之前,读进程不能读取这段内存区域的内容。通常使用信号量或者记录锁来保证进行通讯的进程对共享内存的同步存取。系统在linux/shm.h中为每个共享内存段都设置了一个结构:

structshmid_ds{

structipc_perm_shm_perm;/*operationperms*/

intshm_segsz; /*sizeofsegment(bytes)*/

__kernel_time_tshm_atime; /*lastattachtime*/

__kernel_time_tshm_dtime; /*lastdetachtime*/

__kernel_time_tshm_ctime; /*lastchangetime*/

__kernel_ipc_pid_tshm_cpid;/*pidofcreator*/

__kernel_ipc_pid_tshm_lpid; /*pidoflastoperator*/

unsignedshortshm_nattch; /*no.ofcurrentattaches*/

unsignedshortshm_unused;/*compatibility*/

void*shm_unused2;/*ditto-usedbyDIPC*/

void*shm_unused3;/*unused*/

}和共享内存有关的系统调用有:

intshmget(key_tkey,intsize,intshmflg)

void*shmat(intshmid,constvoid*shmaddr,intshmflg)

intshmdt(constvoid*shmaddr)

intshmctl(intshmid,intcmd,structshmid_ds*buf)

shmget用于打开或者创建一个共享内存段。如果shmadd是0,那么shmat就返回一个没有映射的内存区域,供进程使用;shmdt取消共享内存段和进程之间的关联;shmget对共享内存段执行cmd指定的操作。在Linux中,我们可以编辑/etc/lilo.conf,预留一段内存供共享内存使用。例如,我们有64MB内存,在/etc/lilo.conf中增加一行:

append=“mem=63m”

这样我们就预留了1MB内存。

下面是使用信号量和共享内存实现cp的功能的例子,命令格式为

./a.out<infile>outfile源程序如下:

#include<stdio.h>

#include<signal.h>

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/shm.h>

#include<sys/sem.h>

#defineSHMKEY1(key_t)0x10/*共享内存的关键字*/

#defineSHMKEY2(key_t)0x20/*共享内存的关键字*/

#defineSEMKEY(key_t)0x30/*信号量的关键字*/

#defineMAXSIZE5*BUFSIZ

structdatabuf{

intd_nread;

chard_buf[MAXSIZE];

};

#defineERR((structdatabuf*)-1)

structsembufp_sem1={0,-1,0},p_sem2={1,-1,0},

v_sem1={0,1,0},v_sem2={1,1,0};

staticintshmid1,shmid2,semid;

voidfail(char*msg)

{

perror(msg);

exit(1);

}

voidgetseg(structdatabuf**buf1,structdatabuf**buf2)

{

if((shmid1=shmget(SHMKEY1,sizeof(structdatabuf),IPC_CREAT|IPC_EXCL|0666))<0)

/*创建共享内存区*/

fail("shmgetfail!");

if((shmid2=shmget(SHMKEY2,sizeof(structdatabuf),IPC-CREAT|IPC_EXCL|0666))<0)

fail(“shmgetfail!”);

if((*buf1=(structdatabuf*)(shmat(shmid1,0,0)))==ERR) /*建立与共享内存区的连接*/

fail(“shmatfail!”);

if((*buf2=(structdatabuf*)(shmat(shmid2,0,0)))==ERR)

fail(“shmatfail!”);

}

intgetsem()

{

if((semid=semget(SEMKEY,2,IPC_CREAT|IPC_EXCL|0666))==-1)

/*创建信号量*/

fail(“semgetfail!”);

if(semctl(semid,0,SETVAL,0)<0)

fail(“semctlfail!”);

if(semctl(semid,1,SETVAL,0)<0)

fail(“semctlfail!”);

return(semid);

}

voidclean()

{

if(shmctl(shmid1,IPC_RMID,NULL)<0)

/*释放共享内存区*/

fail(“shmctlfail!”);

if(shmctl(shmid2,IPC_RMID,NULL)<0)

fail(“shmctlfail!”);

if(semctl(semid,0,IPC_RMID,NULL)<0)

/*释放信号量*/

fail(“semctlfail!”);

}

voidreaddata(intsemid,structdatabuf*buf1,structdatabuf*buf2)

{

for(;;){

buf1->d_nread=read(0,buf1->d_buf,MAXSIZE);

semop(semid,&v_sem1,1);/*同步*/

semop(semid,&p_sem2,1);

if(buf1->d_nread<=0)

return;

semop(semid,&v_sem2,1);

semop(semid,&p_sem1,1);

if(buf2->d_nread<=0)

return;

}

}

voidwritedata(intsemid,structdatabuf*buf1,structdatabuf*buf2)

{

for(;;){

semop(semid,&p_sem1,1);

if(buf1->d_nread<=0)

return;

write(1,buf1->d_buf,buf1->d_nread);

semop(semid,&v_sem1,1);

semop(semid,&p_sem2,1);

if(buf2->d_nread<=0)

return;

write(1,buf2->d_buf,buf2->d_nread);

semop(semid,&v_sem2,1);

}

}

intmain()

{

intsemid,pid;

structdatabuf*buf1,*buf2;

semid=getsem();

getseg(&buf1,&buf2);

fprintf(stderr,“shmid:%d,shmid2:%d,semid:%d”,

shmid1,shmid2,semid);

if((pid=fork())<0)

fail(“forkfail!”);

elseif(pid==0){

writedata(semid,buf1,buf2);

}

else{

readdata(semid,buf1,buf2);

}

clean();

exit(0);

}9.2.3套接字

套接字(Socket)是目前进行网络编程使用最为广泛的技术。简单地说,套接字就是使用UNIX系统中的文件描述符实现系统进程间通讯的一种方法。

从历史发展来看,套接字起源于UNIX系统,主要有两种类型:UNIX域套接字(DomainSocket)和伯克利套接字(BerkelySocket)。现在伯克利套接字(以下简称套接字)已经

成为事实上的标准,广泛用于各种平台之间的网络通讯,例如ftp、telnet、http等通讯在底层都是使用Socket编程技术实现的。在ISO的OSI七层模型中,传输层采用的协议有两种,分别是TCP和UDP。其中TCP是面向连接的,提供可靠传输;UDP是无连接的,传输的信息可能发生丢失或者次序混乱。套接字有两种类型分别对应这两种协议:流式套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)。流式套接字的处理流程如图9.2所示,读者可以自行分析数据报套接字的处理流程。图9.2流式套接字的处理流程记住一点,在Linux中所有的内容归根结底都是文件,Socket也不例外。一个Socket对应一个描述符,它是一个整型数,还有几个结构和Socket有关:

structsockaddr{

sa_family_tsa_family;

/*address族,对于IP协议是AF_INET*/

charsa_data[14];

/*14字节的协议地址*/

}为了处理简单,程序员可以定义一个类似于sockaddr的结构在程序中使用:

structsockaddr_in{

sa_family_tsin_family;

/*address族,对于IP协议是AF_INET*/

unsignedshortsin_port; /*端口号*/

structin_addrsin_addr; /*Internet地址*/

unsignedcharsin_zero[8];

/*添0,保证该结构和sockaddr结构大小相同*/

}

sin_addr和sin_port惟一定义了一个套接字,因此可以进行惟一的访问。sockaddr_in结构中的sin_addr成员是in_addr的结构,定义如下:

structin_addr{

unsingnedlongs_addr;

}在Linux中,sockaddr结构的定义位于linux/socket.h,sockaddr_in和in_结构的定义位于linux/in.h,和我们上面的定义稍微有些差别。同时,Linux还提供了相当丰富的进行类型转换的函数,例如有一个structsockaddr_inina的变量,希望将其IP地址赋值为,可以这样使用:

ina.sin_addr.s_addr=inet_addr(“”)

对Socket进行操作的系统调用有很多,下面我们简单介绍图8.2中涉及到的一些系统调用。

(1)socket()。socket()用来创建一个套接字描述符,函数原型如下:

intsocket(intdomain,inttype,intprotocol)

参数domain指明协议类型,对于IP协议该参数应该设置为PF_INET;type指明套接字类型,流式套接字是SOCK_STREAM,数据报套接字是SOCK_DGRAM。

(2)bind()。bind()用于服务器端。其函数原型为

intbind(intsockfd,structsockaddr*my_addr,intaddrlen)

该函数将一个套接字描述符和一个sockaddr结构的参数my_addr绑定在一起。addrlen是structsockaddr的大小。

(3)listen()。listen()用于服务器端。其函数原型为

intlisten(ints,intbacklog)

服务器端完成bind()操作之后,就要调用listen(),开始侦听来自客户端的connect()发出的连接请求。如果有请求到达,就将其加入请求队列(队列长度由backlog参数指定),然后调用accept()处理。

(4)accept()。accept()用于服务器端。其函数原型为:

intaccept(ints,structsockaddr*addr,int*addrlen)

如果accept()成功,则返回一个新的套接字描述符。实际上在客户端和服务器进行通讯时,服务器端除了自行创建的套接字描述符用于listen()之外,每一个客户端的成功连接都会在服务器端新创建一个套接字描述符,客户端和服务器就是通过它进行通讯的。

(5)connect()。connect()用于客户端。其函数原型为

intconnect(intsockfd,structsockaddr*serv_addr,intaddrlen)

参数serv_addr中保存了服务器的信息,该函数将试图和服务器建立连接。

(6)send()和recv()。这两个系统调用用于面向连接的流式套接字的数据的发送和接收。套接字描述符当然可以使用read()和write()进行读写,但是send()和recv()提供了更完善的控制。

send()函数原型为

intsend(ints,constvoid*msg,intlen,unsignedintflags)

s为进行通讯的描述符,也就是accept()的返回值;该函数返回发送的字节数。recv()和它类似,函数原型为

intrecv(ints,void*buf,intlen,unsignedintflags)

(7)sendto()和recvfrom()。这两个系统调用用于无连接的流式数据报的数据的发送和接收,和send()与recv()类似。其函数原型如下:

intsendto(ints,constvoid*msg,intlen,unsignedintflags,conststructsockaddr*to,inttolen)

intrecvfrom(ints,void*buf,intlen,unsignedintflagsstructsockaddr*from,int*fromlen)

(8)close()。close()用于关闭套接字描述符。其函数原型为

intclose(intfd)

另外一个函数shutdown()也提供了类似的功能:

intshutdown(ints,inthow)

二者之间的区别在于close()将关闭套接字描述符,以后不允许任何操作;而shutdown()允许单向关闭套接字描述符,由参数how指定,意义如下:

0以后不允许接收数据

1以后不允许发送数据

2以后不允许任何操作下面我们给出一个面向连接的流式套接字进行通讯的例子。本例由两部分组成:server.c

和client.c。源程序如下:

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

server.c源程序

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

#include<stdio.h>

#include<stdlib.h>

#include<errno.h>

#include<string.h>

#include<netinet/in.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<sys/wait.h>

#defineMYPORT8000

#defineBACKLOG10

intmain()

{

intsock_fd,new_fd;

structsockaddr_inmy_addr;

structsockaddr_intheir_addr;

intsin_size;

if((sock_fd=socket(AF_INET,SOCK_STREAM,0))==-1){

/*创建套接字*/

perror(“socket”);

exit(1);

}

my_addr.sin_family=AF_INET; /*协议地址族*/

my_addr.sin_port=htons(MYPORT); /*端口*/

my_addr.sin_addr.s_addr=INADDR_ANY;

/*internet地址*/

bzero(&(my_addr.sin_zero),8);

if(bind(sock_fd,(structsockaddr*)&my_addr,sizeof(stru

ctsockaddr))==-1){ /*绑定*/

perror(“bind”);

exit(1);

}

if(listen(sock_fd,BACKLOG)==-1){ /*侦听*/

perror(“listen”);

exit(1);

}

while(1){

sin_size=sizeof(structsockaddr_in);

if((new_fd=accept(sock_fd,(structsockaddr*)&their_addr,&sin_size))==-1) /*接收*/

perror("accept");

exit(1);

}

printf("server:getconnectionfrom%s\n",inet_ntoa(their_addr.sin_addr));

if(!fork()){ /*子进程*/

if(send(new_fd,"Hello,World!\n",14,0)==-1)

/*发送信息*/

perror("send");

close(new_fd); /*关闭套接字*/

exit(0);

}

close(new_fd);/*关闭套接字*/

while(waitpid(-1,NULL,WNOHANG)>0);

/*等待子进程全部退出*/

}

close(sock_fd);

return0;

}**************

client.c源程序

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

#include<stdio.h>

#include<stdlib.h>

#include<errno.h>

#include<string.h>

#include<netinet/in.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<sys/wait.h>

#defineMYPORT8000

#defineMAXDATASIZE100

intmain(intargc,char*argv[])

{

intsockfd,numbytes;

charbuf[MAXDATASIZE];

structsockaddr_intheir_addr;

if(argc!=2){

fprintf(stderr,“usage:clienthostname\n”);

exit(1);

}

if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){ /*创建套接字*/

perror(“socket”);

exit(1);

}

their_addr.sin_family=AF_INET; /*协议地址族*/

their_addr.sin_port=htons(MYPORT); /*端口*/

their_addr.sin_addr=iner_addr(argv

温馨提示

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

评论

0/150

提交评论