select多路複用實現多客戶端連接服務器
.>應用程序中同時需要處理多路輸入輸出流時,若採用阻塞模式,將得不到預期的目的;
.>若採用非阻塞模式,對多個輸入進行輪訓有太耗費時間;
.>若設置多個進程分別處理一天數據通路,將產生新的進程同步通信問題,使程序更復雜;
比較好的方法就是採用多路複用,其基本思想就是:
》》》先創建一張有關描述符的的表,然後調用一個函數,當這些文件描述符中的一個或者多個已經準備好進行IO操作時函數才返回;
》》》函數返回時告訴進程哪個描述符已就緒,可以進行IO操作;
函數原型:
int select(int nfds,fd_set *readset,fd_set *writeset,fd_set* exceptset,struct timeval *timeout);
nfds: 第一個參數是:最大的文件描述符值+1;
readset: 可讀描述符集合;
writeset: 可寫描述符集合;
exceptset: 異常描述符;
timeout:select 的監聽時長,如果這短時間內所監聽的 socket 沒有事件發生。
設置文件描述符的幾個宏:
FD_ZERO(fd_set *) 從fd_set中清除所有文件描述符;
FD_SET(int fd, fd_set *) 將fd添加到fd_set中;
FD_CLR(int fd, fd_set *) 將fd從fd_set中清除;
FD_ISSET(int fd, fd_set *) 判斷fd是否在fd_set中;
通俗點解釋,就是將需要監聽的文件描述符添加到fd_set這個文件描述符集合中,然後用宏函數FD_ISSET()判斷該文件描述符是否存在,因爲進行循環後只有有需要處理的文件描述符纔會留在這個文件描述符集合中,否則就會被清除,在對相應描述符進行操作;
//tcp 多路複用服務器端
#include
#include
#include
#include
#include
#define SIZE 32
#define IP "127.0.0.1"
#define PORT 54321
#define LISTENNUM 5
enum SOCK_OP
{
ERROR = -1,
SUCCESS = 0,
};
int main()
{
int socketID = 0;
int maxFd = 0;
int ret = 0;
int newID = 0;
fd_set readFds;
int addrLength = 0;
struct sockaddr_in addr;
char buf[SIZE] = {0};
//init socket
socketID = initSocket();
if (0 > socketID)
{
printf("socket init error\r\n");
return ERROR;
}
printf("init socket success\r\n");
addrLength = sizeof(addr);
//多路複用
maxFd = socketID;
FD_ZERO(&readFds);
FD_SET(socketID, &readFds);
while(1)
{
//設定:把所有要監聽的描述符都放到監聽集合中
fd_set tmp = readFds;
ret = select(maxFd + 1, &tmp, NULL, NULL, NULL);
if (0 > ret)
{
perror("select error");
return ERROR;
}
else if (0 == ret)
{
printf("select time out\r\n");
}
else
{
//判斷哪一個描述符可讀
int i = 0;
for (i = 0; i <= maxFd; i++)
{
if (FD_ISSET(i, &tmp))
{
if (i == socketID)
{
memset(&addr, 0, addrLength);
newID = accept(socketID, (struct sockaddr *)&addr, &addrLength);
if (0 > newID)
{
perror("accept error");
return ERROR;
}
printf("client %d connected, IP=%s,port=%u\r\n", newID, (char *)inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
//把newID加入到監聽描述符集合中
FD_SET(newID, &readFds);
if (newID > maxFd)
{
maxFd = newID;
}
continue;
}//if (i == socketID)
//判斷newID是否可讀
memset(buf, 0, SIZE);
ret = recv(i, buf, SIZE - 1, 0);
if (0 > ret)
{
perror("recv error");
close(i);
FD_CLR(i, &readFds);
printf("client %d closed\r\n", i);
}
else if (0 == ret)
{
close(i);
FD_CLR(i, &readFds);
printf("client %d closed\r\n", i);
}
else
printf("client %d said:%s\r\n", i, buf);
}//if (FD_ISSET(i, &tmp))
}//for(i = 0; i <= maxFd; i++)
}//else
}//while(1)
close(socketID);
return 0;
}
int initSocket()
{
int socketID = 0;
int addrLength = 0;
int flag = 1;
struct sockaddr_in addr;
//創建socket
socketID = socket(AF_INET, SOCK_STREAM, 0);
if (socketID < 0)
{
perror("socket error");
return ERROR;
}
//設置地址可以重複綁定
if( setsockopt(socketID, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)) == -1)
{
perror("setsockopt");
return ERROR;
}
//bind
addrLength = sizeof (addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = INADDR_ANY;
if (0 > bind(socketID, (struct sockaddr *)&addr, addrLength))
{
perror("bind error");
return ERROR;
}
//listen
if (0 > listen(socketID, LISTENNUM))
{
perror("listen error");
return ERROR;
}
return socketID;
}
/*tcp 通信, 客戶端*/
#include
#include
#include
#include
#include
#define SIZE 32
#define IP "127.0.0.1"
#define PORT 54321
#define LISTENNUM 5
enum SOCK_OP
{
ERROR = -1,
SUCCESS = 0,
};
int main()
{
//定義變量
int socketID = 0;
int addrLength = 0;
int ret = 0;
struct sockaddr_in addr;
char buf[SIZE] = {0};
//創建套接字
socketID = socket(AF_INET, SOCK_STREAM, 0);
if (socketID < 0)
{
perror("socket error");
return -1;
}
//設定對方的IP/PORT
addrLength = sizeof(addr);
memset(&addr, 0, addrLength);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr(IP);
//發送連接請求
ret = connect(socketID, (const struct sockaddr *)(&addr), addrLength);
if (ret < 0)
{
perror("connect error");
close(socketID);
return -1;
}
printf("connect success\r\n");
//通信(發,收)
while(1)
{
fgets(buf, SIZE - 1, stdin);
ret = send(socketID, buf, strlen(buf), 0);
if (ret < 0)
{
perror("send error");
close(socketID);
return -1;
}
printf("send success %s,%d\r\n", buf, ret);
if (strncmp(buf, "quit", 4) == 0)
{
break;
}
}
//關閉套接字
close(socketID);
return 0;
}
程序可實現多客戶端與服務器端進行連接,而服務器都可及時響應客戶端,不會出現服務器
阻塞;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.