linux 非阻塞通訊程式 select
linux 的socket函式分為阻塞和非阻塞兩種方式,比如accept函式,在阻塞模式下,它會一直等待有客戶連線。而在非阻塞情況下,會立刻返回。我們一般都 希望程式能夠執行在非阻塞模式下。一種方法就是做一個死迴圈,不斷去查詢各個socket的狀態,但是這樣會浪費大量的cpu時間。解決這個問題的一個方 法就是使用select函式。使用select函式可以以非阻塞的方式和多個socket通訊。當有socket需要處理時,select函式立刻返回, 期間並不會佔用cpu時間。
例程分析:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MYPORT 1234 // 偵聽埠
#define BACKLOG 5 // 最大可連線客戶端數量
#define BUF_SIZE 200
int fd_A[BACKLOG]; // 連線的FD陣列
int conn_amount; // 當前連線的數量
void showclient()
{
int i;
printf("client amount: %d/n", conn_amount);
for (i = 0; i < BACKLOG; i++)
{
printf("[%d]:%d ", i, fd_A[i]);
}
printf("/n/n");
}
int main(void)
{
int sock_fd, new_fd; // 偵聽sock_fd, 新連線new_fd
struct sockaddr_in server_addr; // server address information
struct sockaddr_in client_addr; // connector's address information
socklen_t sin_size;
int yes = 1;
char buf[BUF_SIZE];
int ret;
int i;
//建立偵聽Socket
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Create listening socket error!");
exit(1);
}
//配置偵聽Socket
//SO_REUSEADDR BOOL 允許套介面和一個已在使用中的地址捆綁。
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
{
perror("setsockopt error!");
exit(1);
}
server_addr.sin_family = AF_INET; // host byte order
server_addr.sin_port = htons(MYPORT); // short, network byte order
server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
memset(server_addr.sin_zero, '/0', sizeof(server_addr.sin_zero));
//繫結新建立的Socket到指定的IP和埠
if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
perror("bind error!");
exit(1);
}
//開始偵聽,最大連線數為BACKLOG
if (listen(sock_fd, BACKLOG) == -1)
{
perror("listen error!");
exit(1);
}
printf("listen port %d/n", MYPORT);
//監控檔案描述符集合
fd_set fdsr;
//監控檔案描述符集合中最大的檔案號
int maxsock;
//Select超時返回的時間。
struct tim tv;
conn_amount = 0;
sin_size = sizeof(client_addr);
maxsock = sock_fd;
while (1)
{
// 初始化檔案描述符集合 initialize file descriptor set
FD_ZERO(&fdsr);
// 把Sock_fd加入到檔案描述符集合
FD_SET(sock_fd, &fdsr);
// 超時設定30秒
tv.tv_sec = 30;
tv.tv_usec = 0;
// 把活動的socket的控制代碼加入到檔案描述符集合中
for (i = 0; i < BACKLOG; i++)
{
if (fd_A[i] != 0)
{
FD_SET(fd_A[i], &fdsr);
}
}
//Select 函式原型
//int select(nfds, readfds, writefds, exceptfds, timeout)
//nfds: select監視的檔案控制代碼數,視程序中開啟的檔案數而定,一般設為呢要監視各檔案中的
//最大檔案號加一
//readfds:select監視的可讀檔案控制代碼集合
//writefds:select監視的可寫檔案控制代碼集合。
//exceptfds:select監視的異常檔案控制代碼集合。
//timeout:本次select的超時結束時間。
ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);
if (ret < 0)
{
perror("select error!");
break;
}
else if (ret == 0)
{
printf("timeout/n");
continue;
}
// 輪詢各個檔案描述符(socket)
for (i = 0; i < conn_amount; i++)
{
//FD_ISSET(int fd, fdset *fdset):檢查fdset聯絡的檔案控制代碼fd是否可讀寫,
// >0表示可讀寫。
if (FD_ISSET(fd_A[i], &fdsr))
{
//接收資料
ret = recv(fd_A[i], buf, sizeof(buf), 0);
if (ret <= 0) //接收資料出錯
{
printf("client[%d] close/n", i);
close(fd_A[i]);
FD_CLR(fd_A[i], &fdsr);
fd_A[i] = 0;
}
else // 資料接收成功
{
//將接收資料的最後一位補0