基于I/O复用的TCP回射客户-服务器程序设计
一、实验目的
1. 理解五种I/O模型及其区别。
2. 掌握I/O复用模型,掌握select和poll函数的使用方法。
二、实验内容
1. 分别使用select和poll函数编写TCP回射服务器程序。
2. 使用select函数编写TCP回射客户程序。
三、实验步骤
1. 搭建网络编程实验环境
按照《网络编程实验环境搭建》进行。
2.编写基于I/O复用的TCP程序
1)打开终端窗口,建立一个文件夹,假如文件夹的名称是tcpdemo,可使用命令mkdir tcpdemo进行。
2)进入tcpdemo文件夹(cd tcpdemo),使用vi编辑器编写
² 编写服务器端程序:
n 使用select函数编写TCP回射服务器程序
点击(此处)折叠或打开
- #include "unp.h"
- int
- main(int argc, char **argv)
- {
- int i, maxi, maxfd, listenfd, connfd, sockfd;
- int nready, client[FD_SETSIZE];
- ssize_t n;
- fd_set rset, allset;
- char buf[MAXLINE];
- socklen_t clilen;
- struct sockaddr_in cliaddr, servaddr;
- listenfd = Socket(AF_INET, SOCK_STREAM, 0);
- bzero(&servaddr, sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- servaddr.sin_port = htons(SERV_PORT);
- Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
- Listen(listenfd, LISTENQ);
- maxfd = listenfd; /* initialize */
- maxi = -1; /* index into client[] array */
- for (i = 0; i < FD_SETSIZE; i++)
- client[i] = -1; /* -1 indicates available entry */
- FD_ZERO(&allset);
- FD_SET(listenfd, &allset);
- /* end fig01 */
- /* include fig02 */
- for ( ; ; ) {
- rset = allset; /* structure assignment */
- nready = Select(maxfd+1, &rset, NULL, NULL, NULL);
- if (FD_ISSET(listenfd, &rset)) { /* new client connection */
- clilen = sizeof(cliaddr);
- connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
- #ifdef NOTDEF
- printf("new client: %s, port %d\n",
- Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
- ntohs(cliaddr.sin_port));
- #endif
- for (i = 0; i < FD_SETSIZE; i++)
- if (client[i] < 0) {
- client[i] = connfd; /* save descriptor */
- break;
- }
- if (i == FD_SETSIZE)
- err_quit("too many clients");
- FD_SET(connfd, &allset); /* add new descriptor to set */
- if (connfd > maxfd)
- maxfd = connfd; /* for select */
- if (i > maxi)
- maxi = i; /* max index in client[] array */
- if (--nready <= 0)
- continue; /* no more readable descriptors */
- }
- for (i = 0; i <= maxi; i++) { /* check all clients for data */
- if ( (sockfd = client[i]) < 0)
- continue;
- if (FD_ISSET(sockfd, &rset)) {
- if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
- /*4connection closed by client */
- Close(sockfd);
- FD_CLR(sockfd, &allset);
- client[i] = -1;
- } else
- Writen(sockfd, buf, n);
- if (--nready <= 0)
- break; /* no more readable descriptors */
- }
- }
- }
- }
n 使用poll函数编写TCP回射服务器程序
点击(此处)折叠或打开
- /* include fig01 */
- #include "unp.h"
- #include <limits.h> /* for OPEN_MAX */
- #define OPEN_MAX 32
- #define POLLRDNORM 0x040
- int
- main(int argc, char **argv)
- {
- int i, maxi, listenfd, connfd, sockfd;
- int nready;
- ssize_t n;
- char buf[MAXLINE];
- socklen_t clilen;
- struct pollfd client[OPEN_MAX];
- struct sockaddr_in cliaddr, servaddr;
- listenfd = Socket(AF_INET, SOCK_STREAM, 0);
- bzero(&servaddr, sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- servaddr.sin_port = htons(SERV_PORT);
- Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
- Listen(listenfd, LISTENQ);
- client[0].fd = listenfd;
- client[0].events = POLLRDNORM;
- for (i = 1; i < OPEN_MAX; i++)
- client[i].fd = -1; /* -1 indicates available entry */
- maxi = 0; /* max index into client[] array */
- /* end fig01 */
- /* include fig02 */
- for ( ; ; ) {
- nready = Poll(client, maxi+1, INFTIM);
- if (client[0].revents & POLLRDNORM) { /* new client connection */
- clilen = sizeof(cliaddr);
- connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
- #ifdef NOTDEF
- printf("new client: %s\n", Sock_ntop((SA *) &cliaddr, clilen));
- #endif
- for (i = 1; i < OPEN_MAX; i++)
- if (client[i].fd < 0) {
- client[i].fd = connfd; /* save descriptor */
- break;
- }
- if (i == OPEN_MAX)
- err_quit("too many clients");
- client[i].events = POLLRDNORM;
- if (i > maxi)
- maxi = i; /* max index in client[] array */
- if (--nready <= 0)
- continue; /* no more readable descriptors */
- }
- for (i = 1; i <= maxi; i++) { /* check all clients for data */
- if ( (sockfd = client[i].fd) < 0)
- continue;
- if (client[i].revents & (POLLRDNORM | POLLERR)) {
- if ( (n = read(sockfd, buf, MAXLINE)) < 0) {
- if (errno == ECONNRESET) {
- /*4connection reset by client */
- #ifdef NOTDEF
- printf("client[%d] aborted connection\n", i);
- #endif
- Close(sockfd);
- client[i].fd = -1;
- } else
- err_sys("read error");
- } else if (n == 0) {
- /*4connection closed by client */
- #ifdef NOTDEF
- printf("client[%d] closed connection\n", i);
- #endif
- Close(sockfd);
- client[i].fd = -1;
- } else
- Writen(sockfd, buf, n);
- if (--nready <= 0)
- break; /* no more readable descriptors */
- }
- }
- }
- }
² 编写客户端程序
点击(此处)折叠或打开
- #include "unp.h"
- void
- str_cli(FILE *fp, int sockfd)
- {
- int maxfdp1, stdineof;
- fd_set rset;
- char buf[MAXLINE];
- int n;
- stdineof = 0;
- FD_ZERO(&rset);
- for ( ; ; ) {
- if (stdineof == 0)
- FD_SET(fileno(fp), &rset);
- FD_SET(sockfd, &rset);
- maxfdp1 = max(fileno(fp), sockfd) + 1;
- Select(maxfdp1, &rset, NULL, NULL, NULL);
- if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
- if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
- if (stdineof == 1)
- return; /* normal termination */
- else
- err_quit("str_cli: server terminated prematurely");
- }
- Write(fileno(stdout), buf, n);
- }
- if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
- if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
- stdineof = 1;
- Shutdown(sockfd, SHUT_WR); /* send FIN */
- FD_CLR(fileno(fp), &rset);
- continue;
- }
- Writen(sockfd, buf, n);
- }
- }
- }
tcpcli01.c
点击(此处)折叠或打开
- #include "unp.h"
- int
- main(int argc, char **argv)
- {
- int sockfd;
- struct sockaddr_in servaddr;
- if (argc != 2)
- err_quit("usage: tcpcli
" ); - sockfd = Socket(AF_INET, SOCK_STREAM, 0);
- bzero(&servaddr, sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_port = htons(SERV_PORT);
- Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
- Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
- str_cli(stdin, sockfd); /* do it all */
- exit(0);
- }
实验结果:
在客户端输入aaa,然后回显aaa
四 实验总结
通过本次实验,了解了I/O复用:select和poll函数,有了I/O复用,我们就可以调用select或poll,在这两个系统调用中的某一个上阻塞,而不是阻塞于真正的I/O系统调用。I/O复用模型最常用的函数时select,因此对select函数的掌握对于I/O复用的学习是有很大的帮助。通过本次实验的学习,强化了TCP客户-服务器程序的基本框架,使我真正明白了客户端和服务器直接回射的机理,熟悉了Ubuntu,同时对网络底层的一些运行原理有了很好的理解及掌握。
参考资料:《Unix网络编程》