以下知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。
本章主要讲述在I/O操作上设置超时的三种方法,然后介绍5种I/O函数中的3种,如何确定套接字接收缓冲区中的数据量,在套接字上使用C的标准I/O函数库,最后讨论等待事件的一些高级方法。东西有点杂,并且与标题有点不太符合,将就吸收吧!
1、套接字超时
i、使用alarm函数和SIGALRM信号,具体举列见代码,如下
/* include connect_timeo */
#include "unp.h"
static void connect_alarm(int);
int
connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec)//前三个参数用于调用connect函数,最后是等待的秒数
{
Sigfunc *sigfunc;
int n;
sigfunc = Signal(SIGALRM, connect_alarm);//为SIGALRM建立一个信号处理函数
if (alarm(nsec) != 0)//报警时钟设置成由调用者指定的秒数
err_msg("connect_timeo: alarm was already set");
if ((n = connect(sockfd, saptr, salen)) < 0) {//connect函数调用成功返回0,出错返回-1
close(sockfd);
if (errno == EINTR)
errno = ETIMEDOUT;
}
alarm(0); /* turn off the alarm */
Signal(SIGALRM, sigfunc); /* restore previous signal handler */
return(n);
}
static void
connect_alarm(int signo)
{
return; /* just interrupt the connect() */
}
/* end connect_timeo */
void
Connect_timeo(int fd, const SA *sa, socklen_t salen, int sec)//包裹函数
{
if (connect_timeo(fd, sa, salen, sec) < 0)
err_sys("connect_timeo error");
}
ii、使用select为recvfrom设置超时
,具体举列见代码,如下
#include "unp.h"
struct timeval {
long tv_sec;
long tv_usec;
};
int
readable_timeo(int fd, int sec)
{
fd_set rset;
struct timeval tv;
FD_ZERO(&rset);//描述符集置0
FD_SET(fd, &rset);//打开相应给定描述符对应的比特位
tv.tv_sec = sec;
tv.tv_usec = 0;
//select函数的最后一个参数即为所设置的时间参数
return(select(fd + 1, &rset, NULL, NULL, &tv));//阻塞在select且有时间限制,若最后一个参数为NULL,则一直阻塞。
//出错时返回-1,超时发生时为0,否则就是就绪描述符数目
/* 4> 0 if descriptor is readable */
}
iii、使用SO_RCVTIMEO套接字选项为recvfrom设置超时
,具体举列见代码,如下
#include "unp.h"
void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
//通过设置套接字选项,给描述符填入期望的超时值,其超时设置将应用于该描述符上的所有读操作
Setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);//如果超时,此处将返回一个EWOULDBLOCK
if (n < 0) {
if (errno == EWOULDBLOCK) {
fprintf(stderr, "socket timeout\n");
continue;
}
else
err_sys("recvfrom error");
}
recvline[n] = 0; /* null terminate */
Fputs(recvline, stdout);
}
}
2、5种I/O函数
#include<sys/socket.h>
//均返回:若成功则为读或写的字节数,若出错则为-1
ssize_t read(int fileds, void *buff, size_t nbytes);
ssize_t write(int fileds,const void *buff, size_t nbytes);
ssize_t recv(int sockfd, void *buff, size_t nbytes,int flags);
ssize_t send(int sockfd,const void *buff, size_t nbytes,int flags);
struct iovec {
void *iov_base;//内存超始地址
size_t iov_len;//内存大小
};
ssize_t readv(int fileds,const struct iovec* iov, size_t iovcnt);
ssize_t writev(int fileds, const struct iovec* iov, size_t iovcnt);
ssize_t recvfrom(int sockfd, void* buff,size_t nbytes,int flags,struct sockaddr* from,socklent_t* addrlen);
ssize_t sendto(int sockfd,const void* buff, size_t nbytes, int flags,const struct sockaddr* to, socklent_t* addrlen);
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
//主要讲解一下recvmsg和sendmsg的第二个参数
struct msghdr {
void* msg_name;//指向协议地址
socklen_t msg_namelen;//协议地址大小
struct iovec* msg_iov;//指定输入或输出缓冲区数组,类似readv和writev的第二个参数,即可以携带数据的buff
int msg_iovlen;//指明Buff的大小
void* msg_control;//指定可选的辅助数据的位置和大小,msg_controllen对于recvmsg是一个值-结果参数
socklen_t msg_controllen;//msg_controllen对于recvmsg是一个值-结果参数
int msg_flags;//recvmsg使用msg_flags成员,而sendmsg则忽略msg_flags成员
};
针对send/recv、sendto/recvfrom、sendmsg/recvmsg函数的flags参数以及结构msghdr内的成员msg_flags,具体标志设置见书上P308页,此处不再码出,相当懒哈!
3、辅助数据(针对上一点中struct msghdr中msg_control所指向的buff区域所携带的信息)
辅助数据由一个或多个辅助数据对象(ancillary data object)构成,每个对象以一个定义在头文件<sys/socket.h>的cmsghdr结构开头。其结构见下
struct cmsghdr {
socklen_t cmsg_len;
int cmsg_level;
int cmsg_type;
/*followed by unsigned char cmsg_data[]*/
};
其中cmsg_level、cmsg_type参考值见书上P310 图14-11。
4、套接字和标准I/O(存在着输入输出缓冲区问题)
标准I/O函数库处理UNIX I/O函数时,需要注意自动缓冲输入流和输出流问题以及附加的问题。
标准I/O函数库可用于套接字:
i、通过调用fdopen,可以从任何一个描述符创建出一个标准I/O流;代码如下:
#include "unp.h"
void
str_echo(int sockfd)
{
char line[MAXLINE];
FILE *fpin, *fpout;
//以下问题在于输出流是完全缓冲(后面介绍)
fpin = Fdopen(sockfd, "r");//从sockfd创建一个标准I/O流,用于流入
fpout = Fdopen(sockfd, "w");//从sockfd创建一个标准I/O流,用于流出
while (Fgets(line, MAXLINE, fpin) != NULL)
Fputs(line, fpout);
}
ii、通过调用fdopen,可以获取一个给定标准I/O流对应的描述符;代码如下:
FILE* fp;
iofd = fileno(fp);
iii、tcp和udp套接字是全双工的,标准I/O流也可以是全双工:只要以r+类型打开流,r+意味着读写。但在这样的流上,必须在调用一个输出函数之后插入一个fflush、fseek、fsetpos或rewind调用才能接着调用一个输入函数。反之,调用一个输入函数后也必须插入一个fseek、fsetpos或rewind调用才能接着调用一个输出函数,除非输入函数遇见一个EOF。fseek、fsetpos或rewind这3个函数的问题是它们都调用lseek而lseek用在套接字上只会失败。
标准I/O函数库执行以下三类缓冲:
i、完全缓冲:意味着只在出现下列情况时才发生I/O:缓冲区满,进程显式调用fflush(需要注意一下怎么用才好??),或进程调用exit终止自身。
ii、行缓冲:意味着只在出现下列情况时才发生I/O:碰到一个换行符,进程调用fflush,或进程调用exit终止自身。
iii、不缓冲:意味着每次调用标准I/O输出函数都发生I/O。
标准I/O函数库的大多数UNIX实现使用如下规则:
i、标准错误输出总是不缓冲;
ii、标准输入和标准输出完全缓冲,除非它们指代终端设备(这种情况下它们行缓冲);
iii、所有其他I/O流都是完全缓冲,除非它们指代终端设备(这种情况下它们行缓冲)。
5、高级轮询技术(只介绍/dev/poll接口(此机制代码应被认为不可移值的))
/dev/poll的特殊文件提供了一个可扩展的轮询大量描述符的方法,其与selectt和poll的最大区别:此轮询进程可以预先设置好待查询描述符的列表,然后进入一个循环等待事件发生,每次循环回来时不必再次设置该列表(这点我对比过,有点似懂非懂唉)。
使用的操作过程:打开/dev/poll,轮询进程必须先初始化一个pollfd结构数组。再调用write往/dev/poll设备上写这个结构数组以把它传递给内核,然后执行ioctl(第17章讲解)的DP_POLL命令阻塞自身以等待事件发生。传递给ioctl调用的结构如下:
struct pollfd {
int fd;
short events;
short revents;
};
struct dvpoll {
struct pollfd* dp_fds;//供ioctl返回时存放pollfd结构数组
int dp_nfds;//指定上述buff的大小
int dp_timeout;//0:非阻塞,-1没有超时设置
};
实际应用代码:
#include "unp.h"
#include <sys/devpoll.h>
void
str_cli(FILE *fp, int sockfd)
{
int stdineof;
char buf[MAXLINE];
int n;
int wfd;
struct pollfd pollfd[2];
struct dvpoll dopoll;
int i;
int result;
wfd = Open("/dev/poll", O_RDWR, 0);//打开/dev/poll
//轮询进程初始化pollfd结构数组
pollfd[0].fd = fileno(fp);//获取一个给定I/O流对应的描述符
pollfd[0].events = POLLIN;
pollfd[0].revents = 0;
pollfd[1].fd = sockfd;
pollfd[1].events = POLLIN;
pollfd[1].revents = 0;
Write(wfd, pollfd, sizeof(struct pollfd) * 2);//再调用write往/dev/poll设备上精心pollfd结构数组以传递给内核
stdineof = 0;
for (; ; ) {
/* block until /dev/poll says something is ready */
dopoll.dp_timeout = -1;//设置超时
dopoll.dp_nfds = 2;//指定缓冲区大小
dopoll.dp_fds = pollfd;//供ioctl返回时存放pollfd结构数组
result = Ioctl(wfd, DP_POLL, &dopoll);//轮询,阻塞于此,等待有事可做。ioctl返回值即已就绪描述符个数
/* loop through ready file descriptors */
for (i = 0; i < result; i++) {
if (dopoll.dp_fds[i].fd == sockfd) {
/* socket is readable */
if ((n = Read(sockfd, buf, MAXLINE)) == 0) {
if (stdineof == 1)//说明之前有EOF结束符
return; /* normal termination */
else
err_quit("str_cli: server terminated prematurely");
}
Write(fileno(stdout), buf, n);
}
else {
/* input is readable */
if ((n = Read(fileno(fp), buf, MAXLINE)) == 0) {
stdineof = 1;
Shutdown(sockfd, SHUT_WR); /* send FIN */
continue;
}
Writen(sockfd, buf, n);
}
}
}
}