linux socket怎么實現多個客戶端連接服務器端

一、引言

?? ?在實際情況中,人們往往遇到多個客戶端連接服務器端的情況。由于之前介紹的函數如connect,recv,send等都是阻塞性函數,若資源沒有充分準備好,則調用該函數的進程將進入睡眠狀態,這樣就無法處理I/O多路復用的情況了。

?? ?本文給出兩種I/O多路復用的方法:fcntl(),select()。可以看到,由于linux中把socket當作一種特殊的文件描述符,這給用戶的處理帶來很大方便。

二、fcntl

fcntl()函數有如下特性:

1)非阻塞I/O: 可將cmd 設為F_SETFL,將lock設為O_NONBLOCK

2)信號驅動I/O:可將cmd設為F_SETFL,將lock設為O_ASYNC.

例程:

#include <sys/types.h> #include <sys/socket.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/un.h> #include <sys/time.h> #include <sys/ioctl.h> #include <unistd.h> #include <netinet/in.h> #include <fcntl.h> #include <unistd.h>  #define SERVPORT 3333 #define BACKLOG 10 #define MAX_CONNECTED_NO 10 #define MAXDATASIZE 100  int main() {   struct sockaddr_in server_sockaddr,client_sockaddr;   int sin_size,recvbytes,flags;   int sockfd,client_fd;   char buf[MAXDATASIZE]; /*創建socket*/   if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){     perror("socket");     exit(1);   }   printf("socket success!,sockfd=%dn",sockfd);  /*設置sockaddr結構*/   server_sockaddr.sin_family=AF_INET;   server_sockaddr.sin_port=htons(SERVPORT);   server_sockaddr.sin_addr.s_addr=INADDR_ANY;   bzero(&(server_sockaddr.sin_zero),8);  /*將本地ip地址綁定端口號*/   if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))==-1){     perror("bind");     exit(1);   }   printf("bind success!n");  /*監聽*/   if(listen(sockfd,BACKLOG)==-1){     perror("listen");     exit(1);   }   printf("listening....n");  /*fcntl()函數,處理多路復用I/O*/   if((flags=fcntl( sockfd, F_SETFL, 0))<0)       perror("fcntl F_SETFL");     flags |= O_NONBLOCK;     if(fcntl( sockfd, F_SETFL,flags)<0)       perror("fcntl");   while(1){     sin_size=sizeof(struct sockaddr_in);     if((client_fd=accept(sockfd,(struct sockaddr*)&client_sockaddr,&sin_size))==-1){  //服務器接受客戶端的請求,返回一個新的文件描述符       perror("accept");       exit(1);     }     if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){       perror("recv");       exit(1);     }     if(read(client_fd,buf,MAXDATASIZE)<0){       perror("read");       exit(1);     }     printf("received a connection :%s",buf);  /*關閉連接*/   close(client_fd);   exit(1);   }/*while*/ }

運行該程序:

[root@localhost net]# ./fcntl socket success!,sockfd=3 bind success! listening.... accept: Resource temporarily unavailable

可以看到,當accept的資源不可用時,程序會自動返回。

若將紅色加粗代碼替換為:

if((flags=fcntl( sockfd, F_SETFL, 0))<0)       perror("fcntl F_SETFL");     flags |= O_ASYNC;     if(fcntl( sockfd, F_SETFL,flags)<0)       perror("fcntl");

運行結果如下:

[root@localhost net]# ./fcntl1 socket success!,sockfd = 3 bind success! listening...

可以看到,進程一直處于等待中,直到另一相關信號驅動它為止。

三、select

#include <sys/types.h> #include <sys/socket.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/un.h> #include <sys/time.h> #include <sys/ioctl.h> #include <unistd.h> #include <netinet/in.h> #define SERVPORT 3333 #define BACKLOG 10 #define MAX_CONNECTED_NO 10 #define MAXDATASIZE 100 int main() {   struct sockaddr_in server_sockaddr,client_sockaddr;   int sin_size,recvbytes;   fd_set readfd;   fd_set writefd;   int sockfd,client_fd;   char buf[MAXDATASIZE]; /*創建socket*/   if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){     perror("socket");     exit(1);   }   printf("socket success!,sockfd=%dn",sockfd); /*設置sockaddr結構*/   server_sockaddr.sin_family=AF_INET;   server_sockaddr.sin_port=htons(SERVPORT);   server_sockaddr.sin_addr.s_addr=INADDR_ANY;   bzero(&(server_sockaddr.sin_zero),8); /*將本地ip地址綁定端口號*/   if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))==-1){     perror("bind");     exit(1);   }   printf("bind success!n"); /*監聽*/   if(listen(sockfd,BACKLOG)==-1){     perror("listen");     exit(1);   }   printf("listening....n"); /*select*/   FD_ZERO(&readfd);              // 將readfd 清空  FD_SET(sockfd,&readfd);         //將sockfd加入到readfd集合中   while(1){   sin_size=sizeof(struct sockaddr_in);   if(select(MAX_CONNECTED_NO,&readfd,NULL,NULL,(struct timeval(FD_ISSET(sockfd,&readfd)>0){         // FD_ISSET 這個宏判斷 sockfd 是否屬于可讀的文件描述符。從 sockfd 中讀入, 輸出到標準輸出上去.       if((client_fd=accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size))==-1){   //client_sockaddr:客戶端地址         perror("accept");         exit(1);       }       if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))==-1){         perror("recv");         exit(1);       }       if(read(client_fd,buf,MAXDATASIZE)<0){         perror("read");         exit(1);       }       printf("received a connection :%s",buf);     }/*if*/     close(client_fd);     }/*select*/   }/*while*/ } 運行結果如下: [root@localhost net]#  gcc select1.c -o select1 [root@localhost net]# ./select1 socket create success! bind success! listening...

? 版權聲明
THE END
喜歡就支持一下吧
點贊9 分享