套接字

套接字是一種通訊機制,憑藉這種機制,客戶/服務器系統的開發工做既能夠在本地單機上進行,也能夠跨網絡進行,Linux所提供的功能(如打印服 務,ftp等)一般都是經過套接字來進行通訊的,套接字的建立和使用與管道是有區別的,由於套接字明確地將客戶和服務器區分出來,套接字能夠實現將多個客 戶鏈接到一個服務器。數據庫

 

套接字屬性ubuntu

套接字的特性由3個屬性肯定,他們是,域,類型和協議vim

域指定套接字通訊中使用的網絡介質,最多見的套接字域是AF_INET,它指的是Internet網絡服務器

套接字類型網絡

一個套接字可能有多種不一樣的通訊方式數據結構

流套接字,流套接字提供一個有序,可靠,雙向節流的連接,流套接字由類型SOCK_STREAM指定,它是在AF_INET域中經過TCP/IP連接實現的,這就是套接字類型(其實就是通訊方式)dom

與流套接字相反,由類型SOCK_DGRAM指定的數據報套接字不創建和維持一個鏈接,它對能夠發送的數據長度有限制,數據報做爲一個單獨的網絡消息被傳輸,它可能會丟失,複製或亂序socket

最後一個是套接字協議,一般使用默認就能夠了(也就是最後一個參數填0)tcp

 

建立套接字ide

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 <netdb.h>

 

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 <netdb.h>

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 <arpa/inet.h>

char *inet_ntoa(struct in_addr in);

這個函數的做用是將一個因特網主機地址轉換爲一個點分四元租格式的字符串

下面這個程序getname.c用來獲取一臺主機有關的信息,以下:

#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>

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 <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

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 <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
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 <sys/types.h>
#include <sys/time.h>
 
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 <sys/types.h>
#include <sys/time.h>
 
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 <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
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 <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
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#
....
相關文章
相關標籤/搜索