篇一:TCP套接字編程(一)
篇二:TCP套接字編程(二)
前面通過多進程,多線程,支持處理多個客戶端通信,本篇將講述用select這個IO複用來實現。
IO模型
首先連接一下IO模型,unix下有5種可用IO模型,分別是:阻塞式IO、非阻塞式IO、IO複用、信號驅動式IO。
1、阻塞式IO
阻塞式IO是最常用的IO模式,它就是沒有數據就阻塞,一直等到有數據位置,期間啥也不幹,就一門心思等數據。
2、非阻塞式IO
非阻塞式IO,就是不斷輪詢,問“數據有沒有準備好啊”,“數據有沒有準備好啊”….。數據準備好了,返回數據。不斷詢問,會耗費大量CPU時間。
3、IO複用模型
slect和pool就是IO複用。它是阻塞在系統調用,而不是阻塞在真正的IO系統調用上。學會轉移矛盾,避免呢傷害了,自己很忙,不能阻塞自己,讓別人幫忙等待(系統調用)。
4、信號驅動式IO模型
信號是個好東西,可以讓內核在描述符就緒了,用信號(SIGIO)通知自己呀,這樣不就工作學習兩不誤了。
5、異步IO模型
信號驅動IO是由內核通知我們何時可以啓動一個IO操作,這個不通知我們何時啓動,而是直接替我們工作,並由內核告訴我們何時可以完成。一個是何時開始,一個是何時完成。
select函數
slelect函數原型爲:
int select(int maxfdp1,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
參數含義:
- maxfdp1:指定待預測的描述符個數,它的值是待測試的最大描述符加1,描述符0,1,2,… ,maxfdp1-1;
- readfds,writefds,exceptfds:指定我們要讓內核測試讀、寫和異常條件的描述符;
- timeout:告知內核等待就緒描述符可花多長時間(不等,等一段時間,永久等待)。
描述符操作:
void FD_ZERO(fd_set *fdset); //initialize the set:all bits off
void FD_SET(int fd,fd_set *fdset); //turn on the bit for fd in dfset
void FD_CLR(int fd,fd_set *fdset); //turn off the bit for fd in dfset
int FD_ISSET(int fd,fd_set *fdset); // is the bit for fd on in fdset
瞭解了select的使用,下面就來用select實現echo服務器。
server select版
/**
Handle multiple socket connections with select and fd_set on Linux
*/
#include <stdio.h>
#include <string.h> //strlen
#include <stdlib.h>
#include <errno.h>
#include <unistd.h> //close
#include <arpa/inet.h> //close
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/select.h> //FD_SET, FD_ISSET, FD_ZERO macros
#define TRUE 1
#define FALSE 0
#define PORT 8888
#define BUF_SIZE 1205
#define MAX_ENQ 1024
int main(int argc , char *argv[])
{
int opt = TRUE;
int listenfd ,connfd,max_fd,sockfd, activity,i ,valread ;
int client[FD_SETSIZE],max_client_index;
socklen_t socklen=sizeof(struct sockaddr_in);
char buffer[BUF_SIZE]; //data buffer of 1K
struct sockaddr_in servaddr,cliaddr;
//set of socket descriptors
fd_set rset,allset;
//initialise all client_socket[] to 0 so not checked
for (i = 0; i < FD_SETSIZE; i++)
{
client[i] = -1;
}
max_client_index=-1;
//create a master socket
if( (listenfd = socket(AF_INET , SOCK_STREAM , 0)) <0)
{
perror("socket failed");
exit(EXIT_FAILURE);
}
//set master socket to allow multiple connections , this is just a good habit, it will work without this
if( setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 )
{
perror("setsockopt");
exit(EXIT_FAILURE);
}
//type of socket created
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons( PORT );
//bind the socket to localhost port 8888
if (bind(listenfd, (struct sockaddr *)&servaddr, socklen)<0)
{
perror("bind failed");
exit(EXIT_FAILURE);
}
//try to specify maximum of 3 pending connections for the master socket
if (listen(listenfd, MAX_ENQ) < 0)
{
perror("listen");
exit(EXIT_FAILURE);
}
//accept the incoming connection
printf("echo server use select startup, listen on port %d\n", PORT);
printf("max connection: %d\n", FD_SETSIZE);
puts("Waiting for connections ...");
//clear the socket set
FD_ZERO(&allset);
//add master socket to set
FD_SET(listenfd, &allset);
max_fd = listenfd;
while(TRUE)
{
rset=allset;
//wait for an activity on one of the sockets , timeout is NULL , so wait indefinitely
activity = select( max_fd + 1 , &rset , NULL , NULL , NULL);
if ((activity < 0) && (errno!=EINTR))
{
printf("select error");
}
//If something happened on the master socket , then its an incoming connection
if (FD_ISSET(listenfd, &rset))
{
if ((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &socklen))<0)
{
perror("accept");
exit(EXIT_FAILURE);
}
//inform user of socket number - used in send and receive commands
printf("New connection , socket fd is %d , ip is : %s , port : %d \n" , connfd , inet_ntoa(cliaddr.sin_addr) , ntohs(cliaddr.sin_port));
//send new connection greeting message
//a message
char *message = "good ,connect successfully \r\n";
if( send(connfd, message, strlen(message), 0) != strlen(message) )
{
perror("send");
}
//add new socket to array of sockets
for (i = 0; i < FD_SETSIZE; i++)
{
if( client[i] ==-1)
{
client[i] = connfd;
break;
}
}
if (i == FD_SETSIZE)
{
fprintf(stderr, "too many connection, more than %d\n", FD_SETSIZE);
close(connfd);
continue;
}
if (connfd>max_fd)
max_fd=connfd;
if(i>max_client_index)
max_client_index=i;
FD_SET(connfd, &allset); //一定不能丟,一定不能丟
if(--activity<=0)
continue;
}
//else its some IO operation on some other socket :)
for (i = 0; i <= max_client_index; i++)
{
if((sockfd = client[i])!=-1)
{
if (FD_ISSET( sockfd , &rset))
{
//Check if it was for closing , and also read the incoming message
if ((valread = read( sockfd , buffer, BUF_SIZE)) == 0)
{
//Somebody disconnected , get his details and print
getpeername(sockfd , (struct sockaddr*)&cliaddr , &socklen);
printf("Host disconnected , ip %s , port %d \n" , inet_ntoa(cliaddr.sin_addr) , ntohs(cliaddr.sin_port));
//Close the socket and mark as 0 in list for reuse
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
continue;
}
else if (valread<0)
{
perror("read error");
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
continue;
}
//Echo back the message that came in
else
{
printf("get message form client %d,meeasge is %s\n" , i,buffer);
//set the string terminating NULL byte on the end of the data read
buffer[valread] = '\0';
send(sockfd , buffer , strlen(buffer) , 0 );
}
if(--activity<=0)
break;
}
}
}
}
return 0;
}
真是不容易,花了一個早上才調試通。測試結果如下:
服務器:
客戶端1:
客戶端2:
客戶端3:
select學習就到這裏了,接下來試着用pool和epool實現服務器,加油!!!!!