UNIX操作系统下一共有5种I/O模型,他们分别是:
1.
阻塞IO (Blocking I/O Model),如下图:
这个模型是I/O模型当中最简单的一种,阻塞很简单,直接调用read函数,一般平时使用的就是这个模型,kernel会自动完成等待数据跟取得数据2个步骤,如果用这个模型,只能处理单个I/O的情况。
代码如下:
服务器代码:
- #include "unp.h"
- int main()
- {
- int server_sockfd, client_sockfd;
- int server_len, client_len;
- struct sockaddr_un server_address;
- struct sockaddr_un client_address;
- //删除之前留有的server_socket
- unlink("server_socket");
- //建立一个本地的socket
- server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
- server_address.sun_family = AF_UNIX;
- strcpy(server_address.sun_path, "server_socket");
- server_len = sizeof(server_address);
- //将socket绑定
- bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
- //监听server_sockfd,这里的第二个参数比较注意,在kernel2.2之后,第二个参数的含义就不再是对connect有限制了
- //他的含义是同时能进行的握手次数,这里就一般设置为5了。具体可以参考man 2 listen。
- listen(server_sockfd, 5);
- while(1)
- {
- char ch;
- printf("server waiting\n");
- client_len = sizeof(client_address);
- //对客户机connect上来的fd进行握手,这里有个问题就是这里返回的client_sockfd跟客户机的fd是不一样的,
- //这个函数是重新创建了一个新的socket并且用一个新的file descriptor来描述那个新创建的socket
- client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);
- printf("i think client_sockfd is %d", client_sockfd);
- //blocking model 直接read block用户进程,这样的话多个用户进程就不能用时获取数据了。
- read(client_sockfd, & ch, 1);
- ch++;
- write(client_sockfd, &ch, 1);
- close(client_sockfd);
- }
- }
客户机代码:
- #include "unp.h"
- int main()
- {
- int sockfd;
- int len;
- struct sockaddr_un address;
- int result;
- int maxfdp1;
- fd_set rset;
- char ch = 'A';
- address.sun_family = AF_UNIX;
- printf("created socketfd\n");
- strcpy(address.sun_path, "server_socket");
- len = sizeof(address);
- sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
- result = connect(sockfd, (struct sockaddr *)&address, len);
- if (result == -1)
- {
- perror("oops: client1");
- exit(1);
- }
- printf("socket client %d", sockfd);
- write(sockfd, &ch, 1);
- read(sockfd, &ch, 1);
- close(sockfd);
- printf("char from server = %c\n", ch);
- exit(0);
- }
2.
非阻塞IO (Nonblocking I/O Model)
这个模型是说,当I/O进行操作的时候,用read函数调用,但是kernel不会阻塞这个进程,而是直接返回一个值判断是否能够读到数据。如果能读到,就把数据从kernel拷贝到用户空间。
这个模型感觉实用性不大,因为用户必须不停的去询问kernel是否准备好了,这样的话也浪费cpu的占有率。
使用该模型前,首先要用fcntl函数把fd设置为O_NONBLOCK。3.
IO多路复用 (I/O Multiplexing Model)
这种模型在多个客户机的的情况下也可以使用,所以一般的大型工程都会使用这个模型,这个模型服务器在连接好之后,会在一开始调用select函数,询问多个描述符准备情况,如果有一个符合条件,则返回。这个模型可以搭配FD_ISSET()函数使用,这个函数判断给定的描述符是否在准备好的描述符的set里面。
android的zygote这个进程里也使用这个模型来进行IPC。
如下:
- private static void runSelectLoopMode() throws MethodAndArgsCaller {
- ArrayList<FileDescriptor> fds = new ArrayList();
- ArrayList<ZygoteConnection> peers = new ArrayList();
- FileDescriptor[] fdArray = new FileDescriptor[4];
- fds.add(sServerSocket.getFileDescriptor());
- peers.add(null);
- int loopCount = GC_LOOP_COUNT;
- while (true) {
- int index;
- /*
- * Call gc() before we block in select().
- * It's work that has to be done anyway, and it's better
- * to avoid making every child do it. It will also
- * madvise() any free memory as a side-effect.
- *
- * Don't call it every time, because walking the entire
- * heap is a lot of overhead to free a few hundred bytes.
- */
- if (loopCount <= 0) {
- gc();
- loopCount = GC_LOOP_COUNT;
- } else {
- loopCount--;
- }
- try {
- fdArray = fds.toArray(fdArray);
- //这个函数内部使用select,并使用多路复用I/0模型。
- index = selectReadable(fdArray);
- } catch (IOException ex) {
- throw new RuntimeException("Error in select()", ex);
- }
- if (index < 0) {
- throw new RuntimeException("Error in select()");
- } else if (index == 0) {
- ZygoteConnection newPeer = acceptCommandPeer();
- peers.add(newPeer);
- fds.add(newPeer.getFileDesciptor());
- } else {
- boolean done;
- done = peers.get(index).runOnce();
- if (done) {
- peers.remove(index);
- fds.remove(index);
- }
- }
- }
- }
代码如下:
服务器代码:
- #include "unp.h"
- int main()
- {
- int server_sockfd, client_sockfd;
- int server_len, client_len;
- struct sockaddr_un server_address;
- struct sockaddr_un client_address;
- int result;
- fd_set readfds, testfds;
- unlink("server_socket");
- server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
- server_address.sun_family = AF_UNIX;
- strcpy(server_address.sun_path, "server_socket");
- server_len = sizeof(server_address);
- bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
- listen(server_sockfd, 5);
- FD_ZERO(&readfds);
- FD_SET(server_sockfd, &readfds);
- while(1)
- {
- char ch;
- int fd;
- int nread;
- testfds = readfds;
- printf("server waiting\n");
- result = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, NULL);
- if (result < 1)
- {
- perror("server 2");
- exit(1);
- }
- for (fd = 0; fd < FD_SETSIZE; fd++)
- {
- //这个FD_ISSET函数的含义是判断fd是不是testfds这个集合里面select激活的那几个fd中的一个。
- if (FD_ISSET(fd, &testfds))
- {
- if ( fd == server_sockfd)
- {
- client_len = sizeof(client_address);
- client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);
- FD_SET(client_sockfd, &readfds);
- printf("adding client on fd %d\n", client_sockfd);
- }
- else
- {
- ioctl(fd, FIONREAD, &nread);
- if ( nread == 0)
- {
- close(fd);
- FD_CLR(fd, &readfds);
- printf("removing client on fd %d\n", fd);
- }
- else
- {
- read(fd, &ch, 1);
- sleep(5);
- printf("serving client on fd %d\n", fd);
- ch++;
- write(fd, &ch, 1);
- }
- }
- }
- }
- }
- }
客户机代码由于跟阻塞模型一致,所以就不写出了。4. 信号驱动IO (Signal-Driven I/O Model)
信号驱动也不是真正的异步模型,他首先建立了一个SIGIO signal ,然后kernel会在数据准备好之后返回一个信号,这个时候用户进程调用read将从kernel的数据拷贝到用户进程当中。
严格来讲必须进行下列3步骤:(from unix network
programming)
1. A signal
handler must be established for the SIGIO signal.
2. The socket
owner must be set, normally with the F_SETOWN command of fcntl.
3. Signal-driven
I/O must be enabled for the socket, normally with the F_SETFL command of fcntl to turn on the O_ASYNC
flag.
5.
异步IO (Asynchronous I/O Model)
异步IO这个模型是真正的异步模型,他直接调用了一个系统函数,然后等待kernel的数据准备好之后直接返回一个信号。用户进程接收到这个信号之后得到从kernel发来的数据。
这个模型不会让用户进程等待,所以他是真正的异步模型。
参考资料:网络
阅读(4063) | 评论(0) | 转发(2) |