版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
網(wǎng)絡(luò)編程實(shí)用教程第二版12.1.1
問題的提出站在應(yīng)用程序?qū)崿F(xiàn)的角度,應(yīng)用程序如何方便地使用協(xié)議棧軟件進(jìn)行通信呢?如果能在應(yīng)用程序與協(xié)議棧軟件之間提供一個(gè)軟件接口,就可以方便客戶與服務(wù)器軟件的編程。22.1 UNIX套接字網(wǎng)絡(luò)編程接口的產(chǎn)生與發(fā)展套接字應(yīng)用程序編程接口是網(wǎng)絡(luò)應(yīng)用程序通過網(wǎng)絡(luò)協(xié)議棧進(jìn)行通信時(shí)所使用的接口,即應(yīng)用程序與協(xié)議棧軟件之間的接口,簡(jiǎn)稱套接字編程接口(Socket
API)。它定義了應(yīng)用程序與協(xié)議棧軟件進(jìn)行交互時(shí)可以使用的一組操作,決定了應(yīng)用程序使用協(xié)議棧的方式、應(yīng)用程序所能實(shí)現(xiàn)的功能、以及開發(fā)具有這些功能的程序的難度。3加州大學(xué)伯克利(Berkley)分校開發(fā)并推廣了一個(gè)包括
TCP/IP
互聯(lián)協(xié)議的
UNIX
,UNIX
(
Berkeley
Software
DistributionUNIX)操作系統(tǒng),套接字編程接口是這個(gè)操作系統(tǒng)的一個(gè)部分。后來的許多操作系統(tǒng)并沒有另外搞一套其它的編程接口,而是選擇了對(duì)于套接字編程接口的支持。由于這個(gè)套接字規(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要想實(shí)現(xiàn)套接字編程接口,可以采用兩種實(shí)現(xiàn)方式,一種是在操作系統(tǒng)的內(nèi)核中增加相應(yīng)的軟件來實(shí)現(xiàn),一種是通過開發(fā)操作系統(tǒng)之外的函數(shù)庫來實(shí)現(xiàn)。62.1.4
套接字編程接口的兩種實(shí)現(xiàn)方式2.1.5
套接字通信與UNIX操作系統(tǒng)的輸入/輸出UNIX操作系統(tǒng)對(duì)文件和所有其它的輸入/輸出設(shè)備采用一種統(tǒng)一的的操作模式,就是
“打開-讀-寫-關(guān)閉”(open-read-write-close)的I/O模式。當(dāng)TCP/IP協(xié)議被集成到UNIX內(nèi)核中的時(shí)候,相當(dāng)于在UNIX系統(tǒng)中引入了一種新型的I/O操作,就是應(yīng)用程序通過網(wǎng)絡(luò)協(xié)議棧來交換數(shù)據(jù)。7在UNIX
系統(tǒng)的實(shí)現(xiàn)中,套接字是完全與其他I/O集成在一起的。操作系統(tǒng)和應(yīng)用程序都將套接字編程接口也看作一種輸入/輸出機(jī)制。但是,用戶進(jìn)程與網(wǎng)絡(luò)協(xié)議的交互作用實(shí)際要比用戶進(jìn)程與傳統(tǒng)的I/O設(shè)備相互作用要復(fù)雜得多。8其次,使用套接字的應(yīng)用程序必須說明許多細(xì)節(jié)。僅僅提供open、read、write、close四個(gè)過程遠(yuǎn)遠(yuǎn)不夠。為避免單個(gè)套接字函數(shù)參數(shù)過多,套接字編程接口的設(shè)計(jì)者定義了多個(gè)函數(shù)。92.2
套接字編程的基本概念圖2.1
電插座與電話插座的作用套接口是對(duì)網(wǎng)絡(luò)中不同主機(jī)上應(yīng)用進(jìn)程之間進(jìn)行雙向通信的端點(diǎn)的抽象,一個(gè)套接口就是網(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)從多個(gè)層面來理解套接字這個(gè)概念的內(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)系從實(shí)現(xiàn)的角度來講,非常復(fù)雜。套接字是一個(gè)復(fù)雜的軟件機(jī)構(gòu),包含了一定的數(shù)據(jù)結(jié)構(gòu),包含許多選項(xiàng),由操作系統(tǒng)內(nèi)核管理。從使用的角度來講,非常簡(jiǎn)單。對(duì)于套接字的操作形成了一種網(wǎng)絡(luò)應(yīng)用程序的編程接口(API)。本書把這一套操作套接字的編程接口函數(shù)稱作套接字編程接口,套接字是它的操作對(duì)象??傊捉幼质蔷W(wǎng)絡(luò)通信的基石。132.2.2
套接字的特點(diǎn)1.通信域接字存在于通信域中,通信域是為了處理一14般的進(jìn)程通過套接字通信而引入的一種抽象概念,套接字通常只和同一域中的套接字交換數(shù)據(jù)。
如果數(shù)據(jù)交換要穿越域的邊界,就一定要執(zhí)行某種解釋程序?,F(xiàn)在,僅僅針對(duì)
Internet
域,并且使用Internet協(xié)議族(即TCP/IP協(xié)議族)來通信。2.套接字具有三種類型每一個(gè)正被使用的套接字都有它確定的類型,只有相同類型的套接字才能相互通信。(
1
)數(shù)據(jù)報(bào)套接字(
DatagraSOCKET)數(shù)據(jù)報(bào)套接字提供無連接的不保證可靠的獨(dú)立的數(shù)據(jù)報(bào)傳輸服務(wù)。在Internet通信域中,數(shù)據(jù)報(bào)套接字使用UDP數(shù)據(jù)報(bào)協(xié)議形成的進(jìn)程間通路,具有UDP協(xié)議為上層所提供的服務(wù)的所有特點(diǎn)。15圖2.316在Internet通信域中,數(shù)據(jù)報(bào)套接字基于UDP協(xié)議(2)流式套接字(Stream
SOCKET)17流式套接字提供雙向的、有序的、無重復(fù)的、無記錄邊界的可靠的數(shù)據(jù)流傳輸服務(wù)。在
Internet通信域中,流式套接字使用TCP協(xié)議形成的進(jìn)程間通路,具有TCP協(xié)議為上層所提供的服務(wù)的所有特點(diǎn),在使用流式套接字傳輸數(shù)據(jù)之前,必須在數(shù)據(jù)的發(fā)送端和接收端之間建立連接,如圖2.4所示。圖2.418在Internet通信域中,流式套接字基于TCP協(xié)議(3)原始式套接字(RAW
SOCKET)19
原始式套接字允許對(duì)較低層次的協(xié)議,如IP、ICMP直接訪問,用于檢驗(yàn)新的協(xié)議的實(shí)現(xiàn)。3.套接字由應(yīng)用層的通信進(jìn)程創(chuàng)建,并為其服務(wù)20就是說,每一個(gè)套接字都有一個(gè)相關(guān)的應(yīng)用進(jìn)程,操作該套接字的代碼是該進(jìn)程的組成部分。4.使用確定的IP地址和傳輸層端口號(hào)21
往往在生成套接字的描述符后,要將套接字與計(jì)算機(jī)上的特定的IP地址和傳輸層端口號(hào)相關(guān)聯(lián),這個(gè)過程稱為綁定。一個(gè)套接口要使用一個(gè)確定的三元組網(wǎng)絡(luò)地址信息,才能使它在網(wǎng)絡(luò)中唯一地被標(biāo)識(shí)。不管是采用對(duì)等模式或者客戶機(jī)/服務(wù)器模式,通信雙方的應(yīng)用程序都需要開發(fā)。雙方所交換數(shù)據(jù)的結(jié)構(gòu)和交換數(shù)據(jù)的順序有特定的要求,不符合現(xiàn)在成熟的應(yīng)用層協(xié)議,甚至需要自己去開發(fā)應(yīng)用層協(xié)議,自己設(shè)計(jì)最適合的數(shù)據(jù)結(jié)構(gòu)和信息交換規(guī)程。222.2.3
套接字的應(yīng)用場(chǎng)合2.2.4
套接字使用的數(shù)據(jù)類型和相關(guān)的問題231.三種表示套接字地址的結(jié)構(gòu)在套接字編程接口中,專門定義了三種結(jié)構(gòu)型的數(shù)據(jù)類型,用來存儲(chǔ)協(xié)議相關(guān)的網(wǎng)絡(luò)地址,在套接字編程接口的函數(shù)調(diào)用中要用到它們。(1)sockaddr結(jié)構(gòu),針對(duì)各種通信域的套接字,存儲(chǔ)它們的地址信息。struct
sockaddr
{unsigned
short
sa_family;
//地址家族char
sa_data;
//協(xié)議地址}24(2)sockaddr_in結(jié)構(gòu),專門針對(duì)Internet通信域,存儲(chǔ)套接字相關(guān)的網(wǎng)絡(luò)地址信息,例如IP地址,傳輸層端口號(hào)等信息。struct
sockaddr_in
{shortint
sin_family;
//地址家族unsigned
short
int
sin_port;
//
端口號(hào)25sin_addr;
//IP
地址sin_zero[8];
//全為0struct
in_addrunsigned
char}(3)in_addr結(jié)構(gòu),專門用來存儲(chǔ)IP地址。
Struct
in_addr{Unsigned
long
s_addrl;}26(4)這些數(shù)據(jù)結(jié)構(gòu)的一般用法:27
首先,定義一個(gè)Sockaddr_in的結(jié)構(gòu)實(shí)例,并將它清零。比如:struct
sockaddr_in
myad;memset(&myad,0,sizeof(structsockaddr_in));然后,為這個(gè)結(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)用中使用時(shí),將這個(gè)結(jié)構(gòu)強(qiáng)制轉(zhuǎn)換為sockaddr類型。如:
accept(listenfd,(sockaddr*)(&myad),&addrlen);292.本機(jī)字節(jié)順序和網(wǎng)絡(luò)字節(jié)順序30在具體計(jì)算機(jī)中的多字節(jié)數(shù)據(jù)的存儲(chǔ)順序,稱為本機(jī)字節(jié)順序。多字節(jié)數(shù)據(jù)在網(wǎng)絡(luò)協(xié)議報(bào)頭中的存儲(chǔ)順序,稱為網(wǎng)絡(luò)字節(jié)順序。網(wǎng)絡(luò)應(yīng)用程序要在不同的計(jì)算機(jī)中運(yùn)行,本機(jī)字節(jié)順序是不同的,但是,網(wǎng)絡(luò)字節(jié)順序是一定的。所以,應(yīng)用程序在編程的時(shí)候,在把
IP地址和端口號(hào)裝入套接字的時(shí)候,應(yīng)當(dāng)把它們從本機(jī)字節(jié)順序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)順序;相反,在本機(jī)輸出時(shí),應(yīng)將它們從網(wǎng)絡(luò)字節(jié)順序轉(zhuǎn)換為本機(jī)字節(jié)順序。31套接字編程接口特為解決這個(gè)問題設(shè)置了四個(gè)函數(shù):htons()
短整數(shù)本機(jī)順序轉(zhuǎn)換為網(wǎng)絡(luò)順序,用于端口號(hào)。htonl()
長(zhǎng)整數(shù)本機(jī)順序轉(zhuǎn)換為網(wǎng)絡(luò)順序,用于IP地址。32ntohs()
短整數(shù)網(wǎng)絡(luò)順序轉(zhuǎn)換為本機(jī)順序,用于端口號(hào)。ntohl()
長(zhǎng)整數(shù)網(wǎng)絡(luò)順序轉(zhuǎn)化為本機(jī)順序,用于IP地址。
這四個(gè)函數(shù)將被轉(zhuǎn)換的數(shù)值作為函數(shù)的參數(shù),函數(shù)返回值是轉(zhuǎn)換后的結(jié)果。333.點(diǎn)分十進(jìn)制的IP地址的轉(zhuǎn)換34在因特網(wǎng)中,IP
地址常常用點(diǎn)分十進(jìn)制的表示方法,但在套接字中,IP地址是無符號(hào)的長(zhǎng)整型數(shù),套接字編程接口設(shè)置了兩個(gè)函數(shù),專門用于兩種形式的IP地址的轉(zhuǎn)換。(1)inet-addr函數(shù)35unsigned
long
inet-addr(
const
char*cp)入口參數(shù)cp:點(diǎn)分十進(jìn)制形式的IP地址。返回值:網(wǎng)絡(luò)字節(jié)順序的IP地址,是無符號(hào)的長(zhǎng)整數(shù),(2)inet_ntoa函數(shù)36char*
inet_ntoa(struct
in_addr
in)入口參數(shù)in:包含長(zhǎng)整型IP地址的in_addr
結(jié)構(gòu)變量,返回值:指向點(diǎn)分十進(jìn)制IP地址的字符串的指針。通常,我們使用域名來標(biāo)識(shí)站點(diǎn),可以將文字型的主機(jī)域名直接轉(zhuǎn)換成IP地址,struct
hostent*
gethodtbyname(
constchar*
name);入口參數(shù):是站點(diǎn)的主機(jī)域名字符串,返回值: 是指向hostent
結(jié)構(gòu)的指針,hostent結(jié)構(gòu)包含主機(jī)名,主機(jī)別名數(shù)組,返回地址的類型(一般是AF-INET),地址長(zhǎng)度的字節(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)建一個(gè)套接字并返回一個(gè)整型描述符:int SOCKET(
int
Protofamily, int
Type,int
Protocol);2.綁定套接字到指定的地址BIND()40int
BIND(
int
Sockfd,
struct
sockaddr*My_addr,
int
Addrlen);3.啟動(dòng)監(jiān)聽Listen()41int
LISTEN(
int
Sockfd,
int
Queuesize);舉例:LISTEN(Sockfe,10);圖2.642監(jiān)聽套接字使用緩沖區(qū)接納多個(gè)客戶端的連接請(qǐng)求4.接收連接請(qǐng)求ACCEPT()int
ACCEPT(int
Sockfd,
struct
sockaddr*
Addr,int*
addrlen);舉例:int
clientfd;
//定義響應(yīng)套接字描述//獲得套接字//定義用于返符變量int
addrler=sizeof(sockaddr);地址結(jié)構(gòu)長(zhǎng)度。struct
sockaddr_in
cltsockaddr;回客戶端地址的結(jié)構(gòu)。clientfd=ACCEPT(listenfd,
(sockaddr*)(&cltsockaddr),&addrlen);
//接收客戶連接請(qǐng)求435.請(qǐng)求建立連接CONNECT()44int
CONNECT(
int
Sockfd,
struct
sockaddr*Service_addr,
int
Addrlen);舉例:(structif
(CONNECT(sockfd,sockaddr*)(&serv_addr),sizeof(structsockaddr))<0){報(bào)錯(cuò),并退出}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
面向連接的套接字編程實(shí)例1.實(shí)例的功能服務(wù)器對(duì)來訪的客戶計(jì)數(shù),并向客戶報(bào)告這個(gè)計(jì)數(shù)值??蛻艚⑴c服務(wù)器的一個(gè)連接并等待它的輸出。每當(dāng)連接請(qǐng)求到達(dá)時(shí),服務(wù)器生成一個(gè)可打印的ASCII串信息,將它在連接上發(fā)回,然后關(guān)閉連接??蛻麸@示收到的信息,然后退出。48
49例如,對(duì)于服務(wù)器接收的第10次客戶連接請(qǐng)求,該客戶將收到并打印如下信息:This
server
has
been
contacted10times.2.實(shí)例程序的命令行參數(shù)50實(shí)例是UNIX環(huán)境下的C程序,客戶和服務(wù)器程序在編譯后,均以命令行的方式執(zhí)行。服務(wù)器程序執(zhí)行時(shí)可以帶一個(gè)命令行參數(shù),是用來接受請(qǐng)求的監(jiān)聽套接字的協(xié)議端口號(hào)。這個(gè)參數(shù)是可選的。如果不指定端口號(hào),代碼將使用程序內(nèi)定的缺省端口號(hào)5188??蛻舫绦驁?zhí)行時(shí)可以帶兩個(gè)命令行參數(shù):一個(gè)是服務(wù)器所在計(jì)算機(jī)的主機(jī)名,另一個(gè)是服務(wù)器監(jiān)聽的協(xié)議端口號(hào)。這兩個(gè)參數(shù)都是可選的。如果沒有指定協(xié)議端口號(hào),客戶使用程序內(nèi)定的缺省值5188。如果一個(gè)參數(shù)也沒有,客戶使用缺省端口和主機(jī)名localhost,localhost是映射到客戶所運(yùn)行的計(jì)算機(jī)的一個(gè)別名。允許客戶與本地機(jī)上的服務(wù)器通信,對(duì)調(diào)試是很有用的。513.客戶程序代碼52/*----------------------------------------------------程序:client.c目的:創(chuàng)建一個(gè)套接字,通過網(wǎng)絡(luò)連接一個(gè)服務(wù)器,并打印來自服務(wù)器的信息語法:client[host[port]]**host-運(yùn)行服務(wù)器的計(jì)算機(jī)的名字port-服務(wù)器監(jiān)聽套接字所用協(xié)議端口號(hào)注意:兩個(gè)參數(shù)都是可選的。如果未指定主機(jī)名,客戶使用localhost;如果未指定端口號(hào),客戶將使用PROTOPORT中給定的缺省協(xié)議端口號(hào)*----------------------------------------------------/*
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é)議端口號(hào)*/extern
int
errno;char
localhost=“l(fā)ocalhost”;
/*缺省主機(jī)名*/main(argc,argv)int
argc;
char
*argv[];{struct
hostent
*ptrh;54/*
指向主機(jī)列表中一個(gè)條目的指針*/struct
sockaddr_in
servaddr; /*
存放服務(wù)器端網(wǎng)絡(luò)地址的結(jié)構(gòu)*/intintsockfd;port;char*
host;int
n;char buf[1000]
;/*
客戶端的套接字描述符*//*
服務(wù)器端套接字協(xié)議端口號(hào)*//*
服務(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ù),如果有,就抽取端口號(hào)。否則使用內(nèi)定的缺省值*/if(argc>2){/*
如果指定了協(xié)議端口,就轉(zhuǎn)/*
否則,使用缺省端口號(hào)*//*
如果端口號(hào)是合法的數(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{ /*
否則,打印錯(cuò)誤信息并退出*/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)建一個(gè)套接字*/sockfd
=
SOCKET(AF_INET,
SOCK_STREAM,
0);if
(sockfd
<
0)
{fprintf(stderr,
”socket
creation
failed\n”
);exit(1);}/*
請(qǐng)求連接到服務(wù)器*/if
(connect(
sockfd,
(struct
sockaddr
*)&
servaddr,sizeof(servaddr))
<
0)
{/*
連接請(qǐng)求被拒絕,fprintf(stderr,”connect
failed\n”);報(bào)錯(cuò)并退出*/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ù)器實(shí)例代碼/*程序:server.c目的:分配一個(gè)套接字,然后反復(fù)執(zhí)行如下幾步:(1)等待客戶的下一個(gè)連接(2)發(fā)送一個(gè)短消息給客戶(3)關(guān)閉與客戶的連接(4)轉(zhuǎn)向(1)步命令行語法:server[port]port
–服務(wù)器端監(jiān)聽套接字使用的協(xié)議端口號(hào)注意:端口號(hào)可選。如果未指定端口號(hào),服務(wù)器使用PROTOPORT中指定的缺省端口號(hào)**/60#include
<sys/types.h>#include
<sys/socket.h>/*
監(jiān)聽套接字的缺省/*
監(jiān)聽套接字的請(qǐng)求隊(duì)#include
<netinet/in.h>#include
<netdb.h>#include
<stdio.h>#include
<string.h>#define
PROTOPORT
5188協(xié)議端口號(hào)*/#define
QLEN
6列大小*/int visits
=
0;/*
對(duì)于客戶連接的計(jì)數(shù)*/61main(argc,argc)int
argc;char*
argv[];/*
指向主機(jī)列表中一個(gè)條目{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é)議端口號(hào)*//*
地址長(zhǎng)度*//*
供服務(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ù),如果指定了,就是用該端口號(hào),否則使用缺省端口號(hào)*/if
(argc
>
1){/*
如果指定了端口號(hào),就將它/*
否則,使用缺省端口號(hào)*/port
=atoi(argv[1]);轉(zhuǎn)換成整數(shù)*/}
else
{port
=PROTOPORT;}63if (port
>
0) /*
測(cè)試端口號(hào)是否合法
*/servaddr.sin_port=htons(
(u_short)port
);
else{ /*
打印錯(cuò)誤信息并退出
*/64number
%s\n”,
argv[1]fprintf(
stderr,
”bad
port);exit(1);}/*
創(chuàng)建一個(gè)用于監(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)聽套接字請(qǐng)求隊(duì)列的長(zhǎng)度
*/if (listen(listenfd,
QLEN)
<
0)
{fprintf(stderr,
”listen
filed\n”
);exit(1);}/*
服務(wù)器主循環(huán)—接受和處理來自客戶端的連接請(qǐng)求*/while(1){alen=sizeof(clientaddr); /*
接受客戶端連接請(qǐng)求,并生成響應(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)程的阻塞問題和對(duì)策681.什么是阻塞
阻塞是指一個(gè)進(jìn)程執(zhí)行了一個(gè)函數(shù)或者系統(tǒng)調(diào)用,該函數(shù)由于某種原因不能立即完成,因而不能返回調(diào)用它的進(jìn)程,導(dǎo)致進(jìn)程受控于這個(gè)函數(shù)而處于等待的狀態(tài),進(jìn)程的這種狀態(tài)稱為阻塞。圖2.8
RECV()函數(shù)的兩種執(zhí)行方式692.能引起阻塞的套接字調(diào)用70在Berkeley
套接字網(wǎng)絡(luò)編程接口的模型中,套接字的默認(rèn)行為是阻塞的,具體地說,在一定情況下,有多個(gè)操作套接字的系統(tǒng)調(diào)用會(huì)引起進(jìn)程阻塞。ACCEPT()READ()、RECV()和READFORM()WRITE()、SEND()和SENDTO()CONNECT()SELECT()CLOSESOCKET()3.阻塞工作模式帶來的問題采用阻塞工作模式的單進(jìn)程服務(wù)器是不能很好地同時(shí)為多個(gè)客戶服務(wù)的。圖2.9是一個(gè)例子。圖2.971采用阻塞工作模式的服務(wù)器不能很好地為多個(gè)客戶服務(wù)4.一種解決方案72
利用UNIX操作系統(tǒng)的FORK()系統(tǒng)調(diào)用,編制多進(jìn)程并發(fā)執(zhí)行的服務(wù)器程序??梢詣?chuàng)建子進(jìn)程。對(duì)于每一個(gè)客戶端,用一個(gè)專門的進(jìn)程為它服務(wù),通過進(jìn)程的并發(fā)執(zhí)行,來實(shí)現(xiàn)對(duì)多個(gè)客戶的并發(fā)服務(wù)?;镜木幊炭蚣苁牵焊高M(jìn)程代碼If
((pid
=
FORK())
==
0)
{…….子進(jìn)程代碼........}
else
if
(pid<0)
{報(bào)錯(cuò)信息}父進(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/*
啟動(dòng)套接字的監(jiān)聽*/listen(listenfd,5);addrlen
=
sizeof(sockaddr);77服務(wù)器進(jìn)入循環(huán),接受并處理來自不同客戶端的連接請(qǐng)求=/**/while
(1)
{clientfdaccept(listenfd,(sockaddr*)(&csockaddr),&addrlen);/*
accept
調(diào)用返回時(shí),表明有客戶端請(qǐng)求連接,創(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)閉與這個(gè)客戶連接的套接字*/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ù)報(bào)套接字開發(fā)網(wǎng)絡(luò)應(yīng)用程序,既可以采用客戶/服務(wù)器模式,也可以采用對(duì)等模式。圖2.1081對(duì)等模式的數(shù)據(jù)報(bào)套接字的編程模型1.對(duì)等模式2.客戶/服務(wù)器模式圖2.11 C/S模式的數(shù)據(jù)報(bào)套接字的編程模型822.4.2
兩個(gè)專用的系統(tǒng)調(diào)用83發(fā)送數(shù)據(jù)報(bào)SENDTO()int
SENDTO(
int
sockfd,
const
void*
msg,
intlen,
unsigned
int
flags,
struct
sockaddr*
to,int
tolen);接收數(shù)據(jù)報(bào)RECVFROM()int
RECVFROM(
int
sockfd,
void*
buf,
intlen,unsigned
int
flags,
struct
sockaddr*
from,int*
fromlen
)2.4.3
數(shù)據(jù)報(bào)套接字的對(duì)等模式編程實(shí)例聊天程序#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;/*
判斷用戶輸入的命令行是否正確,如果有錯(cuò),提示用法*/if
(argc!=5){85目的
IP
目的端口 源
IP
源端口
\n",printf("用法:%sargv[0]);exit(0);}/*
設(shè)定中斷處理函數(shù),并設(shè)置時(shí)間限制*/signal(SIGALRM,int_proc);alarm(timer);/*
建立數(shù)據(jù)報(bào)套接字*/sockfd
=
socket(AF_INET,
SOCK_DGRAM,
0);if
(sockfd
<
0)
{fprintf(stderr,
"socket
error!\n");exit(1);}86/*為結(jié)構(gòu)變量daddr的各個(gè)字段賦值*/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的各個(gè)字段賦值*/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等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 第四節(jié) 給水用水量標(biāo)準(zhǔn) 第五節(jié) 給水設(shè)計(jì)流21課件講解
- 2024秋新滬粵版物理8年級(jí)上冊(cè)教學(xué)課件 1.3 長(zhǎng)度和時(shí)間測(cè)量的應(yīng)用
- 《感壓膠基礎(chǔ)技術(shù)》課件
- 《乳房疾病》課件
- 內(nèi)蒙古烏蘭察布市集寧區(qū)2024屆九年級(jí)上學(xué)期期末考試數(shù)學(xué)試卷(含解析)
- 養(yǎng)老院老人請(qǐng)假審批制度
- 《電工基礎(chǔ)知識(shí)講解》課件
- 《創(chuàng)新的原點(diǎn)》課件
- 教培退款協(xié)議書(2篇)
- 《礦內(nèi)空氣》課件
- 設(shè)備維保的安全操作與個(gè)人防護(hù)措施
- 多導(dǎo)睡眠報(bào)告
- 景德鎮(zhèn)陶瓷報(bào)告
- 降低針刺傷發(fā)生率品管圈課件
- 單招考試物理基礎(chǔ)知識(shí)梳理
- 降低墜床跌倒品管圈課件
- 壓瘡QCC匯報(bào) 降低壓瘡事件的發(fā)生率品管圈護(hù)理課件
- 初中九年級(jí)數(shù)學(xué)課件-反比例函數(shù)k的幾何意義
- 2024年P(guān)C行業(yè)分析報(bào)告及未來發(fā)展趨勢(shì)
- 壓鑄產(chǎn)品噴漆技巧培訓(xùn)課件
- 廣東省佛山市順德區(qū)2023-2024學(xué)年九年級(jí)上學(xué)期期末考試語文試題(含答案)
評(píng)論
0/150
提交評(píng)論