既然將套接字端點(diǎn)表示為文件描述符,那么只要建立連接,就可以使用read和write來(lái)通過(guò)套接字通信。通過(guò)在connect函數(shù)里設(shè)置對(duì)方地址,數(shù)據(jù)報(bào)套接字也可以“連接”。在套接字描述符上采用read和write是非常有意義的,因?yàn)榭梢詡鬟f套接字描述符到那些原先設(shè)計(jì)為處理本地文件的函數(shù)。而且可以安排傳遞套接字描述符到執(zhí)行程序的子進(jìn)程,該子進(jìn)程并不了解套接字。
盡管可以通過(guò)read和write交換數(shù)據(jù),但這就是這兩個(gè)函數(shù)所能做的一切。如果想指定選項(xiàng)、從多個(gè)客戶端接收數(shù)據(jù)包或者發(fā)送帶外數(shù)據(jù),需要采用6個(gè)傳遞數(shù)據(jù)的套接字函數(shù)中的一個(gè)。
三個(gè)函數(shù)用來(lái)發(fā)送數(shù)據(jù),三個(gè)用來(lái)接收數(shù)據(jù)。首先,考察用于發(fā)送數(shù)據(jù)的函數(shù)。
最簡(jiǎn)單的是send,它和write很像,但是可以指定標(biāo)志來(lái)改變處理傳輸數(shù)據(jù)的方式。
#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);返回值:若成功則返回發(fā)送的字節(jié)數(shù),若出錯(cuò)則返回-1
類似write,使用send時(shí)套接字必須已經(jīng)連接。參數(shù)buf和nbytes與write中的含義一致。
然而,與write不同的是,send支持第四個(gè)參數(shù)flags。其中3個(gè)標(biāo)志是Single UNIX Specification規(guī)定的,但是其他標(biāo)志通常實(shí)現(xiàn)也支持。表16-7總結(jié)了這些標(biāo)志。
表16-7 send套接字調(diào)用標(biāo)志
如果send成功返回,并不必然表示連接另一端的進(jìn)程接收數(shù)據(jù)。所保證的僅是當(dāng)send成功返回時(shí),數(shù)據(jù)已經(jīng)無(wú)錯(cuò)誤地發(fā)送到網(wǎng)絡(luò)上。
對(duì)于支持為報(bào)文設(shè)限的協(xié)議,如果單個(gè)報(bào)文超過(guò)協(xié)議所支持的最大尺寸,send失敗并將errno設(shè)置為EMSGSIZE;對(duì)于字節(jié)流協(xié)議,send會(huì)阻塞直到整個(gè)數(shù)據(jù)被傳輸。
函數(shù)sendto和send很類似。區(qū)別在于sendto允許在無(wú)連接的套接字上指定一個(gè)目標(biāo)地址。
#include <sys/socket.h>ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *destaddr, socklen_t destlen);返回值:若成功則返回發(fā)送的字節(jié)數(shù),若出錯(cuò)則返回-1
對(duì)于面向連接的套接字,目標(biāo)地址是忽略的,因?yàn)槟繕?biāo)地址蘊(yùn)含在連接中。對(duì)于無(wú)連接的套接字,不能使用send,除非在調(diào)用connect時(shí)預(yù)先設(shè)定了目標(biāo)地址,或者采用sendto來(lái)提供另外一種發(fā)送報(bào)文方式。
可以使用不止一個(gè)的選擇來(lái)通過(guò)套接字發(fā)送數(shù)據(jù)。可以調(diào)用帶有msghdr結(jié)構(gòu)的sendmsg來(lái)指定多重緩沖區(qū)傳輸數(shù)據(jù),這和writev很相像(http://www.CUOXin.com/nufangrensheng/p/3559304.html)。
#include <sys/socket.h>ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);返回值:若成功則返回發(fā)送的字節(jié)數(shù),出錯(cuò)則返回-1
POSIX.1定義了msghdr結(jié)構(gòu),它至少應(yīng)該有如下成員:
struct msghdr { void *msg_name; /* optional address */ socklen_t msg_namelen; /* address size in bytes */ struct iovec *msg_iov; /* array of I/O buffers */ int msg_iovlen; /* number of elements in array */ void *msg_control; /* ancillary data */ socklen_t msg_controllen; /* number of ancillary bytes */ int msg_flags; /* flags for received message */ ...};
iovec結(jié)構(gòu)可參考http://www.CUOXin.com/nufangrensheng/p/3559304.html。
下面是用于接收數(shù)據(jù)的函數(shù)。
函數(shù)recv和read很像,但是允許指定選項(xiàng)來(lái)控制如何接收數(shù)據(jù)。
#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);返回值:以字節(jié)計(jì)數(shù)的消息長(zhǎng)度,若無(wú)可用消息或?qū)Ψ揭呀?jīng)按序結(jié)束則返回0,出錯(cuò)則返回-1
表16-8總結(jié)了flags標(biāo)志。其中只有三個(gè)標(biāo)志是Single UNIX Specification規(guī)定的。
表16-8 recv套接字調(diào)用標(biāo)志
當(dāng)指定MSG_PEEK標(biāo)志時(shí),可以查看下一個(gè)要讀的數(shù)據(jù)但不會(huì)真正取走。當(dāng)再次調(diào)用read或recv函數(shù)時(shí)會(huì)返回剛才查看的數(shù)據(jù)。
對(duì)于SOCK_STREAM套接字,接收的數(shù)據(jù)可以比請(qǐng)求少。標(biāo)志MSG_WAITALL阻止這種行為,除非所需數(shù)據(jù)全部收到,recv才會(huì)返回。對(duì)于SOCK_DGRAM和SOCK_SEQPACKET套接字,MSG_WAITALL標(biāo)志沒(méi)有改變什么行為,因?yàn)檫@些基于報(bào)文的套接字類型一次讀取就返回整個(gè)報(bào)文。
如果發(fā)送者已經(jīng)調(diào)用shutdown(http://www.CUOXin.com/nufangrensheng/p/3564695.html)來(lái)結(jié)束傳輸,或者網(wǎng)絡(luò)協(xié)議支持默認(rèn)的順序關(guān)閉并且發(fā)送端已經(jīng)關(guān)閉,那么當(dāng)所有數(shù)據(jù)接收完畢后,recv返回0。
如果有興趣定位發(fā)送者,可以使用recvfrom來(lái)得到數(shù)據(jù)發(fā)送者的源地址。
#include <sys/socket.h>ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags, struct sockaddr *restrict addr, socklen_t *restrict addrlen);返回值:以字節(jié)計(jì)數(shù)的消息長(zhǎng)度,若無(wú)可用消息或?qū)Ψ揭呀?jīng)按序結(jié)束則返回0,若出錯(cuò)則返回-1
如果addr非空,它將包含數(shù)據(jù)發(fā)送者的套接字端點(diǎn)地址。當(dāng)調(diào)用recvfrom時(shí),需要設(shè)置addrlen參數(shù)指向一個(gè)包含addr所指的套接字緩沖區(qū)字節(jié)大小的整數(shù)。返回時(shí),該整數(shù)設(shè)為該地址的實(shí)際大小。
因?yàn)榭梢垣@得發(fā)送者的實(shí)際地址,recvfrom通常用于無(wú)連接套接字。否則recvfrom等同于recv。
為了將接收到的數(shù)據(jù)送入多個(gè)緩沖區(qū)(類似于readv(http://www.CUOXin.com/nufangrensheng/p/3559304.html)),或者想接收輔助數(shù)據(jù),可以使用recvmsg。
#include <sys/socket.h>ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);返回值:以字節(jié)計(jì)數(shù)的消息長(zhǎng)度,若無(wú)可用消息或?qū)Ψ揭呀?jīng)按序結(jié)束則返回0,若出錯(cuò)則返回-1
結(jié)構(gòu)msghdr(在sendmsg中見(jiàn)過(guò))被recvmsg用于指定接收數(shù)據(jù)的輸入緩沖區(qū)。可以設(shè)置參數(shù)flags來(lái)改變r(jià)ecvmsg的默認(rèn)行為。返回時(shí),msghdr結(jié)構(gòu)中的msg_flags字段被設(shè)為所接收數(shù)據(jù)的各種特征(進(jìn)入recvmsg時(shí)msg_flags被忽略)。從recvmsg中返回的各種可能值總結(jié)在表16-9中。
表16-9 從recvmsg中返回的msg_flags標(biāo)志
實(shí)例:面向連接的客戶端
程序清單16-4顯示了一個(gè)客戶端命令,該命令用于與服務(wù)器通信以獲得系統(tǒng)命令uptime的輸出。該服務(wù)成為“remote uptime”(簡(jiǎn)稱為“ruptime”)。
程序清單16-4 用于獲取服務(wù)器uptime的客戶端命令
#include "apue.h"#include <netdb.h>#include <errno.h>#include <sys/socket.h>#define MAXADDRLEN 256#define BUFLEN 128 extern int connect_retry(int, const struct sockaddr *, socklen_t);void PRint_uptime(int sockfd){ int n; char buf[BUFLEN]; while(( n = recv(sockfd, buf, BUFLEN, 0)) > 0) write(STDOUT_FILENO, buf, n); if(n < 0) err_sys("recv error");}int main(int argc, char *argv[]){ struct addrinfo *ailist, *aip; struct addrinfo hint; int sockfd, err; if(argc != 2) err_quit("usage: ruptime hostname"); hint.ai_flags = 0; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; if((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0) err_quit("getaddrinfo error: %s", gai_strerror(err)); for(aip = ailist; aip != NULL; aip = aip->ai_next) { if((sockfd = socket(aip->ai_family, SOCK_STREAM, 0)) < 0) err = errno; if(connect_retry(sockfd, aip->ai_addr, aip->ai_addrlen) < 0) { err = errno; } else { print_uptime(sockfd); exit(0); } } fprintf(stderr, "can't connect to %s: %s/n", argv[1], strerror(err)); exit(1);}
其中,connect_retry函數(shù)見(jiàn):http://www.CUOXin.com/nufangrensheng/p/3565858.html中的程序清單16-2
這個(gè)程序連接服務(wù)器,讀取服務(wù)器發(fā)送過(guò)來(lái)的字符串并將其打印到標(biāo)準(zhǔn)輸出。既然使用SOCK_STREAM套接字,就不能保證在一次recv調(diào)用中會(huì)讀取整個(gè)字符串,所以需要重復(fù)調(diào)用直到返回0。
如果服務(wù)器支持多重網(wǎng)絡(luò)接口或多重網(wǎng)絡(luò)協(xié)議,函數(shù)getaddrinfo會(huì)返回不止一個(gè)候選地址。輪流嘗試每個(gè)地址,當(dāng)找到一個(gè)允許連接到服務(wù)的地址時(shí)便可停止。
編譯上面的程序成功后,執(zhí)行時(shí)出現(xiàn)錯(cuò)誤:getaddrinfo error:Servname not supported for ai_socktype,后來(lái)經(jīng)查詢?cè)趆ttp://blog.163.com/yjie_life/blog/static/16319833720110311528528/找到了解決辦法。其原因是我們?cè)趃etaddrinfo第二個(gè)參數(shù)傳入的服務(wù)名“ruptime”還沒(méi)有分配端口號(hào),我們可以手動(dòng)為其添加端口號(hào),只需在/etc/services文件中添加一行:ruptime 8888/tcp 其中8888是分配的端口號(hào),需要大于1024且不與其他服務(wù)的端口號(hào)重復(fù)就行,后面的tcp是協(xié)議。
實(shí)例:面向連接的服務(wù)器
程序清單16-5顯示服務(wù)器程序,用來(lái)提供uptime命令到程序清單16-4的客戶端程序的輸出
程序清單16-5 提供系統(tǒng)uptime的服務(wù)器程序
#include "apue.h"#include <netdb.h>#include <errno.h>#include <syslog.h>#include <sys/socket.h>#define BUFLEN 128#define QLEN 10#ifndef HOST_NAME_MAX#define HOST_NAME_MAX 256#endifextern int initserver(int, struct sockaddr *, socklen_t, int);voidserve(int sockfd){ int clfd; FILE *fp; char buf[BUFLEN]; for(;;) { clfd = accept(sockfd, NULL, NULL); if(clfd < 0) { syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno)); exit(1); } if((fp = popen("/usr/bin/uptime", "r")) == NULL) { sprintf(buf, "error: %s/n", strerror(errno)); send(clfd, buf, strlen(buf), 0); } else { while(fgets(buf, BUFLEN, fp) != NULL) send(clfd, buf, strlen(buf), 0); pclose(fp); } close(clfd); }}intmain(int argc, char *argv[]){ struct addrinfo *ailist, *aip; struct addrinfo hint; int sockfd, err, n; char *host; if(argc != 1) err_quit("usage: ruptimed");#ifdef _SC_HOST_NAME_MAX n = sysconf(_SC_HOST_NAME_MAX); if(n < 0) /* best guess */#endif n = HOST_NAME_MAX; host = malloc(n); if(host == NULL) err_sys("malloc error"); if(gethostname(host, n) < 0) err_sys("gethostname error"); daemonize("ruptimed"); hint.ai_flags = AI_CANONNAME; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; if((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) { syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err)); exit(1); } for(aip = ailist; aip != NULL; aip = aip->ai_next) { if((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN)) >= 0) { serve(sockfd); exit(0); } } exit(1);}
其中,
initserver函數(shù)見(jiàn):http://www.CUOXin.com/nufangrensheng/p/3565858.html中的程序清單16-3
daemonize函數(shù)見(jiàn):http://www.CUOXin.com/nufangrensheng/p/3544104.html中的程序清單13-1
為了找到地址,服務(wù)器程序需要獲得其運(yùn)行時(shí)的主機(jī)名字。一些系統(tǒng)不定義_SC_HOST_NAME_MAX常量,因此這種情況下使用HOST_NAME_MAX。如果系統(tǒng)不定義HOST_NAME_MAX就自己定義。POSXI.1規(guī)定該值的最小值為255字節(jié),不包括終結(jié)符,因此定義HOST_NAME_MAX為256以包括終結(jié)符。
通過(guò)調(diào)用gethostname,服務(wù)器程序獲得主機(jī)名字。并查看遠(yuǎn)程uptime服務(wù)(ruptime)地址。可能會(huì)有多個(gè)地址返回,但簡(jiǎn)單地選擇第一個(gè)來(lái)建立被動(dòng)套接字端點(diǎn),在這個(gè)端點(diǎn)等待到來(lái)的連接請(qǐng)求。
實(shí)例:另一個(gè)面向連接的服務(wù)器
前面說(shuō)過(guò)采用文件描述符來(lái)訪問(wèn)套接字是非常有意義的,因?yàn)樵试S程序?qū)β?lián)網(wǎng)環(huán)境的網(wǎng)絡(luò)訪問(wèn)一無(wú)所知。程序清單16-6中顯示的服務(wù)器程序版本顯示了這一點(diǎn)。為了代替從uptime命令中讀取輸出并發(fā)送到客戶端,服務(wù)器安排uptime命令的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)出錯(cuò)替換為連接到客戶端的套接字端點(diǎn)。
程序清單16-6 用于顯示命令直接寫(xiě)到套接字的服務(wù)器程序
#include "apue.h"#include <netdb.h>#include <errno.h>#include <syslog.h>#include <fcntl.h>#include <sys/socket.h>#include <sys/wait.h>#define QLEN 10#ifndef HOST_NAME_MAX#define HOST_NAME_MAX 256#endifextern int initserver(int, struct sockaddr *, socklen_t, int);voidserve(int sockfd){ int clfd, status; pid_t pid; for(;;) { clfd = accept(sockfd, NULL, NULL); if(clfd < 0) { syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno)); exit(1); } if((pid = fork()) < 0) { syslog(LOG_ERR, "ruptimed: fork error: %s", strerror(errno)); exit(1); } else if(pid == 0) /* child */ { /* * The parent called daemonize, so * STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO * are already open to /dev/null. Thus, the call to * close doesn't need to be protected by checks that * clfd isn't already equal to one of these values. */ if(dup2(clfd, STDOUT_FILENO) != STDOUT_FILENO || dup2(clfd, STDERR_FILENO) != STDERR_FILENO) { syslog(LOG_ERR, "ruptimed: unexpected error"); exit(1); } close(clfd); execl("/usr/bin/uptime", "uptime", (char *)0); syslog(LOG_ERR, "ruptimed: unexpected return from exec: %s", strerror(errno)); } else /* parent */ { close(clfd); waitpid(pid, &status, 0); } }}intmain(int argc, char *argv[]){ struct addrinfo *ailist, *aip; struct addrinfo hint; int sockfd, err, n; char *host; if(argc != 1) err_quit("usage: ruptimed");#ifdef _SC_HOST_NAME_MAX n = sysconf(_SC_HOST_NAME_MAX); if(n < 0) /* best guess */#endif n = HOST_NAME_MAX; host = malloc(n); if(host == NULL) err_sys("malloc error"); if(gethostname(host, n) < 0) err_sys("gethostname error"); daemonize("ruptimed"); hint.ai_flags = AI_CANONNAME; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; if((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) { syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err)); exit(1); } for(aip = ailist; aip != NULL; aip = aip->ai_next) { if((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN)) >= 0) { serve(sockfd); exit(0); } } exit(1);}
以前的方式是采用popen來(lái)運(yùn)行uptime命令,并從連接到命令標(biāo)準(zhǔn)輸出的管道讀取輸出,現(xiàn)在采用fork來(lái)創(chuàng)建一個(gè)子進(jìn)程,并使用dup2使子進(jìn)程的STDIN_FILENO的副本打開(kāi)到/dev/null、STDOUT_FILENO和STDERR_FILENO打開(kāi)到套接字端點(diǎn)。當(dāng)執(zhí)行uptime時(shí),命令將結(jié)果寫(xiě)到標(biāo)準(zhǔn)輸出,該標(biāo)準(zhǔn)輸出連到套接字,所以數(shù)據(jù)被送到ruptime客戶端命令。
父進(jìn)程可以安全地關(guān)閉連接到客戶端的文件描述符,因?yàn)樽舆M(jìn)程仍舊打開(kāi)著。父進(jìn)程等待子進(jìn)程處理完畢,所以子進(jìn)程不會(huì)變成僵死進(jìn)程。既然運(yùn)行uptime花費(fèi)時(shí)間不會(huì)太長(zhǎng),父進(jìn)程在接受下一個(gè)連接請(qǐng)求之前,可以等待子進(jìn)程退出。不過(guò),這種策略不適合子進(jìn)程運(yùn)行時(shí)間比較長(zhǎng)的情況。
前面的例子采用面向連接的套接字。但如何選擇合適的套接字類型?何時(shí)采用面向連接的套接字,何時(shí)采用無(wú)連接的套接字呢?答案取決于要做的工作以及對(duì)錯(cuò)誤的容忍程度。
對(duì)于無(wú)連接的套接字,數(shù)據(jù)包的到來(lái)可能已經(jīng)沒(méi)有次序,因此當(dāng)所有的數(shù)據(jù)不能放在一個(gè)包里時(shí),在應(yīng)用程序里必須關(guān)心包的次序。包的最大尺寸是通信協(xié)議的特性。并且對(duì)于無(wú)連接套接字,包可能丟失。如果應(yīng)用程序不能容忍這種丟失,必須使用面向連接的套接字。
容忍包丟失意味著兩個(gè)選擇。如果想和對(duì)方可靠通信,必須對(duì)數(shù)據(jù)報(bào)編號(hào),如果發(fā)現(xiàn)包丟失,則要求對(duì)方重新傳輸。既然包可能因延遲而疑似丟失,我們要求重傳,但該包卻又出現(xiàn),與重傳過(guò)來(lái)的包重復(fù)。因此必須識(shí)別重復(fù)包,如果出現(xiàn)重復(fù)包,則將其丟棄。
另外一個(gè)選擇是通過(guò)讓用戶再次嘗試命令來(lái)處理錯(cuò)誤。對(duì)于簡(jiǎn)單的應(yīng)用程序,這就足夠;但對(duì)于復(fù)雜的應(yīng)用程序,這種處理方式通常不是可行的選擇,一般在這種情況下使用面向連接的套接字更為可取。
面向連接的套接字的缺陷在于需要更多的時(shí)間和工作來(lái)建立一個(gè)連接,并且每個(gè)連接需要從操作系統(tǒng)中消耗更多的資源。
實(shí)例:無(wú)連接的客戶端
程序清單16-7中的程序是采用數(shù)據(jù)報(bào)套接字接口的uptime客戶端命令版本。
程序清單16-7 采用數(shù)據(jù)報(bào)服務(wù)的客戶端命令
#include "apue.h"#include <netdb.h>#include <errno.h>#include <sys/socket.h>#define BUFLEN 128#define TIMEOUT 20void sigalrm(int signo){}void print_uptime(int sockfd, struct addrinfo *aip){ int n; char buf[BUFLEN]; buf[0] = 0; if(sendto(sockfd, buf, 1, 0, aip->ai_addr, aip->ai_addrlen) < 0) err_sys("sendto error"); alarm(TIMEOUT); if((n = recvfrom(sockfd, buf, BUFLEN, 0, NULL, NULL)) < 0) { if(errno != EINTR) alarm(0); err_sys("recv error"); } alarm(0); write(STDOUT_FILENO, buf, 0);}int main(int argc, char *argv[]){ struct addrinfo *ailist, *aip; struct addrinfo hint; int sockfd, err; struct sigaction sa; if(argc != 2) err_quit("usage: ruptime hostname"); sa.sa_handler = sigalrm; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); if(sigaction(SIGALRM, &sa, NULL) < 0) err_sys("sigaction error"); hint.ai_flags = 0; hint.ai_family = 0; hint.ai_socktype = SOCK_DGRAM; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; if((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0) err_quit("getaddrinfo error: %s", gai_strerror(err)); for(aip = ailist; aip != NULL; aip = aip->ai_next) { if((sockfd = socket(aip->ai_family, SOCK_DGRAM, 0)) < 0) { err = errno; } else { print_uptime(sockfd, aip); exit(0); } } fprintf(stderr, "can't contact %s: %s/n", argv[1], strerror(err)); exit(1);}
除了為SIGALRM增加了一個(gè)信號(hào)處理程序以外,基于數(shù)據(jù)報(bào)的客戶端main函數(shù)和面向連接的客戶端中的類似。使用alarm函數(shù)來(lái)避免調(diào)用recvfrom時(shí)無(wú)限期阻塞。
對(duì)于面向連接的協(xié)議,需要在交換數(shù)據(jù)前連接服務(wù)器。對(duì)于服務(wù)器來(lái)說(shuō),到來(lái)的連接請(qǐng)求已經(jīng)足夠判斷出所需提供給客戶端的服務(wù)。但是對(duì)于基于數(shù)據(jù)報(bào)的協(xié)議,需要有一種方法來(lái)通知服務(wù)器需要它提供服務(wù)。本例中,只是簡(jiǎn)單地給服務(wù)器發(fā)送1字節(jié)的消息。服務(wù)器接收后從包中得到地址,并使用這個(gè)地址來(lái)發(fā)送響應(yīng)消息。如果服務(wù)器提供多個(gè)服務(wù),可以使用這個(gè)請(qǐng)求消息來(lái)指示所需要的服務(wù),但既然服務(wù)器只做一件事情,1字節(jié)消息的內(nèi)容是無(wú)關(guān)緊要的。
如果服務(wù)器不在運(yùn)行狀態(tài),客戶端調(diào)用recvfrom便會(huì)無(wú)限期阻塞。對(duì)于面向連接的例子,如果服務(wù)器不運(yùn)行,connect調(diào)用會(huì)失敗。為了避免無(wú)限期阻塞,調(diào)用recvfrom之前設(shè)置警告時(shí)鐘。
實(shí)例:無(wú)連接服務(wù)器
程序清單16-8中的程序是數(shù)據(jù)報(bào)版本的uptime服務(wù)器程序。
程序清單16-8 基于數(shù)據(jù)報(bào)提供系統(tǒng)uptime的服務(wù)器程序
#include "apue.h"#include <netdb.h>#include <errno.h>#include <syslog.h>#include <sys/socket.h>#define BUFLEN 128#define MAXADDRLEN 256#ifndef HOST_NAME_MAX#define HOST_NAME_MAX 256#endifextern int initserver(int, struct sockaddr *, socklen_t, int);voidserve(int sockfd){ int n; socklen_t alen; FILE *fp; char buf[BUFLEN]; char abuf[MAXADDRLEN]; for(;;) { alen = MAXADDRLEN; if((n = recvfrom(sockfd, buf, BUFLEN, 0, (struct sockaddr *)abuf, &alen)) < 0) { syslog(LOG_ERR, "ruptimed: recvfrom error: %s", strerror(errno)); exit(1); } if((fp = popen("/usr/bin/uptime", "r")) == NULL) { sprintf(buf, "error: %s/n", strerror(errno)); sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)abuf, alen); } else { if(fgets(buf, BUFLEN, fp) != NULL) sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)abuf, alen); pclose(fp); } } }intmain(int argc, char *argv[]){ struct addrinfo *ailist, *aip; struct addrinfo hint; int sockfd, err, n; char *host; if(argc != 1) { err_quit("usage: ruptimed"); }#ifdef _SC_HOST_NAME_MAX n = sysconf(_SC_HOST_NAME_MAX); if(n < 0) /* best guess */#endif n = HOST_NAME_MAX; host = malloc(n); if(host == NULL) err_sys("malloc error"); if(gethostname(host, n) < 0) err_sys("gethostname error"); daemonize("ruptimed"); hint.ai_flags = AI_CANONNAME; hint.ai_family = 0; hint.ai_socktype = SOCK_DGRAM; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; if((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) { syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s", gai_strerror(err)); exit(1); } for(aip = ailist; aip != NULL; aip = aip->ai_next) { if((sockfd = initserver(SOCK_DGRAM, aip->ai_addr, aip->ai_addrlen, 0)) >= 0) { serve(sockfd); exit(0); } } exit(1);}
服務(wù)器在recvfrom中阻塞等待服務(wù)請(qǐng)求。當(dāng)一個(gè)請(qǐng)求到達(dá)時(shí),保存請(qǐng)求者地址并使用popen來(lái)運(yùn)行uptime命令。采用sendto函數(shù)將輸出發(fā)送到客戶端,其目標(biāo)地址就設(shè)為剛才的請(qǐng)求者地址。
本篇博文內(nèi)容摘自《UNIX環(huán)境高級(jí)編程》(第2版),僅作個(gè)人學(xué)習(xí)記錄所用。關(guān)于本書(shū)可參考:http://www.apuebook.com/。
新聞熱點(diǎn)
疑難解答
圖片精選