套接字是什么?他的定義如此簡單(dan)
時間:2018-07-20 來源:未知
套接字(zi)是(shi)一種(zhong)通(tong)信機制,憑借這(zhe)種(zhong)機制,客(ke)戶(hu)(hu)/服務(wu)器系(xi)統的(de)開發工作既可(ke)(ke)(ke)以(yi)(yi)在本地單(dan)機上進行,也可(ke)(ke)(ke)以(yi)(yi)跨網絡(luo)進行,Linux所提供的(de)功能(如打印(yin)服務(wu),ftp等)通(tong)常都是(shi)通(tong)過套接字(zi)來進行通(tong)信的(de),套接字(zi)的(de)創(chuang)建(jian)和(he)(he)使(shi)用與管道是(shi)有區別的(de),因為(wei)套接字(zi)明確(que)地將客(ke)戶(hu)(hu)和(he)(he)服務(wu)器區分出來,套接字(zi)可(ke)(ke)(ke)以(yi)(yi)實現將多(duo)個客(ke)戶(hu)(hu)連接到一個服務(wu)器。
套接字屬性
套接字的(de)特性由3個(ge)屬性確(que)定,他(ta)們(men)是(shi),域,類(lei)型和(he)協議
域(yu)指定(ding)套接字(zi)通信中使用的(de)網絡(luo)介質,最常(chang)見的(de)套接字(zi)域(yu)是AF_INET,它指的(de)是Internet網絡(luo)
套接字類型
一(yi)個套接(jie)字可能有多種(zhong)不同的通(tong)信方式
流(liu)(liu)套接(jie)(jie)(jie)字(zi),流(liu)(liu)套接(jie)(jie)(jie)字(zi)提(ti)供一個有序(xu),可靠,雙向節(jie)流(liu)(liu)的鏈(lian)接(jie)(jie)(jie),流(liu)(liu)套接(jie)(jie)(jie)字(zi)由類(lei)型SOCK_STREAM指定,它(ta)是(shi)在AF_INET域(yu)中通過TCP/IP鏈(lian)接(jie)(jie)(jie)實現的,這就(jiu)是(shi)套接(jie)(jie)(jie)字(zi)類(lei)型(其實就(jiu)是(shi)通信方(fang)式)
與(yu)流(liu)套(tao)接(jie)字相反,由類型SOCK_DGRAM指(zhi)定的數(shu)據(ju)(ju)報套(tao)接(jie)字不建(jian)立和(he)維持一個連接(jie),它對可(ke)(ke)以(yi)發(fa)送的數(shu)據(ju)(ju)長度有限制(zhi),數(shu)據(ju)(ju)報作為(wei)一個單獨的網(wang)絡消息被傳輸,它可(ke)(ke)能(neng)會丟(diu)失,復制(zhi)或(huo)亂序
最后一個是(shi)套接字協議,通常使用(yong)默認就(jiu)(jiu)可以了(也(ye)就(jiu)(jiu)是(shi)最后一個參數填(tian)0)
創建套接字
socket系統調(diao)用創建一個套接字(zi)并返(fan)回一個描述符(fu),該(gai)描述符(fu)可(ke)以用來(lai)訪問該(gai)套接字(zi)
#include
#include
int socket(int domain,int type,int protocol);
創建的(de)套接字是一條(tiao)通(tong)信(xin)線路的(de)一個(ge)端點,domain參(can)數指定(ding)協(xie)議(yi)族(zu)(使(shi)用的(de)網絡(luo)介質),type參(can)數指定(ding)這(zhe)個(ge)套接字的(de)通(tong)信(xin)類型(通(tong)信(xin)方式),protocot參(can)數指定(ding)使(shi)用的(de)協(xie)議(yi)
domain參數可以指定如下協議族
AF_UNIX UNIX域協議(文(wen)件系統套接字)
AF_INET ARPA因(yin)特網協議
AF_ISSO ISO標(biao)準協議
AF_NS Xerox網絡協議
AF_IPX Novell IPX協議
AF_APPLETALK Appletalk DDS協議
最(zui)常用的套接字(zi)域是AF_UNIX和(he)AF_INET,前(qian)者(zhe)用于通過UNIX和(he)Linux文件(jian)系統(tong)實(shi)現本地(di)套接字(zi)
socket函(han)數(shu)的第二個參(can)數(shu)type指定用于新套接字的特性,它的取值包括SOCK_STREAM和SOCK_DGRAM
SOCK_STREAM是一(yi)(yi)個有序,可靠,面向(xiang)連接的(de)雙向(xiang)字(zi)節流(liu),一(yi)(yi)般用這個
最(zui)后一個protocol參數,將(jiang)參數設為0表(biao)示(shi)使用默(mo)認協議。
套接字地址
每個套接字(端點)都有其自己的地址格(ge)式,對于AF_UNIX套接字來(lai)說,它的地址由結(jie)構(gou)sockaddr_un來(lai)描(miao)述,該結(jie)構(gou)體(ti)定義在頭文件(jian)sys/un.h中,如下:
struct sockaddr_un {
sa_family_t sun_family; //套接字域
char sun_path[];//名字
};
而在AF_INET域中,套接字(zi)地(di)址結(jie)構由(you)sockaddr_in來指定(ding),該結(jie)構體定(ding)義在頭文件netinet/in.h中
struct sockaddr_in {
short int sin_family;//套接字域(yu)
unsigned short int sin_port;//接口
struct in_addr sin_addr;
}
IP地址結構in_addr被定義如下:
struct in_addr {
unsigned long int s_addr;
};
命名套接字
要(yao)想讓通過(guo)socket調用創建的(de)套接(jie)(jie)字(zi)可以被(bei)其(qi)它進(jin)程使用,服務器(qi)程序就(jiu)必須給該套接(jie)(jie)字(zi)命名,如下,AF_INET套接(jie)(jie)字(zi)就(jiu)會關(guan)聯到一個IP端口號
#include
int bind(int socket,const struct sockaddr *address,size_t address_len);
bind系統調用把參數(shu)address中的地址(zhi)分配給與文件(jian)描述符socket關聯的未(wei)命名套接字
創建套(tao)接(jie)字隊列
為了能夠(gou)在(zai)套接(jie)字上接(jie)受進(jin)入鏈接(jie),服務器程序(xu)必須創建一個(ge)隊列來保(bao)存未處(chu)理的請(qing)求,它用(yong)listen系統調(diao)用(yong)來完成這一工作
#include
int listen(int socket,int backlog);
Linux系統可能會對隊列中未處理的連接的最大(da)數目做出限制
接受連接
一旦服務器程序創建并命名套(tao)接(jie)字(zi)(zi)之(zhi)后,他就可以(yi)通過(guo)accept系(xi)統調用來等待客戶(hu)建立對該套(tao)接(jie)字(zi)(zi)的(de)連接(jie)
#include
int accept(int socket,struct sockaddr *address,size_t *address_len);
accept函數將創建一(yi)個(ge)新套(tao)接字來與(yu)該客戶(hu)進行通(tong)信,并且(qie)返回新套(tao)接字描(miao)述符(這個(ge)描(miao)述符和(he)客戶(hu)端中描(miao)述符是一(yi)樣等同(tong))
請求連接
客戶程序(xu)通(tong)過一個未(wei)命名套(tao)接(jie)字和服務器監(jian)聽套(tao)接(jie)字之(zhi)間建(jian)立的連接(jie)的方法來(lai)連接(jie)到(dao)服務器,如(ru)下:
#include
int connect(int socket,const struct sockaddr *address,size_t address_len);
參(can)數socket指定的套(tao)接(jie)字(zi)將連(lian)接(jie)到(dao)參(can)數address指定的服務器套(tao)接(jie)字(zi)
關閉套接字
你可以(yi)通(tong)過調用close函數來終(zhong)止服(fu)務(wu)器(qi)和客(ke)戶(hu)上的(de)套接字連接
套接字通信
套接字可(ke)以通(tong)過調用read(),write()系統調用來進行傳(chuan)輸數(shu)據(ju)
下(xia)面是套接字的(de)一些例子
一個簡單的本地客戶(hu)
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd;
int len;
struct sockaddr_un address;//套接(jie)字地址
int result;
char ch = 'A';
sockfd = socket(AF_UNIX,SOCK_STREAM,0);//創建一(yi)個套(tao)接字(端(duan)點),并返回一(yi)個描述符
address.sun_family = AF_UNIX;//指明網絡(luo)介(jie)質
strcpy(address.sun_path,"server_socket");// 名字(zi)
len = sizeof(address);
result = connect(sockfd,(struct sockaddr *)&address,len);//請求連接到address
if(result == -1) {
perror("oops: clientl");
exit(1);
}
write(sockfd,&ch,1);//把(ba)數據(ju)寫入(ru)套接字
read(sockfd,&ch,1);//服務(wu)器處理(li)后(hou)讀(du)出處理(li)后(hou)數據
printf("char form server = %c\n",ch);
close(sockfd);
exit(0);
}
下面是(shi)一(yi)個本地服務器
#include
#include
#include
#include
#include
#include
int main()
{
int server_sockfd,client_sockfd;//定義套(tao)接字描述符
int server_len,client_len;
struct sockaddr_un server_address;//套接字地址
struct sockaddr_un client_address;//套接(jie)字地址
unlink("server_socket");
server_sockfd = socket(AF_UNIX,SOCK_STREAM,0);//創(chuang)建一個(ge)(ge)套接字(zi),并返回一個(ge)(ge)描述符
server_address.sun_family = AF_UNIX;//指定網(wang)絡介質
strcpy(server_address.sun_path,"server_socket");//名字
server_len = sizeof(server_address);
bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接字
listen(server_sockfd,5);//創(chuang)建套接字隊列
while(1) {
char ch;
printf("server waiting\n");
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address,&client_len);//接受連接
read(client_sockfd,&ch,1);//從套接字中讀取數(shu)據
ch++;// 處(chu)理數據
write(client_sockfd,&ch,1);//把數據(ju)重新寫回套接(jie)字
close(client_sockfd);
}
}
這(zhe)兩(liang)個程序運行(xing)如(ru)下(xia):
root@www:/opt/chengxu# ./server1 &
[3] 4644
root@www:/opt/chengxu# server waiting
root@www:/opt/chengxu# ./client1 & ./client1 & ./client1 &
[4] 4652
[5] 4653
[6] 4654
root@www:/opt/chengxu# server waiting
server waiting
server waiting
char form server = B
char form server = B
char form server = B
[4] Done ./client1
[5]- Done ./client1
[6]+ Done
下面看(kan)一(yi)個(ge)網絡套接字(zi)的例子,先看(kan)server程序:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int server_sockfd,client_sockfd;//定義套接字描述符
int server_len,client_len;
struct sockaddr_in server_address;//套(tao)接字(zi)地址結構體
struct sockaddr_in client_address;//套接字地址結構體
unlink("server_socket");
server_sockfd = socket(AF_INET,SOCK_STREAM,0);//創建一(yi)個(ge)套接字,并(bing)返回一(yi)個(ge)描述符(fu)
server_address.sin_family = AF_INET;//指(zhi)定網(wang)絡介質
//server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
server_address.sin_addr.s_addr = htonl(INADDR_ANY);//客戶連接(jie)到(dao)服務(wu)器的IP
server_address.sin_port = htons(9734);//客(ke)戶連接到端口
server_len = sizeof(server_address);
bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接字
listen(server_sockfd,5);//創(chuang)建套接字隊列
while(1) {
char ch;
printf("server waiting\n");
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address,&client_len);//接受連接
read(client_sockfd,&ch,1);//從套接字中讀(du)取(qu)數(shu)據(ju)
ch++;//處(chu)理數據
write(client_sockfd,&ch,1);//把處理后數據寫(xie)入套接字
close(client_sockfd);//關閉套接字
}
}
下面是客戶(hu)端程(cheng)序:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd;//套接字(zi)描述(shu)符
int len;
struct sockaddr_in address;//套(tao)接字(zi)地址結構體
int result;
char ch = 'A';
sockfd = socket(AF_INET,SOCK_STREAM,0);//創建(jian)一個(ge)套接字并返回一個(ge)描述符(fu)
address.sin_family = AF_INET;// 指定網絡介質
address.sin_addr.s_addr = htonl(INADDR_ANY);//要(yao)連接(jie)到(dao)主機IP
address.sin_port = htons(9734);//要連接到(dao)端口號
len = sizeof(address);
result = connect(sockfd,(struct sockaddr *)&address,len);//請求連接
if(result == -1) {
perror("oops: clientl");
exit(1);
}
write(sockfd,&ch,1);//把(ba)數據寫(xie)入套(tao)接字
read(sockfd,&ch,1);//服務器(qi)處理完數據后從新讀取(qu)出(chu)來
printf("char form server = %c\n",ch);
close(sockfd);
exit(0);
}
運行這兩個程序輸(shu)出如下:
root@www:/opt/chengxu# ./server2 &
[4] 4746
root@www:/opt/chengxu# server waiting
root@www:/opt/chengxu# ./client2 & ./client2 & ./client2 &
[5] 4749
[6] 4750
[7] 4751
root@www:/opt/chengxu# server waiting
server waiting
server waiting
char form server = B
char form server = B
char form server = B
[5] Done ./client2
[6]- Done ./client2
[7]+ Done ./client2
root@www:/opt/chengxu#
輸出(chu)結果基本和上(shang)(shang)面的(de)例(li)子差不多,通(tong)(tong)過上(shang)(shang)面程序可以發現客戶端寫入(ru)到套接字中的(de)數據可以從服(fu)務器(qi)中讀出(chu)來,而且(qie)客戶端會(hui)等待服(fu)務器(qi)把數據讀出(chu)處理后再(zai)把數據讀回(hui)來,這(zhe)有(you)一個(ge)順(shun)序,并不會(hui)出(chu)現亂序,有(you)點類型于(yu)管(guan)道通(tong)(tong)信而且(qie)是雙向的(de)
主機字節序(xu)和(he)網絡字節序(xu)
通過(guo)套接字接口傳遞的(de)(de)端口號(hao)和地址都是(shi)二(er)進制的(de)(de),不同(tong)計算(suan)機使用不同(tong)的(de)(de)字節序來表(biao)示整(zheng)數,如32位(wei)的(de)(de)整(zheng)數分(fen)為4個連(lian)續的(de)(de)字節,并以(yi)(yi)1-2-3-4存在(zai)內存中(zhong),這里的(de)(de)1表(biao)示最高位(wei),也即大端模(mo)式,而有的(de)(de)處(chu)理(li)器是(shi)以(yi)(yi)4-3-2-1存取的(de)(de),兩個不同(tong)的(de)(de)計算(suan)機得到的(de)(de)整(zheng)數就會不一致(zhi)
為(wei)了使不(bu)同類型的計算機可以(yi)就(jiu)通過網(wang)絡(luo)傳輸多字(zi)節(jie)(jie)的值達成一(yi)致,客(ke)戶和服務器程序必須在傳出之前,將它(ta)們內部(bu)整數(shu)表(biao)示(shi)方式轉換(huan)為(wei)網(wang)絡(luo)字(zi)節(jie)(jie)序,它(ta)們通過下面函數(shu)轉換(huan)(也(ye)就(jiu)是把端口號等轉換(huan)成統一(yi)網(wang)絡(luo)字(zi)節(jie)(jie)序)
#include
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);
這些函數將16位和32位整數在(zai)主機字(zi)(zi)節序(xu)和標準的(de)網絡字(zi)(zi)節序(xu)之(zhi)間進行轉(zhuan)換,如上面例子中用到的(de)
address.sin_addr.s_addr = htonl(INADDR_ANY);//要連接(jie)到主機IP
address.sin_port = htons(9734);//要連(lian)接到端口號(hao)
這(zhe)樣(yang)就能保證網(wang)絡字(zi)節序的(de)正確,如果(guo)你使用的(de)計算機(ji)上的(de)主(zhu)機(ji)字(zi)節序和網(wang)絡字(zi)節序相同,將(jiang)看不到(dao)任何(he)差異
網絡信息
到(dao)目前為止,我(wo)(wo)們(men)(men)客戶和服(fu)務器程序一直是把地(di)(di)址和端口編譯到(dao)它們(men)(men)自己的(de)(de)內部,對于一個更通(tong)用(yong)的(de)(de)服(fu)務器和客戶程序來(lai)說,我(wo)(wo)們(men)(men)可以通(tong)過網(wang)(wang)絡信息函數來(lai)決(jue)定應該使用(yong)的(de)(de)地(di)(di)址和端口(也就是說可以通(tong)過網(wang)(wang)絡信息函數來(lai)獲取(qu)IP和端口號(hao)等信息)
類(lei)似(si)的,如果給(gei)定一個計算機名字,你可以通過調用解析地址的主機數(shu)(shu)據庫函數(shu)(shu)來確(que)定它的IP地址等信息
主機(ji)數據庫函數在接(jie)口文件(jian)netdb.h中聲明,如下:
#include
struct hostent *gethostbyaddr(const void *addr,size_t len,int type);
struct hostent *gethostbyname(const char *name);//獲得計算(suan)機(ji)主機(ji)數據庫(ku)信息(xi)
這些(xie)函數返(fan)回的結(jie)構體(ti)中至少包括以(yi)下幾個成(cheng)員
struct hostent {
char *h_name; //name of the host
char **h_aliases;//list of aliases
int h_addrtypr;//address type
int h_length;//length in bytes of the address
char **h_addr_list;//list of address
};
如果沒有與我(wo)們(men)查(cha)詢(xun)的(de)主機或地址相關(guan)的(de)數(shu)據項,這(zhe)些信(xin)息函(han)數(shu)將(jiang)返回一個空指針
類型地,與服務(wu)及相關(guan)聯端口(kou)號有(you)關(guan)的信息(xi)也可以通過一(yi)些服務(wu)信息(xi)函數來獲取
#include
struct servent *getservbyname(const char *name,const char *proto);//檢查是否(fou)有某個服務
struct servent *getservbyport(int port,const char *proto);
proto參數指(zhi)定用于連接(jie)該服務的(de)(de)協議(yi),它的(de)(de)兩個選(xuan)項(xiang)tcp和udp,前(qian)者(zhe)用于SOCK_STREAM類型
返回結構體中(zhong)至少包含如下幾個成(cheng)員(yuan):
struct servent {
char *s_name;//name of the service
char **s_aliases;//list of aliases
int s_port;//The IP port number
char *s_proto;//The service type,usually "tcp" or "udp"
};
如果想獲取某臺(tai)計算機(ji)主機(ji)數據庫信(xin)息,可(ke)以(yi)調用(yong)(yong)gethostbyname函(han)數并且將(jiang)結果打印出來,注意,要把(ba)返回的(de)地(di)址(zhi)表轉換為(wei)正(zheng)確的(de)地(di)址(zhi)類型,并用(yong)(yong)函(han)數inet_ntoa將(jiang)它(ta)們從網絡(luo)字(zi)節序(xu)裝(zhuang)換為(wei)可(ke)打印的(de)字(zi)符串,如下(xia):
#include
char *inet_ntoa(struct in_addr in);
這個(ge)函數(shu)的作用(yong)是將(jiang)一個(ge)因(yin)特網主機地(di)址轉換為一個(ge)點分四元租(zu)格式的字符串
下面這個程序(xu)getname.c用來獲(huo)取(qu)一臺主機有關的信(xin)息,如下:
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
char *host,**names,**addrs;//接收用到(dao)的一(yi)些指針
struct hostent *hostinfo;//指(zhi)向gethostbyname函數返回的結構(gou)體指(zhi)針(zhen)
if(argc == 1) {
char myname[256];
gethostname(myname,255);
host = myname;
}
else
host = argv[1];//獲(huo)取主機名(ming)
hostinfo = gethostbyname(host);//獲取主(zhu)機數(shu)據(ju)庫
if(!hostinfo) {
fprintf(stderr,"cannot get info for host: %s\n",host);
exit(1);
}
printf("results for host %s:\n",host);
printf("Name: %s\n",hostinfo -> h_name);
printf("Aliases");
names = hostinfo -> h_aliases;
while(*names) {
printf(" %s",*names);
names++;
}
printf("\n");
if(hostinfo -> h_addrtype != AF_INET) {
fprintf(stderr,"not an IP host!\n");
exit(1);
}
//顯示主機的(de)所有(you)IP地址
addrs = hostinfo -> h_addr_list;
while(*addrs) {
printf(" %s",inet_ntoa(*(struct in_addr *)*addrs));
addrs++;
}
printf("\n");
exit(0);
}
運行這個(ge)程序(xu)輸(shu)出(chu)如(ru)下(xia)所示:
root@www:/opt/chengxu# ./getname
results for host www:
Name: www.kugoo.com
Aliases www
127.0.1.1
root@www:/opt/chengxu# ./getname localhost
results for host localhost:
Name: localhost
Aliases ip6-localhost ip6-loopback
127.0.0.1 127.0.0.1
root@www:/opt/chengxu#
下面一個例(li)子是連接到標準服務
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])//argc表示參數(shu)個數(shu),argv[]表示具體參數(shu)程序名為argv[0]
{
char *host;
int sockfd;
int len,result;
struct sockaddr_in address;//套接(jie)字地(di)址結構(gou)體
struct hostent *hostinfo;//指向(xiang)gethostbyname函數返回的(de)結構體(ti)指針
struct servent *servinfo;//指向getservbyname函數返回的結構體指針
char buffer[128];
if(argc == 1)
host = "localhost";
else
host = argv[1];
hostinfo = gethostbyname(host);//獲取主(zhu)機數據庫信(xin)息
if(!hostinfo) {
fprintf(stderr,"no host: %s\n",host);
exit(1);
}
//檢查主機上是否有(you)daytime服務
servinfo = getservbyname("daytime","tcp");
if(!servinfo) {
fprintf(stderr,"no daytime service\n");
exit(1);
}
sockfd = socket(AF_INET,SOCK_STREAM,0);//創(chuang)建一(yi)(yi)個套(tao)接字,并返回一(yi)(yi)個描述(shu)符
address.sin_family = AF_INET;//使(shi)用網(wang)絡介質
address.sin_port = servinfo -> s_port;//端口號
address.sin_addr = *(struct in_addr *)*hostinfo -> h_addr_list;//獲取主(zhu)機IP地址(zhi)
len = sizeof(address);
result = connect(sockfd,(struct sockaddr *)&address,len);//請(qing)求連接(jie)
if(result == -1) {
perror("oops:getdate");
exit(1);
}
result = read(sockfd,buffer,sizeof(buffer));
buffer[result] = '\0';
printf("read %d bytes: %s",result,buffer);
close(sockfd);
exit(0);
}
運行(xing)這個程序輸出(chu)如下:
root@www:/opt/chengxu# ./getname1 localhost
oops:getdate: Connection refused
root@www:/opt/chengxu#
之所以這樣是因(yin)為我的虛擬機中沒有啟動daytime這個服務
我用的(de)是(shi)ubuntu虛擬(ni)機(ji),下面來啟動(dong)這個daytime服務(wu)看一下
首先
root@www:/opt/chengxu# vim /etc/inetd.conf
進(jin)入到inetd的(de)配置文件(jian)把這個服(fu)務前面的(de)#號(hao)去掉,改成如(ru)下:
18 #discard dgram udp wait root internal
19 daytime stream tcp nowait root internal
20 #time stream tcp nowait root internal
然后保存退出
接下(xia)來(lai)重啟一下(xia)服(fu)務就可(ke)以了通(tong)過(guo)下(xia)面命令啟動(或重啟)xinetd服(fu)務(xinetd和openbsd-inetd差不多都(dou)是屬于守護進程)
root@www:/opt/chengxu# /etc/init.d/openbsd-inetd restart
* Restarting internet superserver inetd [ OK ]
root@www:/opt/chengxu#
可以通過下(xia)面命令看一下(xia)daytime這個服務是(shi)否處于(yu)監聽狀(zhuang)態了,如下(xia):
root@www:/opt/chengxu# netstat -a | grep daytime
tcp 0 0 *:daytime *:* LISTEN
root@www:/opt/chengxu#
下面(mian)再重新運行一下上(shang)面(mian)程序(xu)看(kan)看(kan):
root@www:/opt/chengxu# ./getname1 localhost
read 26 bytes: Sun Sep 23 23:15:14 2012
root@www:/opt/chengxu#
因特(te)網守(shou)護進程(cheng)
當(dang)有客戶(hu)端連接到某個(ge)(ge)(ge)服(fu)務時(shi),守護進(jin)(jin)程就運行相應的(de)服(fu)務器,這(zhe)使得(de)針對各項(xiang)網絡(luo)服(fu)務器不(bu)需要移植運行著,我(wo)們通常是(shi)通過(guo)一個(ge)(ge)(ge)圖形界面來(lai)配(pei)置(zhi)xinetd,ubuntu用的(de)好像是(shi)openbsd-inetd這(zhe) 個(ge)(ge)(ge)守護進(jin)(jin)程,我(wo)們可以直接修(xiu)改它(ta)(ta)(ta)的(de)配(pei)置(zhi)文(wen)件(jian)(jian),ubuntu對應的(de)是(shi)/etc/inetd.conf這(zhe)個(ge)(ge)(ge)文(wen)件(jian)(jian),就拿我(wo)們上(shang)面那個(ge)(ge)(ge)daytime這(zhe)個(ge)(ge)(ge)服(fu)務為(wei) 例(li),在ubuntu中(zhong)它(ta)(ta)(ta)默(mo)認是(shi)關(guan)閉(bi)的(de),這(zhe)時(shi)我(wo)們要進(jin)(jin)入它(ta)(ta)(ta)的(de)配(pei)置(zhi)文(wen)件(jian)(jian)里,把它(ta)(ta)(ta)前面的(de)#字符去掉就可以了(le),修(xiu)改了(le)的(de)配(pei)置(zhi)文(wen)件(jian)(jian)如下:
17 #discard stream tcp nowait root internal
18 #discard dgram udp wait root internal
19 daytime stream tcp nowait root internal
20 #time stream tcp nowait root internal
21
22 #:STANDARD: These are standard services.
23
24 #:BSD: Shell, login, exec and talk are BSD protocols.
25
26 #:MAIL: Mail, news and uucp services.
27
28 #:INFO: Info services
29
30 #:BOOT: TFTP service is provided primarily for booting. Most sites
31 # run this only on machines acting as "boot servers."
32 bootps dgram udp wait root /usr/sbin/bootpd bootpd -i -t 120
33 tftp dgram udp wait nobody /usr/sbin/tcpd /usr/sbin/in.tftpd /srv/tftproot
34 #bootps dgram udp wait root /usr/sbin/bootpd bootpd -i -t 120
之后重(zhong)啟一下守(shou)護進(jin)程就可以(yi)了,如(ru)下:
root@www:/opt/chengxu# /etc/init.d/openbsd-inetd restart
* Restarting internet superserver inetd [ OK ]
root@www:/opt/chengxu# netstat -a | grep daytime
tcp 0 0 *:daytime *:* LISTEN
root@www:/opt/chengxu#
多客戶
到(dao)目(mu)前為止(zhi),一(yi)(yi)直(zhi)都是介紹(shao)如何用套(tao)接字來實現本(ben)地的(de)和跨網絡(luo)的(de)客戶(hu)/服務器(qi)系統,一(yi)(yi)旦連接建(jian)立,套(tao)接字連接的(de)行為就類似(si)于打開底層文件(jian)描述符(fu),而且(qie)在(zai)很多方(fang)面類似(si)雙向管道,現在(zai)我們來考慮多個(ge)用戶(hu)同時連接一(yi)(yi)個(ge)服務器(qi)的(de)情況,下(xia)面是一(yi)(yi)個(ge)例子:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int server_sockfd,client_sockfd;//定義套(tao)接(jie)字描述符
int server_len,client_len;
struct sockaddr_in server_address;//套接字(zi)地址結構體
struct sockaddr_in client_address;//套接字地(di)址結構體
server_sockfd = socket(AF_INET,SOCK_STREAM,0);//創建一個套接字,并返回一個描述符
server_address.sin_family = AF_INET;//網絡介(jie)質
server_address.sin_addr.s_addr = htonl(INADDR_ANY);//要連接(jie)到的服務器IP地址
server_address.sin_port = htons(9734);//要連接到的端口號
server_len = sizeof(server_address);
bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接(jie)字
listen(server_sockfd,5);//創建(jian)套接字隊列
signal(SIGCHLD,SIG_IGN);
while(1) {
char ch;
printf("server waiting\n");
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address,&client_len);//接(jie)(jie)收(shou)連接(jie)(jie)
if(fork() == 0) {//創(chuang)建一(yi)個(ge)子進程用于傳(chuan)輸數據(ju)
read(client_sockfd,&ch,1);
sleep(5);
ch++;
write(client_sockfd,&ch,1);
close(client_sockfd);
exit(0);
}
else {
close(client_sockfd);//在父進程中關閉打開的套接字描述符
}
}
}
下面是這個程(cheng)序(xu)的運行情(qing)況
root@www:/opt/chengxu# ./server4 &
root@www:/opt/chengxu# ./client2 & ./client2 & ./client2 &
[6] 6742
[7] 6743
[8] 6744
server waiting
root@www:/opt/chengxu# server waiting
server waiting
char form server = B
char form server = B
char form server = B
[6] Done ./client2
[7]- Done ./client2
[8]+ Done ./client2
root@www:/opt/chengxu#
上面服(fu)務器程序(xu)用到(dao)了(le)子進程來(lai)處理要處理數據,這(zhe)樣一來(lai)就可以多個客戶連接到(dao)服(fu)務器了(le)
select系統調(diao)用
select系統調用允(yun)許程序同時在多個底層(ceng)文件描(miao)(miao)(miao)述(shu)符上等待用戶輸入(或完成輸出),檢測(ce)讀(du),寫,異常文件描(miao)(miao)(miao)述(shu)符集,select函數(shu)對數(shu)據結構fd_set進行(xing)操作(也(ye)就是說對文件描(miao)(miao)(miao)述(shu)符集進行(xing)操作),有一組(zu)定(ding)義好的宏可(ke)以用來控制(zhi)這些文件描(miao)(miao)(miao)述(shu)符集,如下:
#include
#include
void FD_ZERO(fd_set *fdset);//初(chu)始化(清零)文(wen)件描述符集
void FD_CLR(int fd,fd_set *fdset);//清除(chu)文(wen)件(jian)描述(shu)符(fu)集
void FD_SET(int fd,fd_set *fdset);//把(ba)文(wen)件描述符(fu)fd加到文(wen)件描述符(fu)集中
int FD_ISSET(int fd,fd_set *fdset);//檢測(ce)文件描述符變(bian)化(hua)情況
select函數還可以用一(yi)個超時值(zhi)來防止(zhi)無限(xian)期(qi)阻塞,這(zhe)個超時值(zhi)由一(yi)個timeval結構給(gei)出,如(ru)下(xia):
struct timeval {
time_t tv_sec;
long tv_usec;
}
select系統調用的原型(xing)如下所(suo)示:
#include
#include
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
select系統調(diao)用用于(yu)測試文(wen)件(jian)描述符(fu)集合中,是(shi)否有一個(ge)文(wen)件(jian)描述符(fu)已(yi)經處于(yu)可(ke)讀狀態或(huo)可(ke)寫狀態或(huo)錯(cuo)誤狀態,它(ta)將阻塞等待某個(ge)文(wen)件(jian)描述符(fu)進(jin)入上述狀態。
select函數會(hui)在發(fa)生如(ru)下情(qing)況(kuang)時(shi)(shi)(shi)返(fan)回:readfds集(ji)合中描(miao)述符(fu)可讀(也就是(shi)寫入數據了,寫完數據后(hou)會(hui)發(fa)送一個可讀信號),writefds集(ji)合中 有(you)(you)描(miao)述符(fu)可寫或errorfds集(ji)合中有(you)(you)描(miao)述符(fu)錯誤,如(ru)果這三種情(qing)況(kuang)都沒有(you)(you)發(fa)生,select函數將(jiang)在timrout指(zhi)定的超(chao)時(shi)(shi)(shi)后(hou)返(fan)回,這時(shi)(shi)(shi)所有(you)(you)文件描(miao)述 符(fu)集(ji)合將(jiang)被清(qing)空
下面是一個select系統調用的例子:
#include
#include
#include
#include
#include
#include
#include
int main()
{
char buffer[128];
int result,nread;
fd_set inputs,testfds;//定義(yi)文(wen)件描(miao)述符集合(he)
struct timeval timeout;//定義超時結構體
FD_ZERO(&inputs);//清零文(wen)件描述符集合
FD_SET(0,&inputs);//把標(biao)準輸入加到文件描述符集合中
while(1) {
testfds = inputs;
timeout.tv_sec = 2;
timeout.tv_usec = 500000;
result = select(FD_SETSIZE,&testfds,(fd_set *)NULL,(fd_set *)NULL,&timeout);//檢測是否有標準(zhun)輸入,沒(mei)有的(de)話每隔2.5秒打印一次timeout
switch(result) {
case 0:
printf("timeout\n");
break;
case -1:
perror("select");
exit(1);
default:
if(FD_ISSET(0,&testfds)) {//檢測(ce)是否有(you)標準輸入(ru)
ioctl(0,FIONREAD,&nread);//得(de)到緩沖(chong)區里(li)有多(duo)少字節要被讀取,存到nread中
if(nread == 0) {
printf("keyboard done\n");
exit(0);
}
nread = read(0,buffer,nread);//從(cong)標準輸入緩沖區(qu)中讀取數據
buffer[nread] = 0;
printf("read %d from keyboard: %s",nread,buffer);
}
break;
}
}
}
下面(mian)是這個程序運行時情況
root@www:/opt/chengxu# ./select
timeout
hello
read 6 from keyboard: hello
timeout
timeout
fread
read 6 from keyboard: fread
keyboard done
root@www:/opt/chengxu#
最后(hou)來看一個改進的多客戶/服(fu)務器程序:
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int server_sockfd,client_sockfd;//定義套接字描述(shu)符
int server_len,client_len;
struct sockaddr_in server_address;//套接字(zi)地址結構體
struct sockaddr_in client_address;//套接字(zi)地址結(jie)構體
int result;
fd_set readfds,testfds;//定義文(wen)件描述符集(ji)合
server_sockfd = socket(AF_INET,SOCK_STREAM,0);//創建(jian)一(yi)個套接字,并返回一(yi)個描(miao)述符
server_address.sin_family = AF_INET;//網(wang)絡(luo)介質(zhi)
server_address.sin_addr.s_addr = htonl(INADDR_ANY);//要連接到服務器的Ip地址
server_address.sin_port = htons(9734);//要連接到(dao)的端口(kou)號(hao)
server_len = sizeof(server_address);
bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接(jie)字
listen(server_sockfd,5);// 創建套接(jie)字隊列
FD_ZERO(&readfds);//清零描述符
FD_SET(server_sockfd,&readfds);//把server_sockfd描述(shu)(shu)符加到readfds描述(shu)(shu)符集合中
while(1) {
char ch;
int fd;
int nread;
testfds = readfds;
printf("server waiting\n");
result = select(FD_SETSIZE,&testfds,(fd_set *)0,(fd_set *)0,(struct timeval *)0);//檢測(ce)testfds描述符集合是否有變化,沒有的話(hua)阻塞(sai)等待(dai)
if(result < 1) {
perror("server5");
exit(1);
}
for(fd = 0;fd < FD_SETSIZE;fd++) {//逐個(ge)檢查描(miao)述符
if(FD_ISSET(fd,&testfds)) {//檢測那個(ge)描述符集合發生變(bian)化,這(zhe)個(ge)很重要
if(fd == server_sockfd) {//如果是server_sockfd描述符(fu)集變化
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address,&client_len);//接收連接
FD_SET(client_sockfd,&readfds);//把客戶端(duan)的client_sockfd描(miao)述符加進描(miao)述符集合(he)中(zhong),這(zhe)樣就可以用(yong)select來檢測客戶端(duan)的描(miao)述符了
printf("adding client on fd %d\n",client_sockfd);
}
else {
ioctl(fd,FIONREAD,&nread);//能得到緩(huan)沖區里有(you)多少(shao)字節要被(bei)讀(du)取,存進nread中
if(nread == 0) {
close(fd);
FD_CLR(fd,&readfds);
printf("removing client on fd %d\n",fd);
}
else {
read(fd,&ch,1);
sleep(50);
printf("serving client on fd %d\n",fd);
ch++;
write(fd,&ch,1);
}
}
}
}
}
}
這個程(cheng)序運(yun)行如下:
root@www:/opt/chengxu# ./server5 &
[6] 7344
root@www:/opt/chengxu# server waiting
root@www:/opt/chengxu# ./client2 & ./client2 & ./client2 &
[7] 7352
[8] 7353
[9] 7354
root@www:/opt/chengxu# server waiting
server waiting
server waiting
char form server = B
char form server = B
char form server = B
[7] Done ./client2
[8]- Done ./client2
[9]+ Done ./client2
root@www:/opt/chengxu#
....

