《計算機操作系統教程》課件-第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

提交評論