1. 其他函数准备
1. TCP 回射服务器程序: str_echo 函数
#include “unp.h”void str_echo(int sockfd)
{ssize_t n;char buf[MAXLINE];again:/*write()
函数定义:ssize_t write (int fd, const void * buf, size_t count);
函数说明:write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。
返回值:如果顺利write()会返回实际写入的字节数(len)。当有错误发生时则返回-1,错误代码存入errno中。
*//*
read()函数定义:ssize_t read(int fd, void * buf, size_t count);函数说明:read()会把参数fd所指的文件传送count 个字节到buf 指针所指的内存中。返回值:返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据。若参数count 为0, 则read()不会有作用并返回0。另外,以下情况返回值小于count:
*/while((n = read(sockfd, buf, MAXLINE)) > 0)Writen(sockfd, buf, n);if(n < 0 && errno == EINTR) // 被中断后继续执行goto again;else if(n < 0)err_sys("str_echo: read error");
}
2. TCP 回射客户端程序: str_cli 函数
#include "unp.h"void str_cli(FILE *fp, int sockfd)
{char sendline[MAXLINE], recvline[MAXLINE];while(Fgets(sendline, NAXLINE, fp) != NULL){Writen(sockfd, sendline, strlen(sendline));if(Readline(sockfd, recvline, MALINE) == 0)err_quit("str_cli: server terminated prematurely");Fputs(recvline, stdout);}}
显示客户IP地址和端口号的时间获取服务器程序
#include "unp.h"
#include <time.h>#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop" //这是防止IDE输出警告的,不用管。
int
main(int argc, char **argv)
{int listenfd, connfd;// //len是一个 值——结果 变量socklen_t len;struct sockaddr_in servaddr,cliaddr;//cliaddr存放客户的协议地址//缓冲区存放转换结果char buff[MAXLINE];//存放时间time_t ticks;//初始化套接字,指定协议类型(第一个参数),套接字类型(字节流,数据报,或者原始套接字(极少使用))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(13);//本地协议地址赋予套接字//参数一:套接字描述符,用于标识套接字//参数二:指定特定于协议的地址结构的指针//参数三:地址结构的长度/*详细解释下为什么要传如地址结构的长度:由于该结构体的传递方向为从进程到内核,所以内核需要知道有多大的内容复制进来,所以需要一个参数告知内核传入地址的大小,这种变量叫做“值——结果”变量*/Bind(listenfd,(SA*) &servaddr,sizeof (servaddr));//使用socket创建的套接字最初为主动套接字,listen将其转换为一个被动套接字,指示内核应该接受指向该套接字的连接请求Listen(listenfd,LISTENQ);while (1){len =sizeof (cliaddr);//accept()函数功能是,从处于 established 状态的连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。//如果accept成功,那么它的返回值是由内核生成的全新的描述符,代表与客户之间的 已成功建立 的TCP链接(已连接套接字描述符)//参数一:监听套接字描述符//参数二:客户进程的协议地址//参数三:该地址的大小//监听套接字与已连接套接字的区别:监听套接字在该服务的生命周期内一直存在,而每个已完成三次握手的TCP链接都会分配一个已连接套接字connfd = Accept(listenfd,(SA*) &cliaddr,&len);printf("connection from %s , port %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr,buff,sizeof (buff)),//打印IP地址ntohs(cliaddr.sin_port));//打印端口号//获取时间ticks = time(NULL);//为什么不使用printf:因为printf的缓冲区不可控制,对我们来说难以控制snprintf(buff,sizeof (buff),"%.24s\r\n",ctime(&ticks));Write(connfd,buff,strlen(buff));Close(connfd);}}#pragma clang diagnostic pop
4.2作业
#include "unp.h"int
main(int argc, char **argv)
{int sockfd, n;char recvline[MAXLINE + 1];struct sockaddr_in servaddr;if (argc != 2)err_quit("usage: a.out <IPaddress>");if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)err_sys("socket error");bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(13); /* daytime server */if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)err_quit("inet_pton error for %s", argv[1]);if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)err_sys("connect error");struct sockaddr_in ss;char buff[MAXLINE];socklen_t len;len =sizeof (ss);if (getsockname(sockfd,(SA*) &ss,&len) < 0) printf("错误喽!");else{printf("connection from %s,port %d\n",inet_ntop(AF_INET,&ss.sin_addr,buff,sizeof (buff)),ntohs(ss.sin_port));}while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {recvline[n] = 0; /* null terminate */if (fputs(recvline, stdout) == EOF)err_sys("fputs error");}if (n < 0)err_sys("read error");exit(0);
}
5.2
#include "unp.h"#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
int
main(int argc, char **argv)
{int listenfd,confd;pid_t childpid;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);while (1){clilen =sizeof (cliaddr);confd = Accept(listenfd,(SA*) &cliaddr,&clilen);if((childpid = Fork()) == 0)//fork为每个客户派生一个处理它们的子进程//紫禁城关闭监听套接字,父进程关闭已连接套接字,紫禁城接着调用str_echo处理客户{Close(listenfd);str_echo(listenfd); exit(0);}Close(confd);}}
#pragma clang diagnostic pop
TCP回射客户程序
#include "unp.h"#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
int main(int argc, char **argv)
{int sockfd;struct sockaddr_in servaddr;if (argc !=2 )err_quit("请输入IP地址。");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);//将IP地址转换为二进制机器可读Connect(sockfd,(SA*) &servaddr,sizeof (servaddr));str_cli(stdin,sockfd);//回射函数exit(0);
}
#pragma clang diagnostic pop
僵死进程:
(1)概念: 进程实体已经释放,但进程对应的PCB进程控制块(进程描述符)还在.(2)产生的条件: 子进程比父进程结束的早,且父进程没有调用 wait() 获取子进程的退出码,这时子进程就变为僵死进程.(3)若子进程比父进程结束的晚,则在父进程结束后,子进程的父进程会变成pid为1 的进程
最终版本:
#include "unp.h"int
main(int argc, char **argv)
{int listenfd, connfd;pid_t childpid;socklen_t clilen;struct sockaddr_in cliaddr, servaddr;void sig_chld(int);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);/*按我现阶段的理解:(1)Signal捕获信号SIGCHLD,并交给sig_chld处理(2)Signal第二个参数实际上是一个函数指针,将sig_chld传入*//** sig_chld()函数解析:* 使用waitpid函数,并指定WNOHANG参数(有尚未终止的子进程是不要阻塞)* 为什么不适用wait函数,防止产生僵死进程,因为没有办法防止wait在正在运行的子进程尚有未终止阻塞(具体参见《unix网络编程》110页)* 阻塞:此处的阻塞不同于此前使用的同名词语,这里的阻塞是指阻塞信号或某个信号集,防止他们在阻塞期间提交。* 本节的目的是示范网络编程中可能遇到的情况:* (1)当fork某个进程时,必须捕获SIGCHLD信号* (2)当捕获信号时,必须处理被中断的系统调用* (3)SIGCHLD的信号处理函数必须正确编写,应使用waitpid函数以免留下僵死进程*/Signal(SIGCHLD, sig_chld); /* must call waitpid() */for ( ; ; ){clilen = sizeof(cliaddr);if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {if (errno == EINTR)continue; /* back to for() */elseerr_sys("accept error");}if ( (childpid = Fork()) == 0) { /* child process */Close(listenfd); /* close listening socket */str_echo(connfd); /* process the request */exit(0);}Close(connfd); /* parent closes connected socket */}
}
select()函数
#include "unp.h"#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"void str_cli(FILE *fp, int sockfd) {//需要检查的文件描述符个数int maxfdp1;/* fd_set其实这是一个数组的宏定义,实际上是一long类型的数组,* 每一个数组元素都能与一打开的文件句柄(socket、文件、管道、设备等)建立联系,建立联系的工作由程序员完成,* 当调用select()时,由内核根据IO状态修改fd_set的内容,* 由此来通知执行了select()的进程哪个句柄可读。*/fd_set rset;char sendline[MAXLINE], recvline[MAXLINE];FD_ZERO(&rset);while (1) {//将fd加入set集合/** FD_CLR(int fd, fd_set *fdset); //将fd从set集合中清除*FD_ISSET(int fd, fd_set *fdset); //检测fd是否在set集合中,不在则返回0*FD_ZERO(fd_set *fdset); //将set清零使集合中不含任何fd*///fileno()用来取得参数stream 指定的文件流所使用的文件描述词FD_SET(fileno(fp), &rset);//将套接字描述符加入集合FD_SET(sockfd, &rset);//为什么要加1呢:我们需要描述符的个数,而不是最大值,而描述符是从0开始的(妙吧!)maxfdp1 = max(fileno(fp), sockfd )+ 1;/*int select(int nfds, fd_set* readset, fd_set* writeset, fe_set* exceptset, struct timeval* timeout);* 参数:nfds 需要检查的文件描述字个数readset 用来检查可读性的一组文件描述字。writeset 用来检查可写性的一组文件描述字。exceptset 用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)timeout 超时,填NULL为阻塞,填0为非阻塞,其他为一段超时时间返回值:返回fd的总数,错误时返回SOCKET_ERROR*/Select(maxfdp1, &rset, NULL, NULL, NULL);//FD_ISSET(int fd, fd_set *fdset); //检测fd是否在set集合中,不在则返回0if (FD_ISSET(sockfd, &rset)) {if (Readline(sockfd, recvline, MAXLINE) == 0)err_quit("服务器超时");Fputs(recvline, stdout);}if (FD_ISSET(fileno(fp), &rset)) {if (Fgets(sendline, MAXLINE, fp) == NULL)return;Write(sockfd, sendline, strlen(sendline));}}}#pragma clang diagnostic pop
/* include fig01 */
#include "unp.h"#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
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; /* 初始化 */maxi = -1; /* index into client[] array *///将所有描述符初始化为-1for (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 NOTDEFprintf("new client: %s, port %d\n",Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),ntohs(cliaddr.sin_port));
#endiffor (i = 0; i < FD_SETSIZE; i++)if (client[i] < 0) {client[i] = connfd; /* 保存描述符 */break;/*保存了就跳出*/}if (i == FD_SETSIZE)err_quit("too many clients");FD_SET(connfd, &allset); /*添加新的描述符来设置*/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;} elseWriten(sockfd, buf, n);if (--nready <= 0)break; /* no more readable descriptors */}}}}
/* end fig02 */
#pragma clang diagnostic pop
pselect()函数
函数select()是用一种超时轮循的方式来查看文件的读写错误可操作性。在Linux下,还有一个相似的函数pselect()。
poll()函数
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:
监视并等待多个文件描述符的属性变化
参数:
fds:指向一个结构体数组的第0个元素的指针,每个数组元素都是一个struct pollfd结构,用于指定测试某个给定的fd的条件
struct pollfd{int fd; //文件描述符short events; //等待的事件short revents; //实际发生的事件
};