1. 五種I/O模型
阻塞I/O
當套接口完成連接,可以使用recv函數向系統提出receive請求,來接收數據,這個請求是阻塞的,直到對等方發送數據過來。
非阻塞I/O
使用fcntl函數來將套接字改爲非阻塞模式。fcntl(fd, F_SETFL, flag|O_NONBLOCK);這時候recv函數即使沒有收到數據,也不會阻塞,會返回一個錯誤,返回值爲-1,錯誤代碼爲EWOULDBLOCK。如果還想獲取到數據,就再次提出請求。這個很少使用,由於它接受的過程相當於一個循環,對cpu資源的一種浪費,這種循環接收被稱之爲忙等待。
I/O複用(select和poll)
主要是通過select來實現的,select是用來管理多個文件描述符,當其中的一個或者多個檢測到有數據到來,select就返回,這時候在調用recv就不會阻塞了。實際上就是將阻塞的位置提前到了select上。核心思想就是使用select來管理多個文件描述符。
信號驅動I/O
當數據到來,它是以信號的方式來通知應用進程,應用進程要在信號處理程序中去處理接收數據的細節。
異步I/O
這個模型是效率最高的。使用aio_read來實現的。該函數提交一個請求,會遞交一個應用層緩存區,即使沒有數據到來,該函數也立刻返回,這時候應用進程就可以處理其它的事情達到異步處理的效果。當有數據到達時,就會將數據拷貝到該函數提供的緩存區中,複製完成後,會通過一個信號來通知應用進程的程序來處理數據。數據直接從內核推送到用戶的緩存區中。
2.select
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
參數分析:
fd_set *readfds 讀集合檢測到有數據可讀的套接口就會存放在這裏。
fd_set *writefds 可寫集合
fd_set *exceptfds 異常集合
struct timeval *timeout (超時結構體)超時時間,如果設置爲NULL表示必須等到有事件發生才返回,
如果設置了時間,就表示在這個時間範圍內如果沒有事件發生也會返回,返回事件個數爲0。
如果select返回失敗返回-1.
int nfds 存放在集合中的描述符的最大值+1,有關文件描述符的含義可以查看https://www.jianshu.com/p/a2df1d402b4d這篇文章。
select函數的作用相當於一個管理者,用來管理多個I/O一旦其中的一個I/O或者多個I/O檢測到我們感興趣的事件,select函數返回,返回值爲檢測到的事件個數。並且會返回那些I/O發生的事件。這樣就可以去遍歷這些事件,進而去處理這些事件。該函數的後四個參數都是輸入輸出參數,會根據實際情況,來修改內部的值。
使用到的函數:
void FD_CLR(int fd, fd_set *set);
作用是將fd這個文件描述符號,從這個set中移除。
int FD_ISSET(int fd, fd_set *set);
作用是判定fd該文件描述符,是否在這個集合當中
void FD_SET(int fd,fd_set *set);
作用是將fd添加到集合當中。
void FD_ZERO(fd_set *set);
作用是清空集合
使用select改進回射客戶端程序
使用select來管理標準輸入I/O和套接口I/O
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
using namespace std;
struct packet
{
int len;
char buf[1024];
};
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0);
ssize_t readn(int fd, void *buf, size_t count)
{
size_t nleft = count; // 剩餘字節數
ssize_t nread;
char *bufp = (char*) buf;
while (nleft > 0)
{
nread = read(fd, bufp, nleft);
if (nread < 0)
{
if (errno == EINTR)
{
continue;
}
return -1;
} else if (nread == 0)
{
return count - nleft;
}
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft = count;
ssize_t nwritten;
char* bufp = (char*)buf;
while (nleft > 0)
{
if ((nwritten = write(fd, bufp, nleft)) < 0)
{
if (errno == EINTR)
{
continue;
}
return -1;
}
else if (nwritten == 0)
{
continue;
}
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while (1)
{
int ret = recv(sockfd, buf, len, MSG_PEEK); // 查看傳入消息
if (ret == -1 && errno == EINTR)
{
continue;
}
return ret;
}
}
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
int ret;
int nread;
char *bufp = (char*)buf; // 當前指針位置
int nleft = maxline;
while (1)
{
ret = recv_peek(sockfd, buf, nleft);
if (ret < 0)
{
return ret;
}
else if (ret == 0)
{
return ret;
}
nread = ret;
int i;
for (i = 0; i < nread; i++)
{
if (bufp[i] == '\n')
{
ret = readn(sockfd, bufp, i+1);
if (ret != i+1)
{
exit(EXIT_FAILURE);
}
return ret;
}
}
if (nread > nleft)
{
exit(EXIT_FAILURE);
}
nleft -= nread;
ret = readn(sockfd, bufp, nread);
if (ret != nread)
{
exit(EXIT_FAILURE);
}
bufp += nread;
}
return -1;
}
void ehco_cli(int sockfd)
{
// char recvbuf[1024];
// char sendbuf[1024];
// // struct packet recvbuf;
// // struct packet sendbuf;
// memset(recvbuf, 0, sizeof recvbuf);
// memset(sendbuf, 0, sizeof sendbuf);
// int n = 0;
// while (fgets(sendbuf, sizeof sendbuf, stdin) != NULL) // 鍵盤輸入獲取
// {
// writen(sockfd, sendbuf, strlen(sendbuf)); // 寫入服務器
//
// int ret = readline(sockfd, recvbuf, sizeof recvbuf); // 服務器讀取
// if (ret == -1)
// {
// ERR_EXIT("readline");
// }
// if (ret == 0)
// {
// printf("server close\n");
// break;
// }
//
// fputs(recvbuf, stdout); // 服務器返回數據輸出
//
// // 清空
// memset(recvbuf, 0, sizeof recvbuf);
// memset(sendbuf, 0, sizeof sendbuf);
// }
fd_set rset;
FD_ZERO(&rset);
int nready;
int maxfd;
int fd_stdin = fileno(stdin);
if (fd_stdin > sockfd)
{
maxfd = fd_stdin;
} else {
maxfd = sockfd;
}
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while (1)
{
FD_SET(fd_stdin, &rset);
FD_SET(sockfd, &rset);
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (nready == -1)
{
ERR_EXIT("select");
}
if (nready == 0)
{
continue;
}
if (FD_ISSET(sockfd, &rset)) // sock數據 讀取
{
int ret = readline(sockfd, recvbuf, sizeof recvbuf); // 服務器讀取
if (ret == -1)
{
ERR_EXIT("readline");
}
if (ret == 0)
{
printf("server close\n");
break;
}
fputs(recvbuf, stdout); // 服務器返回數據輸出
memset(recvbuf, 0, sizeof(recvbuf));
}
if (FD_ISSET(fd_stdin, &rset))
{
if (fgets(sendbuf, sizeof sendbuf, stdin) == NULL) // 鍵盤輸入獲取
{
break;
}
writen(sockfd, sendbuf, strlen(sendbuf)); // 寫入服務器
}
}
close(sockfd);
};
void handle_sigchld(int sig)
{
// wait(NULL);
while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main(int argc, char** argv) {
// signal(SIGCHLD, SIG_IGN);
signal(SIGCHLD, handle_sigchld);
// 1. 創建套接字
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
ERR_EXIT("socket");
}
// 2. 分配套接字地址
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof servaddr);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
// servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
// inet_aton("127.0.0.1", &servaddr.sin_addr);
// 3. 請求鏈接
if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof servaddr) < 0) {
ERR_EXIT("connect");
}
struct sockaddr_in localaddr;
socklen_t addrlen = sizeof localaddr;
if (getsockname(sockfd, (struct sockaddr*)&localaddr, &addrlen) < 0)
{
ERR_EXIT("getsockname");
}
printf("id = %s, ", inet_ntoa(localaddr.sin_addr));
printf("port = %d\n", ntohs(localaddr.sin_port));
// 4. 數據交換
ehco_cli(sockfd);
// 5. 斷開連接
close(sockfd);
return 0;
}