套接字是一種通信機制,憑借這種機制,客戶/服務器系統的開發工作既可以在本地單機上進行,也可以跨網絡進行,Linux所提供的功能(如打印服務,ftp等)通常都是通過套接字來進行通信的,套接字的創建和使用與管道是有區別的,因為套接字明確地將客戶和服務器區分出來,套接字可以實現將多個客戶連接到一個服務器。
套接字屬性
套接字的特性由3個屬性確定,他們是,域,類型和協議
域指定套接字通信中使用的網絡介質,最常見的套接字域是AF_INET,它指的是Internet網絡
套接字類型
一個套接字可能有多種不同的通信方式
流套接字,流套接字提供一個有序,可靠,雙向節流的鏈接,流套接字由類型SOCK_STREAM指定,它是在AF_INET域中通過TCP/IP鏈接實現的,這就是套接字類型(其實就是通信方式)
與流套接字相反,由類型SOCK_DGRAM指定的數據報套接字不建立和維持一個連接,它對可以發送的數據長度有限制,數據報作為一個單獨的網絡消息被傳輸,它可能會丟失,復制或亂序
最后一個是套接字協議,通常使用默認就可以了(也就是最后一個參數填0)
創建套接字
socket系統調用創建一個套接字并返回一個描述符,該描述符可以用來訪問該套接字
#include
#include
int socket(int domain,int type,int protocol);
創建的套接字是一條通信線路的一個端點,domain參數指定協議族(使用的網絡介質),type參數指定這個套接字的通信類型(通信方式),protocot參數指定使用的協議
domain參數可以指定如下協議族
AF_UNIX UNIX域協議(文件系統套接字)
AF_INET ARPA因特網協議
AF_ISSO ISO標準協議
AF_NS Xerox網絡協議
AF_IPX Novell IPX協議
AF_APPLETALK Appletalk DDS協議
最常用的套接字域是AF_UNIX和AF_INET,前者用于通過UNIX和Linux文件系統實現本地套接字
socket函數的第二個參數type指定用于新套接字的特性,它的取值包括SOCK_STREAM和SOCK_DGRAM
SOCK_STREAM是一個有序,可靠,面向連接的雙向字節流,一般用這個
最后一個protocol參數,將參數設為0表示使用默認協議。
套接字地址
每個套接字(端點)都有其自己的地址格式,對于AF_UNIX套接字來說,它的地址由結構sockaddr_un來描述,該結構體定義在頭文件sys/un.h中,如下:
struct sockaddr_un {
sa_family_t sun_family; //套接字域
char sun_path[];//名字
};
而在AF_INET域中,套接字地址結構由sockaddr_in來指定,該結構體定義在頭文件netinet/in.h中
struct sockaddr_in {
short int sin_family;//套接字域
unsigned short int sin_port;//接口
struct in_addr sin_addr;
}
IP地址結構in_addr被定義如下:
struct in_addr {
unsigned long int s_addr;
};
命名套接字
要想讓通過socket調用創建的套接字可以被其它進程使用,服務器程序就必須給該套接字命名,如下,AF_INET套接字就會關聯到一個IP端口號
#include
int bind(int socket,const struct sockaddr *address,size_t address_len);
bind系統調用把參數address中的地址分配給與文件描述符socket關聯的未命名套接字
創建套接字隊列
為了能夠在套接字上接受進入鏈接,服務器程序必須創建一個隊列來保存未處理的請求,它用listen系統調用來完成這一工作
#include
int listen(int socket,int backlog);
Linux系統可能會對隊列中未處理的連接的最大數目做出限制
接受連接
一旦服務器程序創建并命名套接字之后,他就可以通過accept系統調用來等待客戶建立對該套接字的連接
#include
int accept(int socket,struct sockaddr *address,size_t *address_len);
accept函數將創建一個新套接字來與該客戶進行通信,并且返回新套接字描述符(這個描述符和客戶端中描述符是一樣等同)
請求連接
客戶程序通過一個未命名套接字和服務器監聽套接字之間建立的連接的方法來連接到服務器,如下:
#include
int connect(int socket,const struct sockaddr *address,size_t address_len);
參數socket指定的套接字將連接到參數address指定的服務器套接字
關閉套接字
你可以通過調用close函數來終止服務器和客戶上的套接字連接
套接字通信
套接字可以通過調用read(),write()系統調用來進行傳輸數據
下面是套接字的一些例子
一個簡單的本地客戶
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd;
int len;
struct sockaddr_un address;//套接字地址
int result;
char ch = 'A';
sockfd = socket(AF_UNIX,SOCK_STREAM,0);//創建一個套接字(端點),并返回一個描述符
address.sun_family = AF_UNIX;//指明網絡介質
strcpy(address.sun_path,"server_socket");// 名字
len = sizeof(address);
result = connect(sockfd,(struct sockaddr *)&address,len);//請求連接到address
if(result == -1) {
perror("oops: clientl");
exit(1);
}
write(sockfd,&ch,1);//把數據寫入套接字
read(sockfd,&ch,1);//服務器處理后讀出處理后數據
printf("char form server = %c\n",ch);
close(sockfd);
exit(0);
}
下面是一個本地服務器
#include
#include
#include
#include
#include
#include
int main()
{
int server_sockfd,client_sockfd;//定義套接字描述符
int server_len,client_len;
struct sockaddr_un server_address;//套接字地址
struct sockaddr_un client_address;//套接字地址
unlink("server_socket");
server_sockfd = socket(AF_UNIX,SOCK_STREAM,0);//創建一個套接字,并返回一個描述符
server_address.sun_family = AF_UNIX;//指定網絡介質
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);//創建套接字隊列
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);//從套接字中讀取數據
ch++;// 處理數據
write(client_sockfd,&ch,1);//把數據重新寫回套接字
close(client_sockfd);
}
}
這兩個程序運行如下:
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
下面看一個網絡套接字的例子,先看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;//套接字地址結構體
struct sockaddr_in client_address;//套接字地址結構體
unlink("server_socket");
server_sockfd = socket(AF_INET,SOCK_STREAM,0);//創建一個套接字,并返回一個描述符
server_address.sin_family = AF_INET;//指定網絡介質
//server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
server_address.sin_addr.s_addr = htonl(INADDR_ANY);//客戶連接到服務器的IP
server_address.sin_port = htons(9734);//客戶連接到端口
server_len = sizeof(server_address);
bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接字
listen(server_sockfd,5);//創建套接字隊列
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);//從套接字中讀取數據
ch++;//處理數據
write(client_sockfd,&ch,1);//把處理后數據寫入套接字
close(client_sockfd);//關閉套接字
}
}
下面是客戶端程序:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd;//套接字描述符
int len;
struct sockaddr_in address;//套接字地址結構體
int result;
char ch = 'A';
sockfd = socket(AF_INET,SOCK_STREAM,0);//創建一個套接字并返回一個描述符
address.sin_family = AF_INET;// 指定網絡介質
address.sin_addr.s_addr = htonl(INADDR_ANY);//要連接到主機IP
address.sin_port = htons(9734);//要連接到端口號
len = sizeof(address);
result = connect(sockfd,(struct sockaddr *)&address,len);//請求連接
if(result == -1) {
perror("oops: clientl");
exit(1);
}
write(sockfd,&ch,1);//把數據寫入套接字
read(sockfd,&ch,1);//服務器處理完數據后從新讀取出來
printf("char form server = %c\n",ch);
close(sockfd);
exit(0);
}
運行這兩個程序輸出如下:
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#
輸出結果基本和上面的例子差不多,通過上面程序可以發現客戶端寫入到套接字中的數據可以從服務器中讀出來,而且客戶端會等待服務器把數據讀出處理后再把數據讀回來,這有一個順序,并不會出現亂序,有點類型于管道通信而且是雙向的
主機字節序和網絡字節序
通過套接字接口傳遞的端口號和地址都是二進制的,不同計算機使用不同的字節序來表示整數,如32位的整數分為4個連續的字節,并以1-2-3-4存在內存中,這里的1表示最高位,也即大端模式,而有的處理器是以4-3-2-1存取的,兩個不同的計算機得到的整數就會不一致
為了使不同類型的計算機可以就通過網絡傳輸多字節的值達成一致,客戶和服務器程序必須在傳出之前,將它們內部整數表示方式轉換為網絡字節序,它們通過下面函數轉換(也就是把端口號等轉換成統一網絡字節序)
#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位整數在主機字節序和標準的網絡字節序之間進行轉換,如上面例子中用到的
address.sin_addr.s_addr = htonl(INADDR_ANY);//要連接到主機IP
address.sin_port = htons(9734);//要連接到端口號
這樣就能保證網絡字節序的正確,如果你使用的計算機上的主機字節序和網絡字節序相同,將看不到任何差異
網絡信息
到目前為止,我們客戶和服務器程序一直是把地址和端口編譯到它們自己的內部,對于一個更通用的服務器和客戶程序來說,我們可以通過網絡信息函數來決定應該使用的地址和端口(也就是說可以通過網絡信息函數來獲取IP和端口號等信息)
類似的,如果給定一個計算機名字,你可以通過調用解析地址的主機數據庫函數來確定它的IP地址等信息
主機數據庫函數在接口文件netdb.h中聲明,如下:
#include
struct hostent *gethostbyaddr(const void *addr,size_t len,int type);
struct hostent *gethostbyname(const char *name);//獲得計算機主機數據庫信息
這些函數返回的結構體中至少包括以下幾個成員
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
};
如果沒有與我們查詢的主機或地址相關的數據項,這些信息函數將返回一個空指針
類型地,與服務及相關聯端口號有關的信息也可以通過一些服務信息函數來獲取
#include
struct servent *getservbyname(const char *name,const char *proto);//檢查是否有某個服務
struct servent *getservbyport(int port,const char *proto);
proto參數指定用于連接該服務的協議,它的兩個選項tcp和udp,前者用于SOCK_STREAM類型
返回結構體中至少包含如下幾個成員:
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"
};
如果想獲取某臺計算機主機數據庫信息,可以調用gethostbyname函數并且將結果打印出來,注意,要把返回的地址表轉換為正確的地址類型,并用函數inet_ntoa將它們從網絡字節序裝換為可打印的字符串,如下:
#include
char *inet_ntoa(struct in_addr in);
這個函數的作用是將一個因特網主機地址轉換為一個點分四元租格式的字符串
下面這個程序getname.c用來獲取一臺主機有關的信息,如下:
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])
{
char *host,**names,**addrs;//接收用到的一些指針
struct hostent *hostinfo;//指向gethostbyname函數返回的結構體指針
if(argc == 1) {
char myname[256];
gethostname(myname,255);
host = myname;
}
else
host = argv[1];//獲取主機名
hostinfo = gethostbyname(host);//獲取主機數據庫
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);
}
//顯示主機的所有IP地址
addrs = hostinfo -> h_addr_list;
while(*addrs) {
printf(" %s",inet_ntoa(*(struct in_addr *)*addrs));
addrs++;
}
printf("\n");
exit(0);
}
運行這個程序輸出如下所示:
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#
下面一個例子是連接到標準服務
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[])//argc表示參數個數,argv[]表示具體參數程序名為argv[0]
{
char *host;
int sockfd;
int len,result;
struct sockaddr_in address;//套接字地址結構體
struct hostent *hostinfo;//指向gethostbyname函數返回的結構體指針
struct servent *servinfo;//指向getservbyname函數返回的結構體指針
char buffer[128];
if(argc == 1)
host = "localhost";
else
host = argv[1];
hostinfo = gethostbyname(host);//獲取主機數據庫信息
if(!hostinfo) {
fprintf(stderr,"no host: %s\n",host);
exit(1);
}
//檢查主機上是否有daytime服務
servinfo = getservbyname("daytime","tcp");
if(!servinfo) {
fprintf(stderr,"no daytime service\n");
exit(1);
}
sockfd = socket(AF_INET,SOCK_STREAM,0);//創建一個套接字,并返回一個描述符
address.sin_family = AF_INET;//使用網絡介質
address.sin_port = servinfo -> s_port;//端口號
address.sin_addr = *(struct in_addr *)*hostinfo -> h_addr_list;//獲取主機IP地址
len = sizeof(address);
result = connect(sockfd,(struct sockaddr *)&address,len);//請求連接
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);
}
運行這個程序輸出如下:
root@www:/opt/chengxu# ./getname1 localhost
oops:getdate: Connection refused
root@www:/opt/chengxu#
之所以這樣是因為我的虛擬機中沒有啟動daytime這個服務
我用的是ubuntu虛擬機,下面來啟動這個daytime服務看一下
首先
root@www:/opt/chengxu# vim /etc/inetd.conf
進入到inetd的配置文件把這個服務前面的#號去掉,改成如下:
18 #discard dgram udp wait root internal
19 daytime stream tcp nowait root internal
20 #time stream tcp nowait root internal
然后保存退出
接下來重啟一下服務就可以了通過下面命令啟動(或重啟)xinetd服務(xinetd和openbsd-inetd差不多都是屬于守護進程)
root@www:/opt/chengxu# /etc/init.d/openbsd-inetd restart
* Restarting internet superserver inetd [ OK ]
root@www:/opt/chengxu#
可以通過下面命令看一下daytime這個服務是否處于監聽狀態了,如下:
root@www:/opt/chengxu# netstat -a | grep daytime
tcp 0 0 *:daytime *:* LISTEN
root@www:/opt/chengxu#
下面再重新運行一下上面程序看看:
root@www:/opt/chengxu# ./getname1 localhost
read 26 bytes: Sun Sep 23 23:15:14 2012
root@www:/opt/chengxu#
因特網守護進程
當有客戶端連接到某個服務時,守護進程就運行相應的服務器,這使得針對各項網絡服務器不需要移植運行著,我們通常是通過一個圖形界面來配置xinetd,ubuntu用的好像是openbsd-inetd這 個守護進程,我們可以直接修改它的配置文件,ubuntu對應的是/etc/inetd.conf這個文件,就拿我們上面那個daytime這個服務為 例,在ubuntu中它默認是關閉的,這時我們要進入它的配置文件里,把它前面的#字符去掉就可以了,修改了的配置文件如下:
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
之后重啟一下守護進程就可以了,如下:
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#
多客戶
到目前為止,一直都是介紹如何用套接字來實現本地的和跨網絡的客戶/服務器系統,一旦連接建立,套接字連接的行為就類似于打開底層文件描述符,而且在很多方面類似雙向管道,現在我們來考慮多個用戶同時連接一個服務器的情況,下面是一個例子:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int server_sockfd,client_sockfd;//定義套接字描述符
int server_len,client_len;
struct sockaddr_in server_address;//套接字地址結構體
struct sockaddr_in client_address;//套接字地址結構體
server_sockfd = socket(AF_INET,SOCK_STREAM,0);//創建一個套接字,并返回一個描述符
server_address.sin_family = AF_INET;//網絡介質
server_address.sin_addr.s_addr = htonl(INADDR_ANY);//要連接到的服務器IP地址
server_address.sin_port = htons(9734);//要連接到的端口號
server_len = sizeof(server_address);
bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接字
listen(server_sockfd,5);//創建套接字隊列
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);//接收連接
if(fork() == 0) {//創建一個子進程用于傳輸數據
read(client_sockfd,&ch,1);
sleep(5);
ch++;
write(client_sockfd,&ch,1);
close(client_sockfd);
exit(0);
}
else {
close(client_sockfd);//在父進程中關閉打開的套接字描述符
}
}
}
下面是這個程序的運行情況
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#
上面服務器程序用到了子進程來處理要處理數據,這樣一來就可以多個客戶連接到服務器了
select系統調用
select系統調用允許程序同時在多個底層文件描述符上等待用戶輸入(或完成輸出),檢測讀,寫,異常文件描述符集,select函數對數據結構fd_set進行操作(也就是說對文件描述符集進行操作),有一組定義好的宏可以用來控制這些文件描述符集,如下:
#include
#include
void FD_ZERO(fd_set *fdset);//初始化(清零)文件描述符集
void FD_CLR(int fd,fd_set *fdset);//清除文件描述符集
void FD_SET(int fd,fd_set *fdset);//把文件描述符fd加到文件描述符集中
int FD_ISSET(int fd,fd_set *fdset);//檢測文件描述符變化情況
select函數還可以用一個超時值來防止無限期阻塞,這個超時值由一個timeval結構給出,如下:
struct timeval {
time_t tv_sec;
long tv_usec;
}
select系統調用的原型如下所示:
#include
#include
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
select系統調用用于測試文件描述符集合中,是否有一個文件描述符已經處于可讀狀態或可寫狀態或錯誤狀態,它將阻塞等待某個文件描述符進入上述狀態。
select函數會在發生如下情況時返回:readfds集合中描述符可讀(也就是寫入數據了,寫完數據后會發送一個可讀信號),writefds集合中 有描述符可寫或errorfds集合中有描述符錯誤,如果這三種情況都沒有發生,select函數將在timrout指定的超時后返回,這時所有文件描述 符集合將被清空
下面是一個select系統調用的例子:
#include
#include
#include
#include
#include
#include
#include
int main()
{
char buffer[128];
int result,nread;
fd_set inputs,testfds;//定義文件描述符集合
struct timeval timeout;//定義超時結構體
FD_ZERO(&inputs);//清零文件描述符集合
FD_SET(0,&inputs);//把標準輸入加到文件描述符集合中
while(1) {
testfds = inputs;
timeout.tv_sec = 2;
timeout.tv_usec = 500000;
result = select(FD_SETSIZE,&testfds,(fd_set *)NULL,(fd_set *)NULL,&timeout);//檢測是否有標準輸入,沒有的話每隔2.5秒打印一次timeout
switch(result) {
case 0:
printf("timeout\n");
break;
case -1:
perror("select");
exit(1);
default:
if(FD_ISSET(0,&testfds)) {//檢測是否有標準輸入
ioctl(0,FIONREAD,&nread);//得到緩沖區里有多少字節要被讀取,存到nread中
if(nread == 0) {
printf("keyboard done\n");
exit(0);
}
nread = read(0,buffer,nread);//從標準輸入緩沖區中讀取數據
buffer[nread] = 0;
printf("read %d from keyboard: %s",nread,buffer);
}
break;
}
}
}
下面是這個程序運行時情況
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#
最后來看一個改進的多客戶/服務器程序:
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int server_sockfd,client_sockfd;//定義套接字描述符
int server_len,client_len;
struct sockaddr_in server_address;//套接字地址結構體
struct sockaddr_in client_address;//套接字地址結構體
int result;
fd_set readfds,testfds;//定義文件描述符集合
server_sockfd = socket(AF_INET,SOCK_STREAM,0);//創建一個套接字,并返回一個描述符
server_address.sin_family = AF_INET;//網絡介質
server_address.sin_addr.s_addr = htonl(INADDR_ANY);//要連接到服務器的Ip地址
server_address.sin_port = htons(9734);//要連接到的端口號
server_len = sizeof(server_address);
bind(server_sockfd,(struct sockaddr *)&server_address,server_len);//命名套接字
listen(server_sockfd,5);// 創建套接字隊列
FD_ZERO(&readfds);//清零描述符
FD_SET(server_sockfd,&readfds);//把server_sockfd描述符加到readfds描述符集合中
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);//檢測testfds描述符集合是否有變化,沒有的話阻塞等待
if(result < 1) {
perror("server5");
exit(1);
}
for(fd = 0;fd < FD_SETSIZE;fd++) {//逐個檢查描述符
if(FD_ISSET(fd,&testfds)) {//檢測那個描述符集合發生變化,這個很重要
if(fd == server_sockfd) {//如果是server_sockfd描述符集變化
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address,&client_len);//接收連接
FD_SET(client_sockfd,&readfds);//把客戶端的client_sockfd描述符加進描述符集合中,這樣就可以用select來檢測客戶端的描述符了
printf("adding client on fd %d\n",client_sockfd);
}
else {
ioctl(fd,FIONREAD,&nread);//能得到緩沖區里有多少字節要被讀取,存進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);
}
}
}
}
}
}
這個程序運行如下:
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#
....