linux網路程式設計之用select方法實現io複用(基於udp)
阿新 • • 發佈:2019-01-24
1、基本概念
IO多路複用是指核心一旦發現程序指定的一個或者多個IO條件準備讀取,它就通知該程序。IO多路複用適用如下場合:
(1)當客戶處理多個描述字時(一般是互動式輸入和網路套介面),必須使用I/O複用。
(2)當一個客戶同時處理多個套介面時,而這種情況是可能的,但很少出現。
(3)如果一個TCP伺服器既要處理監聽套介面,又要處理已連線套介面,一般也要用到I/O複用。
(4)如果一個伺服器即要處理TCP,又要處理UDP,一般要使用I/O複用。
(5)如果一個伺服器要處理多個服務或多個協議,一般要使用I/O複用。
與多程序和多執行緒技術相比,I/O多路複用技術的最大優勢是系統開銷小,系統不必建立程序/執行緒,也不必維護這些程序/執行緒,從而大大減小了系統的開銷。
2、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。
3 、 實現基於udp客戶端到服務端的通訊
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <netinet/in.h>
#include <errno.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
int main()
{
char buf[100] = "";
int udp_fd = 0;
struct sockaddr_in addr;
struct sockaddr_in cliaddr;
//對套接字初始化
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8000);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bzero(&cliaddr, sizeof(cliaddr));
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(8000);
//建立套介面
if ((udp_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("socket create failed\n");
exit(EXIT_FAILURE);
}
puts("socket create success");
//設定埠
if (bind(udp_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
{
perror("bind fail\n");
close(udp_fd);
exit(EXIT_SUCCESS);
}
puts("bind success");
puts("input: \"192.168.221.x\" to send msg to body");
while (1)
{
char buf[100] = "";
fd_set rset;//建立檔案描述符的聚合變數
FD_ZERO(&rset);
FD_SET(0, &rset);
FD_SET(udp_fd, &rset);
write(1, "chenyu:", 7);
if (select(udp_fd + 1, &rset, NULL, NULL, NULL) > 0)
{
if (FD_ISSET(0, &rset)) //測試0是否可以讀寫 ,可以的話返回大於0的資料
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) + 1] = '\0';
char ipbuf[16] = "";
inet_pton(AF_INET, buf + 6, &cliaddr);
printf("\r\033[39m[%s]:\033[39m%s", inet_ntop(AF_INET, &addr.sin_addr, ipbuf, sizeof(ipbuf)), buf);
//iprintf("ip is %s\n", inet_ntop(AF_INET, &cliaddr.sin_addr, ipbuf, sizeof(ipbuf)));
if (strcmp(buf, "exit") == 0)
{
close(udp_fd);
exit(EXIT_SUCCESS);
}
sendto(udp_fd, buf, strlen(buf), 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
}
if (FD_ISSET(udp_fd, &rset))
{
puts("has go in rset udp_fd");
struct sockaddr_in addr;
char ipbuf[1024] = "";
socklen_t addrlen = sizeof(addr);
bzero(&addr, sizeof(addr));
puts("receive data start");
recvfrom(udp_fd, buf, 100, 0 , (struct sockaddr*)&addr, &addrlen);
printf("\r\033[34m[%s]:\033[34m%s", inet_ntop(AF_INET, &addr.sin_addr, ipbuf, sizeof(ipbuf)), buf);
puts("receive data end");
}
}
}
}