select函數定義
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值:就緒描述符的數目,超時返回0,出錯返回-1
參數介紹:
(1)第一個參數maxfdp1指定待測試的描述字個數,它的值是待測試的最大描述字加1(因此把該參數命名爲maxfdp1),描述字0、1、2...maxfdp1-1均將被測試,因爲文件描述符是從0開始的。
(2)中間的三個參數readset、writeset和exceptset指定我們要讓內核測試讀、寫和異常條件的描述字。如果對某一個的條件不感興趣,就可以把它設爲空指針。struct fd_set可以理解爲一個集合,這個集合中存放的是文件描述符,可通過以下四個宏進行設置:
void FD_ZERO(fd_set *fdset); //清空集合
void FD_SET(int fd, fd_set *fdset); //將一個給定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset); //將一個給定的文件描述符從集合中刪除
int FD_ISSET(int fd, fd_set *fdset); // 檢查集合中指定的文件描述符是否可以讀寫
(3)timeout告知內核等待所指定描述字中的任何一個就緒可花多少時間。其timeval結構用於指定這段時間的秒數和微秒數。
struct timeval
long tv_sec; //seconds
long tv_usec; //microseconds
};
這個參數有三種可能:
(1)永遠等待下去:僅在有一個描述字準備好I/O時才返回。爲此,把該參數設置爲空指針NULL。
(2)等待一段固定時間:在有一個描述字準備好I/O時返回,但是不超過由該參數所指向的timeval結構中指定的秒數和微秒數。
(3)根本不等待:檢查描述字後立即返回,這稱爲輪詢。爲此,該參數必須指向一個timeval結構,而且其中的定時器值必須爲0
select的缺點:
(1)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大
(2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
(3)select支持的文件描述符數量太小了,默認是1024
select服務端程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#define LISTENQ 1024
#define SERVPORT 7885
#define MAXLINE 4096
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr, cliaddr;
int listenfd, connfd;
fd_set allset, rset;
int result;
socklen_t client_len;
ssize_t n; //read()讀取的大小
char buf[MAXLINE];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == -1)
{
perror("create socket error");
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERVPORT);
inet_pton(AF_INET, "INADDR_ANY", &servaddr.sin_addr);
// 設置套接字選項避免地址使用錯誤
int on =1;
if((setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)
{
perror("setsockopt failed");
exit(EXIT_FAILURE);
}
if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
{
perror("bind the socket");
return -1;
}
if(listen(listenfd, LISTENQ) == -1)
{
perror("listen the socket");
return -1;
}
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
while(1)
{
int fd;
int nread;
rset = allset;
FD_ZERO(readfds);
result = select(FD_SETSIZE, &rset, NULL, NULL, NULL);
if(result < 1)
{
perror("server 5");
exit(1);
}
//掃描所有文件描述符
for(fd=0; fd<FD_SETSIZE; fd++)
{
if (FD_ISSET(fd, &rset))
{
if(fd == listenfd)//如果此描述符是監聽描述符,則處理客戶連接請求
{
client_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &client_len);
if(connfd == -1)
{
perror("accept error");
return -1;
}
FD_SET(connfd, &allset);
printf("add client on fd %d\n", connfd);
}
else //客戶socket有數據請求時
{
ioctl(fd, FIONREAD, &nread);//取得數據量交給nread
if(nread == 0)
{
close(fd);
FD_CLR(fd, &allset);
printf("remove client on fd %d\n", fd);
}
else
{
memset(buf, 0, sizeof(buf));
n = read(fd, buf, MAXLINE);
printf("serving client on fd %d n=%d buf=%s\n", fd,n,buf);
write(fd, buf, n);
}
}
}
}
}
}
客戶端測試程序:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define MAXLINE 4096
#define SERVPORT 7885
int main(int argc, char *argv[])
{
int client_sockfd;
int len;
struct sockaddr_in address;//服務器端網絡地址結構體
int result;
char sendline[MAXLINE],recvline[MAXLINE];
if(argc != 2)
{
printf("usage:tcpcli <IPaddress>\n");
return 0;
}
client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客戶端socket
address.sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &address.sin_addr);
address.sin_port = htons(SERVPORT);
len = sizeof(address);
result = connect(client_sockfd, (struct sockaddr *)&address, len);
while(1)
{
if(result == -1)
{
perror("oops: client2");
exit(1);
}
if(fgets(sendline, MAXLINE, stdin) == NULL)
{
return -1;
}
printf("sendline is %s\n", sendline);
write(client_sockfd, sendline, strlen(sendline));
//每次接收前清空recvline
memset(recvline, 0, sizeof(recvline));
read(client_sockfd, recvline, strlen(sendline));
printf("char from server = %s\n", recvline);
}
close(client_sockfd);
return 0;
}