《Unix网络编程》卷1:套接字联网API(第3版):非阻塞I/O、ioctl操作、路由套接字

全书共31章+附录。

计划安排
:吃透这本书,一天三章+源码,并实测代码做当天笔记,CSDN见。
时间安排:计划时间1.5个月 == 6个周末 == 12天。
2017.08.05    第01-03章:TCP/IP简介、传输层、套接字编程简介
2017.08.06    第04-06章:基本TCP编程、TCP客户端/服务器程序、I/O复用
2017.08.12    第07-09章:套接字选项、基本UDP编程、基本SCTP编程(略)
2017.08.13    第10-12章:SCTP客户端/服务器程序例子(略)、名字与地址互换、IPv4和IPv6互操作性
2017.08.19    第13-15章:守护进程和inetd超级服务器、高级I/O、Unix域协议
2017.08.20    第16-18章:非阻塞I/O、ioctl操作、路由套接字
2017.08.26    第19-21章:密钥管理套接字、广播、多播
2017.08.27    第22-24章:高级UDP编程、高级SCTP编程、带外数据
2017.09.02    第25-27章:信号驱动I/O、线程、IP选项
2017.09.03    第28-30章:原始套接字、数据链路访问、客户端/服务器程序设计范式
2017.09.09    第31章-附录:流。附录:IPv4/6协议、调试技术
2017.09.10    整理、总结:思维导图。

>>第16章丶非阻塞式I/O

套接字的默认状态是阻塞的。
这就意味着当发出一个不能立即完成的套接字调用时,其进程将被投入睡眠,等待相应操作完成。
可能阻塞的套接字调用可分为以下4类:
(1) 输入操作:read、readv、recv、recvfrom、recvmsg五个函数。
    如果某个进程对一个阻塞的TCP套接字调用这些输入函数之一,而且该套接字的接收缓冲区中没有数据可读,该进程将被投入睡眠,直到有一些数据到达。
(2) 输出操作:write、writev、send、sendto、sendmsg五个函数。
    对于阻塞的套接字,如果其发送缓冲区中没有空间,进程将被投入睡眠,直到有空间为止。
(3) 接受外来连接:accept函数。
    如果对一个阻塞的套接字调用accept函数,并且尚无新的连接到达,调用进程将被投入睡眠。
(4) 发起外出连接:connect函数(TCP)。
    TCP连接的建立涉及三路握手过程,而且connect函数一直要等到客户收到对于自己的SYN的ACK为止才返回。


非阻塞读和写:str_cli函数

使用非阻塞式I/O的版本,防止进程在可做任何有效工作期间发生阻塞。
/* str_cli函数代码演示 */
/* include nonb1 */
#include	"unp.h"

void
str_cli(FILE *fp, int sockfd)
{
	int			maxfdp1, val, stdineof;
	ssize_t		n, nwritten;
	fd_set		rset, wset;
	char		to[MAXLINE], fr[MAXLINE];
	char		*toiptr, *tooptr, *friptr, *froptr;

    // 使用fctnl把3个描述符都设置为非阻塞模式
	val = Fcntl(sockfd, F_GETFL, 0);
	Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);

	val = Fcntl(STDIN_FILENO, F_GETFL, 0);
	Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);

	val = Fcntl(STDOUT_FILENO, F_GETFL, 0);
	Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);

	toiptr = tooptr = to;	/* initialize buffer pointers */
	friptr = froptr = fr;
	stdineof = 0;

	maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
	for ( ; ; ) {
		FD_ZERO(&rset);
		FD_ZERO(&wset);
		if (stdineof == 0 && toiptr < &to[MAXLINE])
			FD_SET(STDIN_FILENO, &rset);	/* read from stdin */
		if (friptr < &fr[MAXLINE])
			FD_SET(sockfd, &rset);			/* read from socket */
		if (tooptr != toiptr)
			FD_SET(sockfd, &wset);			/* data to write to socket */
		if (froptr != friptr)
			FD_SET(STDOUT_FILENO, &wset);	/* data to write to stdout */

		Select(maxfdp1, &rset, &wset, NULL, NULL);
/* end nonb1 第一部分:初始化并调用select */
/* include nonb2 */
		if (FD_ISSET(STDIN_FILENO, &rset)) {
			if ( (n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) {
				if (errno != EWOULDBLOCK)
					err_sys("read error on stdin");

			} else if (n == 0) {
#ifdef	VOL2
				fprintf(stderr, "%s: EOF on stdin\n", gf_time());
#endif
				stdineof = 1;			/* all done with stdin */
				if (tooptr == toiptr)
					Shutdown(sockfd, SHUT_WR);/* send FIN */

			} else {
#ifdef	VOL2
				fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(), n);
#endif
				toiptr += n;			/* # just read */
				FD_SET(sockfd, &wset);	/* try and write to socket below */
			}
		}

		if (FD_ISSET(sockfd, &rset)) {
			if ( (n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) {
				if (errno != EWOULDBLOCK)
					err_sys("read error on socket");

			} else if (n == 0) {
#ifdef	VOL2
				fprintf(stderr, "%s: EOF on socket\n", gf_time());
#endif
				if (stdineof)
					return;		/* normal termination */
				else
					err_quit("str_cli: server terminated prematurely");

			} else {
#ifdef	VOL2
				fprintf(stderr, "%s: read %d bytes from socket\n",
								gf_time(), n);
#endif
				friptr += n;		/* # just read */
				FD_SET(STDOUT_FILENO, &wset);	/* try and write below */
			}
		}
/* end nonb2 第二部分:从标准输入或套接字读入 */
/* include nonb3 */
		if (FD_ISSET(STDOUT_FILENO, &wset) && ( (n = friptr - froptr) > 0)) {
			if ( (nwritten = write(STDOUT_FILENO, froptr, n)) < 0) {
				if (errno != EWOULDBLOCK)
					err_sys("write error to stdout");

			} else {
#ifdef	VOL2
				fprintf(stderr, "%s: wrote %d bytes to stdout\n",
								gf_time(), nwritten);
#endif
				froptr += nwritten;		/* # just written */
				if (froptr == friptr)
					froptr = friptr = fr;	/* back to beginning of buffer */
			}
		}

		if (FD_ISSET(sockfd, &wset) && ( (n = toiptr - tooptr) > 0)) {
			if ( (nwritten = write(sockfd, tooptr, n)) < 0) {
				if (errno != EWOULDBLOCK)
					err_sys("write error to socket");
			} else {
#ifdef	VOL2
				fprintf(stderr, "%s: wrote %d bytes to socket\n",
								gf_time(), nwritten);
#endif
				tooptr += nwritten;	/* # just written */
				if (tooptr == toiptr) {
					toiptr = tooptr = to;	/* back to beginning of buffer */
					if (stdineof)
						Shutdown(sockfd, SHUT_WR);	/* send FIN */
				}
			}
		}
	}
}
/* end nonb3 第三部分:写到标准输出或套接字 */
/* gf_time函数:返回指向时间字符串的指针,包括微秒(12:34:56.123456) */
char *gf_time(void)
{
	struct timeval	tv;
	time_t			t;
	static char		str[30];
	char			*ptr;

	if (gettimeofday(&tv, NULL) < 0)
		err_sys("gettimeofday error");

	t = tv.tv_sec;	/* POSIX says tv.tv_sec is time_t; some BSDs don't agree. */
	ptr = ctime(&t);
	strcpy(str, &ptr[11]);
		/* Fri Sep 13 00:00:00 1986\n\0 */
		/* 0123456789012345678901234 5  */
	snprintf(str+8, sizeof(str)-8, ".%06ld", tv.tv_usec);

	return(str);
}

非阻塞connect:时间获取客户程序

/* 代码演示 */
#include	"unp.h"

int connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
	int				flags, n, error;
	socklen_t		len;
	fd_set			rset, wset;
	struct timeval	tval;

    // fcntl把套接字设置为非阻塞模式
	flags = Fcntl(sockfd, F_GETFL, 0);
	Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    // 发起非阻塞connect。EINPROGRESS表示连接建立已经启动但是尚未完成。
	error = 0;
	if ( (n = connect(sockfd, saptr, salen)) < 0)
		if (errno != EINPROGRESS)
			return(-1);

	/* Do whatever we want while the connect is taking place. */
	if (n == 0)
		goto done;	/* 连接建立 connect completed immediately */

	FD_ZERO(&rset);
	FD_SET(sockfd, &rset);
	wset = rset;
	tval.tv_sec = nsec;
	tval.tv_usec = 0;
    // 调用select等待套接字变为可读或可写
	if ( (n = Select(sockfd+1, &rset, &wset, NULL,
					 nsec ? &tval : NULL)) == 0) {
		close(sockfd);		/* timeout */
		errno = ETIMEDOUT;
		return(-1);
	}

	if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
		len = sizeof(error);
		if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
			return(-1);			/* Solaris pending error */
	} else
		err_quit("select error: sockfd not set");

done:
	Fcntl(sockfd, F_SETFL, flags);	/* restore file status flags */

	if (error) {
		close(sockfd);		/* just in case */
		errno = error;
		return(-1);
	}
	return(0);
}

>>第17章丶ioctl操作

网络程序,特别是服务器程序,经常在程序启动执行后使用ioctl获取所在主机全部网络接口信息,包括:接口地址、是否支持广播、是否支持多播等等。

ioctl函数

此函数影响由fd参数引用的一个打开的文件。
#include <sys/ioctl.h>
int ioctl(int d, int request, .../* void *arg */);

网络相关的请求(request)划分为6类:
· 套接字操作(是否位于带外标记等)
· 文件操作(设置或清除非阻塞标志等)
· 接口操作(返回接口列表,获取广播地址等)
· ARP高速缓存操作(创建、修改、获取、删除)
· 路由表操作(增加或删除)
· 流系统

网络相关的ioctl请求总结:

/* 输出主机的硬件地址 - 代码演示 */
#include	"unpifi.h"
#include	<net/if_arp.h>

int main(int argc, char **argv)
{
	int					sockfd;
	struct ifi_info			*ifi;
	unsigned char		*ptr;
	struct arpreq		arpreq;
	struct sockaddr_in	*sin;

	sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
	for (ifi = get_ifi_info(AF_INET, 0); ifi != NULL; ifi = ifi->ifi_next) {
		printf("%s: ", Sock_ntop(ifi->ifi_addr, sizeof(struct sockaddr_in)));

		sin = (struct sockaddr_in *) &arpreq.arp_pa;
		memcpy(sin, ifi->ifi_addr, sizeof(struct sockaddr_in));

		if (ioctl(sockfd, SIOCGARP, &arpreq) < 0) {
			err_ret("ioctl SIOCGARP");
			continue;
		}

		ptr = &arpreq.arp_ha.sa_data[0];
		printf("%x:%x:%x:%x:%x:%x\n", *ptr, *(ptr+1),
			   *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5));
	}
	exit(0);
}

>>第18章丶路由套接字

路由套接字支持3种类型的操作:
(1) <root>进程可以通过写出到路由套接字而往内核发送消息,比如路径的增加和删除。
(2) <root>进程可以通过从路由套接字读入而自内核接收消息。
(3) <all>进程可以使用sysctl函数倾泻出路由表或列出所有已配置的接口。

#include <net/if_dl.h>
struct sockaddr_dl {
    uint8_t     sdl_len;
    sa_family_t sdl_family;     /* AF_LINK */
    uint16_t    sdl_index;      /* system assigned index, if>0 */
    uint8_t     sdl_type;       /* IF_ETHER, ect. from <net/if_types.h> */
    uint8_t     sdl_nlen;       /* name length, starting in sdl_data[0] */
    uint8_t     sdl_alen;       /* link-layer address length */
    uint8_t     sdl_slen;       /* link-layer selector length */
    char        sdl_data[12];   /* mininum work area, can be larger; contains i/f name and link-layer address */
};

读和写相关结构体:
struct rt_msghdr;
struct if_msghdr;
struct ifa_msghdr;
struct ifma_msghdr;
struct if_announcemsghdr;

sysctl函数

这个函数使用类似简单网络管理协议中管理信息库的名字。
#include <sys/param.h>
#include <sys/sysctl.h>

int sysctl (int *name, u_int namelen, void *oldp, size_t *oldenp, void *newp, size_t newlen);

/* sysctl函数判断UDP校验是否开启 */
#include	"unproute.h"
#include	<netinet/udp.h>
#include	<netinet/ip_var.h>
#include	<netinet/udp_var.h>		/* for UDPCTL_xxx constants */

int main(int argc, char **argv)
{
	int		mib[4], val;
	size_t	len;

	mib[0] = CTL_NET;
	mib[1] = AF_INET;
	mib[2] = IPPROTO_UDP;
	mib[3] = UDPCTL_CHECKSUM;

	len = sizeof(val);
	Sysctl(mib, 4, &val, &len, NULL, 0);
	printf("udp checksum flag: %d\n", val);

	exit(0);
}





特别备注:
由于一些特定的网络编程头文件暂时没办法查找到,很多例程比较难以验证,从代码中先学习其中的编程特点吧,后续针对概述的知识面文字内容会多于例程。

2017.08.20
16-18章基本完成...
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姜源Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值