版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
網(wǎng)絡(luò)編程實用教程第二版12.1.1
問題的提出站在應(yīng)用程序?qū)崿F(xiàn)的角度,應(yīng)用程序如何方便地使用協(xié)議棧軟件進(jìn)行通信呢?如果能在應(yīng)用程序與協(xié)議棧軟件之間提供一個軟件接口,就可以方便客戶與服務(wù)器軟件的編程。22.1 UNIX套接字網(wǎng)絡(luò)編程接口的產(chǎn)生與發(fā)展套接字應(yīng)用程序編程接口是網(wǎng)絡(luò)應(yīng)用程序通過網(wǎng)絡(luò)協(xié)議棧進(jìn)行通信時所使用的接口,即應(yīng)用程序與協(xié)議棧軟件之間的接口,簡稱套接字編程接口(Socket
API)。它定義了應(yīng)用程序與協(xié)議棧軟件進(jìn)行交互時可以使用的一組操作,決定了應(yīng)用程序使用協(xié)議棧的方式、應(yīng)用程序所能實現(xiàn)的功能、以及開發(fā)具有這些功能的程序的難度。3加州大學(xué)伯克利(Berkley)分校開發(fā)并推廣了一個包括
TCP/IP
互聯(lián)協(xié)議的
UNIX
,UNIX
(
Berkeley
Software
DistributionUNIX)操作系統(tǒng),套接字編程接口是這個操作系統(tǒng)的一個部分。后來的許多操作系統(tǒng)并沒有另外搞一套其它的編程接口,而是選擇了對于套接字編程接口的支持。由于這個套接字規(guī)范最 是由Berkeley
大學(xué)開發(fā)的,一般將它稱為Berkeley
Sockets規(guī)范。42.1.2
套接字編程接口的起源與應(yīng)用2.1.3
套接字編程接口在Windows和Linux操作系統(tǒng)中得到繼承和發(fā)展微軟公司以UNIX操作系統(tǒng)的BerkeleySockets規(guī)范為范例,定義了WindowsSocktes規(guī)范,全面繼承了套接字網(wǎng)絡(luò)編程接口。詳細(xì)內(nèi)容將在第三章介紹。Linux操作系統(tǒng)中的套接字網(wǎng)絡(luò)編程接口幾乎
與UNIX操作系統(tǒng)的套接字網(wǎng)絡(luò)編程接口一樣。本章著重介紹三大操作系統(tǒng)的套接字網(wǎng)絡(luò)編
程接口的共性問題。5要想實現(xiàn)套接字編程接口,可以采用兩種實現(xiàn)方式,一種是在操作系統(tǒng)的內(nèi)核中增加相應(yīng)的軟件來實現(xiàn),一種是通過開發(fā)操作系統(tǒng)之外的函數(shù)庫來實現(xiàn)。62.1.4
套接字編程接口的兩種實現(xiàn)方式2.1.5
套接字通信與UNIX操作系統(tǒng)的輸入/輸出UNIX操作系統(tǒng)對文件和所有其它的輸入/輸出設(shè)備采用一種統(tǒng)一的的操作模式,就是
“打開-讀-寫-關(guān)閉”(open-read-write-close)的I/O模式。當(dāng)TCP/IP協(xié)議被集成到UNIX內(nèi)核中的時候,相當(dāng)于在UNIX系統(tǒng)中引入了一種新型的I/O操作,就是應(yīng)用程序通過網(wǎng)絡(luò)協(xié)議棧來交換數(shù)據(jù)。7在UNIX
系統(tǒng)的實現(xiàn)中,套接字是完全與其他I/O集成在一起的。操作系統(tǒng)和應(yīng)用程序都將套接字編程接口也看作一種輸入/輸出機(jī)制。但是,用戶進(jìn)程與網(wǎng)絡(luò)協(xié)議的交互作用實際要比用戶進(jìn)程與傳統(tǒng)的I/O設(shè)備相互作用要復(fù)雜得多。8其次,使用套接字的應(yīng)用程序必須說明許多細(xì)節(jié)。僅僅提供open、read、write、close四個過程遠(yuǎn)遠(yuǎn)不夠。為避免單個套接字函數(shù)參數(shù)過多,套接字編程接口的設(shè)計者定義了多個函數(shù)。92.2
套接字編程的基本概念圖2.1
電插座與電話插座的作用套接口是對網(wǎng)絡(luò)中不同主機(jī)上應(yīng)用進(jìn)程之間進(jìn)行雙向通信的端點的抽象,一個套接口就是網(wǎng)絡(luò)上進(jìn)程通信的一端,提供了應(yīng)用層進(jìn)程利用網(wǎng)絡(luò)協(xié)議棧交換數(shù)據(jù)的機(jī)制。102.2.1
什么是套接字(SOCKET)
11
我們應(yīng)當(dāng)從多個層面來理解套接字這個概念的內(nèi)涵。從套接字所處的地位來講,套接字上聯(lián)應(yīng)用進(jìn)程,下聯(lián)網(wǎng)絡(luò)協(xié)議棧,是應(yīng)用程序通過網(wǎng)絡(luò)協(xié)議棧進(jìn)行通信的接口,是應(yīng)用程序與網(wǎng)絡(luò)協(xié)議棧進(jìn)行交互的接口。圖2.212應(yīng)用進(jìn)程、套接口、網(wǎng)絡(luò)協(xié)議棧及操作系統(tǒng)的關(guān)系從實現(xiàn)的角度來講,非常復(fù)雜。套接字是一個復(fù)雜的軟件機(jī)構(gòu),包含了一定的數(shù)據(jù)結(jié)構(gòu),包含許多選項,由操作系統(tǒng)內(nèi)核管理。從使用的角度來講,非常簡單。對于套接字的操作形成了一種網(wǎng)絡(luò)應(yīng)用程序的編程接口(API)。本書把這一套操作套接字的編程接口函數(shù)稱作套接字編程接口,套接字是它的操作對象??傊?,套接字是網(wǎng)絡(luò)通信的基石。132.2.2
套接字的特點1.通信域接字存在于通信域中,通信域是為了處理一14般的進(jìn)程通過套接字通信而引入的一種抽象概念,套接字通常只和同一域中的套接字交換數(shù)據(jù)。
如果數(shù)據(jù)交換要穿越域的邊界,就一定要執(zhí)行某種解釋程序?,F(xiàn)在,僅僅針對
Internet
域,并且使用Internet協(xié)議族(即TCP/IP協(xié)議族)來通信。2.套接字具有三種類型每一個正被使用的套接字都有它確定的類型,只有相同類型的套接字才能相互通信。(
1
)數(shù)據(jù)報套接字(
DatagraSOCKET)數(shù)據(jù)報套接字提供無連接的不保證可靠的獨立的數(shù)據(jù)報傳輸服務(wù)。在Internet通信域中,數(shù)據(jù)報套接字使用UDP數(shù)據(jù)報協(xié)議形成的進(jìn)程間通路,具有UDP協(xié)議為上層所提供的服務(wù)的所有特點。15圖2.316在Internet通信域中,數(shù)據(jù)報套接字基于UDP協(xié)議(2)流式套接字(Stream
SOCKET)17流式套接字提供雙向的、有序的、無重復(fù)的、無記錄邊界的可靠的數(shù)據(jù)流傳輸服務(wù)。在
Internet通信域中,流式套接字使用TCP協(xié)議形成的進(jìn)程間通路,具有TCP協(xié)議為上層所提供的服務(wù)的所有特點,在使用流式套接字傳輸數(shù)據(jù)之前,必須在數(shù)據(jù)的發(fā)送端和接收端之間建立連接,如圖2.4所示。圖2.418在Internet通信域中,流式套接字基于TCP協(xié)議(3)原始式套接字(RAW
SOCKET)19
原始式套接字允許對較低層次的協(xié)議,如IP、ICMP直接訪問,用于檢驗新的協(xié)議的實現(xiàn)。3.套接字由應(yīng)用層的通信進(jìn)程創(chuàng)建,并為其服務(wù)20就是說,每一個套接字都有一個相關(guān)的應(yīng)用進(jìn)程,操作該套接字的代碼是該進(jìn)程的組成部分。4.使用確定的IP地址和傳輸層端口號21
往往在生成套接字的描述符后,要將套接字與計算機(jī)上的特定的IP地址和傳輸層端口號相關(guān)聯(lián),這個過程稱為綁定。一個套接口要使用一個確定的三元組網(wǎng)絡(luò)地址信息,才能使它在網(wǎng)絡(luò)中唯一地被標(biāo)識。不管是采用對等模式或者客戶機(jī)/服務(wù)器模式,通信雙方的應(yīng)用程序都需要開發(fā)。雙方所交換數(shù)據(jù)的結(jié)構(gòu)和交換數(shù)據(jù)的順序有特定的要求,不符合現(xiàn)在成熟的應(yīng)用層協(xié)議,甚至需要自己去開發(fā)應(yīng)用層協(xié)議,自己設(shè)計最適合的數(shù)據(jù)結(jié)構(gòu)和信息交換規(guī)程。222.2.3
套接字的應(yīng)用場合2.2.4
套接字使用的數(shù)據(jù)類型和相關(guān)的問題231.三種表示套接字地址的結(jié)構(gòu)在套接字編程接口中,專門定義了三種結(jié)構(gòu)型的數(shù)據(jù)類型,用來存儲協(xié)議相關(guān)的網(wǎng)絡(luò)地址,在套接字編程接口的函數(shù)調(diào)用中要用到它們。(1)sockaddr結(jié)構(gòu),針對各種通信域的套接字,存儲它們的地址信息。struct
sockaddr
{unsigned
short
sa_family;
//地址家族char
sa_data;
//協(xié)議地址}24(2)sockaddr_in結(jié)構(gòu),專門針對Internet通信域,存儲套接字相關(guān)的網(wǎng)絡(luò)地址信息,例如IP地址,傳輸層端口號等信息。struct
sockaddr_in
{shortint
sin_family;
//地址家族unsigned
short
int
sin_port;
//
端口號25sin_addr;
//IP
地址sin_zero[8];
//全為0struct
in_addrunsigned
char}(3)in_addr結(jié)構(gòu),專門用來存儲IP地址。
Struct
in_addr{Unsigned
long
s_addrl;}26(4)這些數(shù)據(jù)結(jié)構(gòu)的一般用法:27
首先,定義一個Sockaddr_in的結(jié)構(gòu)實例,并將它清零。比如:struct
sockaddr_in
myad;memset(&myad,0,sizeof(structsockaddr_in));然后,為這個結(jié)構(gòu)賦值,比如:myad.sin_family=AF_INET;myad.sin_port=htons(8080);myad.sin_addr.s_addr=htonl(INADDR-ANY);28第三步:在函數(shù)調(diào)用中使用時,將這個結(jié)構(gòu)強(qiáng)制轉(zhuǎn)換為sockaddr類型。如:
accept(listenfd,(sockaddr*)(&myad),&addrlen);292.本機(jī)字節(jié)順序和網(wǎng)絡(luò)字節(jié)順序30在具體計算機(jī)中的多字節(jié)數(shù)據(jù)的存儲順序,稱為本機(jī)字節(jié)順序。多字節(jié)數(shù)據(jù)在網(wǎng)絡(luò)協(xié)議報頭中的存儲順序,稱為網(wǎng)絡(luò)字節(jié)順序。網(wǎng)絡(luò)應(yīng)用程序要在不同的計算機(jī)中運行,本機(jī)字節(jié)順序是不同的,但是,網(wǎng)絡(luò)字節(jié)順序是一定的。所以,應(yīng)用程序在編程的時候,在把
IP地址和端口號裝入套接字的時候,應(yīng)當(dāng)把它們從本機(jī)字節(jié)順序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)順序;相反,在本機(jī)輸出時,應(yīng)將它們從網(wǎng)絡(luò)字節(jié)順序轉(zhuǎn)換為本機(jī)字節(jié)順序。31套接字編程接口特為解決這個問題設(shè)置了四個函數(shù):htons()
短整數(shù)本機(jī)順序轉(zhuǎn)換為網(wǎng)絡(luò)順序,用于端口號。htonl()
長整數(shù)本機(jī)順序轉(zhuǎn)換為網(wǎng)絡(luò)順序,用于IP地址。32ntohs()
短整數(shù)網(wǎng)絡(luò)順序轉(zhuǎn)換為本機(jī)順序,用于端口號。ntohl()
長整數(shù)網(wǎng)絡(luò)順序轉(zhuǎn)化為本機(jī)順序,用于IP地址。
這四個函數(shù)將被轉(zhuǎn)換的數(shù)值作為函數(shù)的參數(shù),函數(shù)返回值是轉(zhuǎn)換后的結(jié)果。333.點分十進(jìn)制的IP地址的轉(zhuǎn)換34在因特網(wǎng)中,IP
地址常常用點分十進(jìn)制的表示方法,但在套接字中,IP地址是無符號的長整型數(shù),套接字編程接口設(shè)置了兩個函數(shù),專門用于兩種形式的IP地址的轉(zhuǎn)換。(1)inet-addr函數(shù)35unsigned
long
inet-addr(
const
char*cp)入口參數(shù)cp:點分十進(jìn)制形式的IP地址。返回值:網(wǎng)絡(luò)字節(jié)順序的IP地址,是無符號的長整數(shù),(2)inet_ntoa函數(shù)36char*
inet_ntoa(struct
in_addr
in)入口參數(shù)in:包含長整型IP地址的in_addr
結(jié)構(gòu)變量,返回值:指向點分十進(jìn)制IP地址的字符串的指針。通常,我們使用域名來標(biāo)識站點,可以將文字型的主機(jī)域名直接轉(zhuǎn)換成IP地址,struct
hostent*
gethodtbyname(
constchar*
name);入口參數(shù):是站點的主機(jī)域名字符串,返回值: 是指向hostent
結(jié)構(gòu)的指針,hostent結(jié)構(gòu)包含主機(jī)名,主機(jī)別名數(shù)組,返回地址的類型(一般是AF-INET),地址長度的字節(jié)數(shù),已符合網(wǎng)絡(luò)字節(jié)順序的主機(jī)網(wǎng)絡(luò)地址等。374.域名服務(wù)2.3
面向連接的套接字編程382.3.1
套接字的工作過程2.3.2 UNIX套接字編程接口的系統(tǒng)調(diào)用391.創(chuàng)建套接字SOCKET()SOCKET過程創(chuàng)建一個套接字并返回一個整型描述符:int SOCKET(
int
Protofamily, int
Type,int
Protocol);2.綁定套接字到指定的地址BIND()40int
BIND(
int
Sockfd,
struct
sockaddr*My_addr,
int
Addrlen);3.啟動監(jiān)聽Listen()41int
LISTEN(
int
Sockfd,
int
Queuesize);舉例:LISTEN(Sockfe,10);圖2.642監(jiān)聽套接字使用緩沖區(qū)接納多個客戶端的連接請求4.接收連接請求ACCEPT()int
ACCEPT(int
Sockfd,
struct
sockaddr*
Addr,int*
addrlen);舉例:int
clientfd;
//定義響應(yīng)套接字描述//獲得套接字//定義用于返符變量int
addrler=sizeof(sockaddr);地址結(jié)構(gòu)長度。struct
sockaddr_in
cltsockaddr;回客戶端地址的結(jié)構(gòu)。clientfd=ACCEPT(listenfd,
(sockaddr*)(&cltsockaddr),&addrlen);
//接收客戶連接請求435.請求建立連接CONNECT()44int
CONNECT(
int
Sockfd,
struct
sockaddr*Service_addr,
int
Addrlen);舉例:(structif
(CONNECT(sockfd,sockaddr*)(&serv_addr),sizeof(structsockaddr))<0){報錯,并退出}6.讀/寫套接字READ()和WRITE()45int
READ(
int
sockfd,
void*
buffer,
int
len
);int
WRITE(
int
sockfd,
void*
buffer,
int
len
)向套接字發(fā)送SEND()和從套接字接收RECV()int
SEND(
int
sockfd,
char*
buf,
intlen,int
flags
);int
RECV(
int
sockfd,
char*
buf,
intlen,int
flags
);468.關(guān)閉套接字CLOSE()47int
CLOSE(
int
sockfd
);2.3.3
面向連接的套接字編程實例1.實例的功能服務(wù)器對來訪的客戶計數(shù),并向客戶報告這個計數(shù)值??蛻艚⑴c服務(wù)器的一個連接并等待它的輸出。每當(dāng)連接請求到達(dá)時,服務(wù)器生成一個可打印的ASCII串信息,將它在連接上發(fā)回,然后關(guān)閉連接??蛻麸@示收到的信息,然后退出。48
49例如,對于服務(wù)器接收的第10次客戶連接請求,該客戶將收到并打印如下信息:This
server
has
been
contacted10times.2.實例程序的命令行參數(shù)50實例是UNIX環(huán)境下的C程序,客戶和服務(wù)器程序在編譯后,均以命令行的方式執(zhí)行。服務(wù)器程序執(zhí)行時可以帶一個命令行參數(shù),是用來接受請求的監(jiān)聽套接字的協(xié)議端口號。這個參數(shù)是可選的。如果不指定端口號,代碼將使用程序內(nèi)定的缺省端口號5188??蛻舫绦驁?zhí)行時可以帶兩個命令行參數(shù):一個是服務(wù)器所在計算機(jī)的主機(jī)名,另一個是服務(wù)器監(jiān)聽的協(xié)議端口號。這兩個參數(shù)都是可選的。如果沒有指定協(xié)議端口號,客戶使用程序內(nèi)定的缺省值5188。如果一個參數(shù)也沒有,客戶使用缺省端口和主機(jī)名localhost,localhost是映射到客戶所運行的計算機(jī)的一個別名。允許客戶與本地機(jī)上的服務(wù)器通信,對調(diào)試是很有用的。513.客戶程序代碼52/*----------------------------------------------------程序:client.c目的:創(chuàng)建一個套接字,通過網(wǎng)絡(luò)連接一個服務(wù)器,并打印來自服務(wù)器的信息語法:client[host[port]]**host-運行服務(wù)器的計算機(jī)的名字port-服務(wù)器監(jiān)聽套接字所用協(xié)議端口號注意:兩個參數(shù)都是可選的。如果未指定主機(jī)名,客戶使用localhost;如果未指定端口號,客戶將使用PROTOPORT中給定的缺省協(xié)議端口號*----------------------------------------------------/*
UNIX下,套接字的相關(guān)包含文件。*/53#include
<sys/types.h>#include
<sys/socket.h>#include
<netinet/in.h>#include
<arpa/inet.h>#include
<netdb.h>#include
<stdio.h>#include
<string.h>#define
PROTOPORT
5188 /*缺省協(xié)議端口號*/extern
int
errno;char
localhost=“l(fā)ocalhost”;
/*缺省主機(jī)名*/main(argc,argv)int
argc;
char
*argv[];{struct
hostent
*ptrh;54/*
指向主機(jī)列表中一個條目的指針*/struct
sockaddr_in
servaddr; /*
存放服務(wù)器端網(wǎng)絡(luò)地址的結(jié)構(gòu)*/intintsockfd;port;char*
host;int
n;char buf[1000]
;/*
客戶端的套接字描述符*//*
服務(wù)器端套接字協(xié)議端口號*//*
服務(wù)器主機(jī)名指針*//*
讀取的字符數(shù)*//*
緩沖區(qū),接收服務(wù)器發(fā)來的數(shù)據(jù)*/memset((char*)&
servaddr,0,sizeof(servaddr));
/*清空sockaddr結(jié)構(gòu)*/servaddr.sin_family=AF_INET; /*
設(shè)置為因特網(wǎng)協(xié)議族*//*
檢查命令行參數(shù),如果有,就抽取端口號。否則使用內(nèi)定的缺省值*/if(argc>2){/*
如果指定了協(xié)議端口,就轉(zhuǎn)/*
否則,使用缺省端口號*//*
如果端口號是合法的數(shù)值,就將它port
=atoi(argv[2]);換成整數(shù)*/}else
{port
=PROTOPORT;}if(port>0)裝入網(wǎng)絡(luò)地址結(jié)構(gòu)*/55servaddr.sin_port
=
htons((u_short)port);else{ /*
否則,打印錯誤信息并退出*/fprintf(stderr,”bad
port
number
%s\n”,argv[2]);exit(1);}/*
檢查主機(jī)參數(shù)并指定主機(jī)名*/if(argc>1){56/*
如果指定了主機(jī)名參數(shù),就使用/*
否則,使用缺省值*/host
=argv[1];它*/}else{host
=localhost;}/*
將主機(jī)名轉(zhuǎn)換成相應(yīng)的IP地址并復(fù)制到servaddr結(jié)構(gòu)中*/57/*
從服務(wù)器主機(jī)名得到/*
檢查主機(jī)名的有效性,ptrh
=
gethostbyname(
host);相應(yīng)的IP地址*/if (
(char
*)ptrh
==
null
)
{無效則退出*/fprintf(
stderr,
”invalid
host:
%s\n”,
host);exit(1);}memcpy(&servaddr.sin_addr,
ptrh->h_addr,
ptrh->h_length
);/*
創(chuàng)建一個套接字*/sockfd
=
SOCKET(AF_INET,
SOCK_STREAM,
0);if
(sockfd
<
0)
{fprintf(stderr,
”socket
creation
failed\n”
);exit(1);}/*
請求連接到服務(wù)器*/if
(connect(
sockfd,
(struct
sockaddr
*)&
servaddr,sizeof(servaddr))
<
0)
{/*
連接請求被拒絕,fprintf(stderr,”connect
failed\n”);報錯并退出*/exit(1);}58/*
從套接字反復(fù)讀數(shù)據(jù),并輸出到用戶屏幕上*/n=recv(sockfd,buf,sizeof(buf),0);while
(
n
>
0)
{write(1,buf,
n);n
=
recv(
sockfd
,
buf,
sizeof(
buf
),
0
);}/*
關(guān)閉套接字*/closesocket(sockfd);/*
終止客戶程序*/exit(0);}594.服務(wù)器實例代碼/*程序:server.c目的:分配一個套接字,然后反復(fù)執(zhí)行如下幾步:(1)等待客戶的下一個連接(2)發(fā)送一個短消息給客戶(3)關(guān)閉與客戶的連接(4)轉(zhuǎn)向(1)步命令行語法:server[port]port
–服務(wù)器端監(jiān)聽套接字使用的協(xié)議端口號注意:端口號可選。如果未指定端口號,服務(wù)器使用PROTOPORT中指定的缺省端口號**/60#include
<sys/types.h>#include
<sys/socket.h>/*
監(jiān)聽套接字的缺省/*
監(jiān)聽套接字的請求隊#include
<netinet/in.h>#include
<netdb.h>#include
<stdio.h>#include
<string.h>#define
PROTOPORT
5188協(xié)議端口號*/#define
QLEN
6列大小*/int visits
=
0;/*
對于客戶連接的計數(shù)*/61main(argc,argc)int
argc;char*
argv[];/*
指向主機(jī)列表中一個條目{struct
hostent
*ptrh;的指針*/struct
sockaddr_in
servaddr; /*
存放服務(wù)器網(wǎng)絡(luò)地址的結(jié)構(gòu)*/struct
sockaddr_in
clientaddr; /*
存放客戶網(wǎng)絡(luò)地址的結(jié)構(gòu)*//*
監(jiān)聽套接字描述符*//*
響應(yīng)套接字描述符*//*
協(xié)議端口號*//*
地址長度*//*
供服務(wù)器發(fā)送字符串所用的int
listenfd;int
clientfd;int
port;
int
alen;char
buf[1000];緩沖區(qū)*/62memset(
(char*)&
servaddr,
0,
sizeof(servaddr)
);
/*清空sockaddr結(jié)構(gòu)*/servaddr.sin_family=AF_INET; /*
設(shè)置為因特網(wǎng)協(xié)議族*/servaddr.sin_addr.s_addr=INADDR_ANY; /*
設(shè)置本地IP地址*//*
檢查命令行參數(shù),如果指定了,就是用該端口號,否則使用缺省端口號*/if
(argc
>
1){/*
如果指定了端口號,就將它/*
否則,使用缺省端口號*/port
=atoi(argv[1]);轉(zhuǎn)換成整數(shù)*/}
else
{port
=PROTOPORT;}63if (port
>
0) /*
測試端口號是否合法
*/servaddr.sin_port=htons(
(u_short)port
);
else{ /*
打印錯誤信息并退出
*/64number
%s\n”,
argv[1]fprintf(
stderr,
”bad
port);exit(1);}/*
創(chuàng)建一個用于監(jiān)聽的流式套接字*/listenfd
=
SOCKET(AF_INET,SOCK_STREAM,0);if (listenfd
<0)
{fprintf(
stderr,
“socket
creation
failed\n”
);exit(1);}/*
將本地地址綁定到監(jiān)聽套接字*/if
(
bind(
listenfd,
(struct
sockaddr
*)&
servaddr,65sizeof(servaddr))
<
0)
{fprintf(stderr,
”bind
failed\n”
);exit(1);}/*
開始監(jiān)聽,并指定監(jiān)聽套接字請求隊列的長度
*/if (listen(listenfd,
QLEN)
<
0)
{fprintf(stderr,
”listen
filed\n”
);exit(1);}/*
服務(wù)器主循環(huán)—接受和處理來自客戶端的連接請求*/while(1){alen=sizeof(clientaddr); /*
接受客戶端連接請求,并生成響應(yīng)套接字*/if((clientfd
=
accept(
listenfd,
(struct
sockaddr
*)&clientaddr,
&alen))
<
0
)
{fprintf(
stderr,
“accept
failed\n”);exit(1);}visits++; /*
累加訪問的客戶數(shù)
*/sprintf(
buf,
“this
server
has
been
contacted
%dtime
\n”,
visits
);send(clientfd,buf,strlen(buf),0); /*
向客戶端發(fā)送信息*//*
關(guān)閉響應(yīng)套接字*/closesocket(
clientfd
);}}66關(guān)于阻塞的問題圖2.767服務(wù)器進(jìn)程因調(diào)用ACCEPT()而被阻塞2.3.4
進(jìn)程的阻塞問題和對策681.什么是阻塞
阻塞是指一個進(jìn)程執(zhí)行了一個函數(shù)或者系統(tǒng)調(diào)用,該函數(shù)由于某種原因不能立即完成,因而不能返回調(diào)用它的進(jìn)程,導(dǎo)致進(jìn)程受控于這個函數(shù)而處于等待的狀態(tài),進(jìn)程的這種狀態(tài)稱為阻塞。圖2.8
RECV()函數(shù)的兩種執(zhí)行方式692.能引起阻塞的套接字調(diào)用70在Berkeley
套接字網(wǎng)絡(luò)編程接口的模型中,套接字的默認(rèn)行為是阻塞的,具體地說,在一定情況下,有多個操作套接字的系統(tǒng)調(diào)用會引起進(jìn)程阻塞。ACCEPT()READ()、RECV()和READFORM()WRITE()、SEND()和SENDTO()CONNECT()SELECT()CLOSESOCKET()3.阻塞工作模式帶來的問題采用阻塞工作模式的單進(jìn)程服務(wù)器是不能很好地同時為多個客戶服務(wù)的。圖2.9是一個例子。圖2.971采用阻塞工作模式的服務(wù)器不能很好地為多個客戶服務(wù)4.一種解決方案72
利用UNIX操作系統(tǒng)的FORK()系統(tǒng)調(diào)用,編制多進(jìn)程并發(fā)執(zhí)行的服務(wù)器程序??梢詣?chuàng)建子進(jìn)程。對于每一個客戶端,用一個專門的進(jìn)程為它服務(wù),通過進(jìn)程的并發(fā)執(zhí)行,來實現(xiàn)對多個客戶的并發(fā)服務(wù)?;镜木幊炭蚣苁牵焊高M(jìn)程代碼If
((pid
=
FORK())
==
0)
{…….子進(jìn)程代碼........}
else
if
(pid<0)
{報錯信息}父進(jìn)程代碼73舉例:#include
<sys/types.h>#include
<sys/socket.h>#include
<stdio.h>#include
<arpa/inet.h>void
main(int
argc,
char**
argv){int
listenfd,clientfd,pid;struct
sockaddr_in
ssockaddr,
csockaddr;char
buffer[1024];int
addrlen,n;74/*
創(chuàng)建監(jiān)聽套接字*/listenfd
=
socket(AF_INET,SOCK_STREAM,0);if (listenfd
<
0)
{fprintf(stderr,
"socket
error!\n");exit(1);}75/*
為監(jiān)聽套接字綁定網(wǎng)絡(luò)地址*/memset(&ssockaddr,0,sizeof(struct
sockaddr_in));ssockaddr.sin_family=AF_INET;ssockaddr.sin_addr.s_addr=htonl(INADDR_ANY);ssockaddr.sin_port=htons(8080);if
(bind(listenfd,&ssockaddr,sizeof(structsockaddr_in))
<
0)
{fprintf(stderr,
"bind
error!\n");exit(2);}76/*
啟動套接字的監(jiān)聽*/listen(listenfd,5);addrlen
=
sizeof(sockaddr);77服務(wù)器進(jìn)入循環(huán),接受并處理來自不同客戶端的連接請求=/**/while
(1)
{clientfdaccept(listenfd,(sockaddr*)(&csockaddr),&addrlen);/*
accept
調(diào)用返回時,表明有客戶端請求連接,創(chuàng)建子進(jìn)程處理連接*/If
((pid
=
FORK())
==
0)
{/*
顯示客戶端的網(wǎng)絡(luò)地址*/printf("ClientAddr:%s%d\n",inet_ntoa(csockaddr.sin_addr),ntohs(csockaddr.sin_port));/*
讀取客戶端發(fā)送來的數(shù)據(jù),在將它們返回到客戶端*/while((n=read(clientfd,buffer,1024))>0){
buffer[n]=0;printf("Client
Send:
%s",buffer);write(
clientfd,
buffer,
n);}if
(n
<
0)
{fprintf(
stderr,
"read
error!\n");exit(3);}78/*
通信完畢,關(guān)閉與這個客戶連接的套接字*/79closed!\n",printf("clent
%sinet_ntoa(csockaddr.sin_addr));close(clientfd);exit(1);}
else
if
(pid
<
0)
printf("fork
failed!\n");close(clientfd);}close(listenfd); /*
關(guān)閉監(jiān)聽套接字*/}2.4
無連接的套接字編程802.4.1
無連接的套接字編程的兩種模式使用數(shù)據(jù)報套接字開發(fā)網(wǎng)絡(luò)應(yīng)用程序,既可以采用客戶/服務(wù)器模式,也可以采用對等模式。圖2.1081對等模式的數(shù)據(jù)報套接字的編程模型1.對等模式2.客戶/服務(wù)器模式圖2.11 C/S模式的數(shù)據(jù)報套接字的編程模型822.4.2
兩個專用的系統(tǒng)調(diào)用83發(fā)送數(shù)據(jù)報SENDTO()int
SENDTO(
int
sockfd,
const
void*
msg,
intlen,
unsigned
int
flags,
struct
sockaddr*
to,int
tolen);接收數(shù)據(jù)報RECVFROM()int
RECVFROM(
int
sockfd,
void*
buf,
intlen,unsigned
int
flags,
struct
sockaddr*
from,int*
fromlen
)2.4.3
數(shù)據(jù)報套接字的對等模式編程實例聊天程序#include
<sys/types.h>#include
<unistd.h>#include
<error.h>#include
<sys/socket.h>#include
<arpa/inet.h>#include
<stdio.h>/*
中斷處理過程*/void
int_proc(
int
signo)
{
}84void
main(int
argc,
char**
argv){struct
sockaddr_in daddr,
saddr,
cmpaddr;int
sockfd;int
timer
=
3;
char
buffer[1024];int
addrlen,n;/*
判斷用戶輸入的命令行是否正確,如果有錯,提示用法*/if
(argc!=5){85目的
IP
目的端口 源
IP
源端口
\n",printf("用法:%sargv[0]);exit(0);}/*
設(shè)定中斷處理函數(shù),并設(shè)置時間限制*/signal(SIGALRM,int_proc);alarm(timer);/*
建立數(shù)據(jù)報套接字*/sockfd
=
socket(AF_INET,
SOCK_DGRAM,
0);if
(sockfd
<
0)
{fprintf(stderr,
"socket
error!\n");exit(1);}86/*為結(jié)構(gòu)變量daddr的各個字段賦值*/addrlen=sizeof(struct
sockaddr_in);memset(&daddr,0,addrlen);daddr.sin_family=AF_INET;daddr.sin_port=htons(atoi(argv[2]));if
(inet_pton(AF_INET,
argv[1],
&daddr.sin_addr
)<=
0)
{fprintf(stderr,
"Invaild
dest
IP!\n");exit(0);}87/*
為結(jié)構(gòu)變量saddr的各個字段賦值*/addrlen=sizeof(struct
sockaddr_in);memset(&saddr,0,addrlen);saddr.sin_family=AF_INET;saddr.sin_port=htons(atoi(argv[4]));if
(inet_pton(AF_INET,
argv[3],
&saddr.sin_addr
)<=
0)
{fprintf(stderr,
"Invaild
source
IP!\n");exit(0);}88/*
綁定地址*/if
(bind(sockfd,
&saddr,
addrlen)
<
0
)
{fprintf(stderr,
"bind
local
addr
error!\n");exit(1);}/*
從標(biāo)準(zhǔn)輸入獲得字符串,并發(fā)送給目標(biāo)地址*/if
(fgets(buffer,
1024,
stdin)
==
NULL
)
exit(0);if
(
sendto(
sockfd,
buffer,
strlen(buffer),
0,&daddr,
addrlen))
{fprintf(stderr,
"sendto
error!\n");exit(2);}89while
(1)
{/*
接收信息并顯示*/n
=
recvfrom(
sockfd,
buffer,
1024,
0,
&cmpaddr,&daddrlen
);if
(n
<
0)
{/*
根據(jù)
errno
中的數(shù)值是否為常
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 合同模式下的分包管理
- 購銷售合同模板
- 倉庫租賃合同范本
- 建筑裝飾裝修合同模板
- 廢氣治理工程合同模板
- 家裝半包合同范本
- 盆栽購買合同
- 設(shè)計委托合同書
- 網(wǎng)絡(luò)信息推廣合同3篇
- 駕駛工作與送貨員合同3篇
- 系統(tǒng)解剖學(xué)(南方醫(yī)科大學(xué))智慧樹知到期末考試答案章節(jié)答案2024年南方醫(yī)科大學(xué)
- 《生物質(zhì)熱電聯(lián)產(chǎn)工程設(shè)計規(guī)范》
- 水稻害蟲精選課件
- 危險化學(xué)品購買管理臺賬
- 最新VTE指南解讀(靜脈血栓栓塞癥的臨床護(hù)理指南解讀)
- 中學(xué)校本課程教材《生活中的化學(xué)》
- 污水處理站運行維護(hù)管理方案
- 農(nóng)村公路養(yǎng)護(hù)工程施工組織設(shè)計
- 個人如何開辦婚介公司,婚介公司經(jīng)營和管理
- 天津市歷年社會保險繳費基數(shù)、比例
- 2024國家開放大學(xué)電大專科《學(xué)前兒童發(fā)展心理學(xué)》期末試題及答案
評論
0/150
提交評論