《网络编程》高级 I/O

本节是套接字的高级 I/O 。为套接字设置超时闹钟,使用更加方便的数据传输函数。套接字的 I/O 操作上设置超时有三种方法:

  1. 调用 alarm 函数,在它指定超时到期时产生 SIGALRM 信号;
  2. 在 select 函数中设置超时阻塞等待 I/O,以替代直接阻塞在 read 或write 调用上;
  3. 使用 SO_RCVTIMEO 和 SO_SNDTIMEO 套接字选项(这两个选项只是一部分实现支持);

下面使用 alarm 产生的 SIGALRM 信号为 connect 函数设置超时,当然系统会为 connect 函数设置超时限制,这里我们只是表示 SIGALRM 信号在设置超时的作用。

#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>

typedef void Sigfunc(int);

extern void err_sys(const char *,...);
static Sigfunc *M_signal(int signo, Sigfunc *func);
static Sigfunc *MySignal(int signo, Sigfunc *func);
static void connect_alarm(int);

int Myconnect_timo(int sockfd, const struct sockaddr *saptr, socklen_t salen, int nsec)
{
    Sigfunc *sigfunc;
    int n;

    /* SIGALRM 信号处理函数,并保存现有信号处理函数 */
    sigfunc = MySignal(SIGALRM, connect_alarm);

    /* 设置alarm超时 */
    if(alarm(nsec) != 0)/* 若已经设置了超时,则alarm返回的是当前剩余秒数,否则返回0 */
        printf("alarm was already set\n");/* 提示:已经设置过alarm超时 */

    if( (n = connect(sockfd, saptr, salen)) < 0)
    {/* 由超时处理函数调用中断导致连接失败,则关闭套接字,并设置是由超时导致的失败 */
        close(sockfd);
        if(errno == EINTR)
            errno = ETIMEDOUT;
    }
    /* 关闭 alarm */
    alarm(0);
    /* 恢复原来的处理函数 */
    MySignal(SIGALRM, sigfunc);

    return(n);
}

static void connect_alarm(int signo)
{
    printf("flag: %d\n", signo);
    return;/* just interrupt the connect */
}

static Sigfunc *MySignal(int signo, Sigfunc *func)
{
    Sigfunc *sigfunc;
    if( (sigfunc = M_signal(signo, func)) == SIG_ERR)
        err_sys("signal error");
    return (sigfunc);
}

static Sigfunc *M_signal(int signo, Sigfunc *func)
{
    struct sigaction act, oact;

    /* 设置信号处理函数 */
    act.sa_handler = func;
    /* 初始化信号集 */
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if(signo == SIGALRM)
    {/* 若是SIGALRM信号,则系统不会自动重启 */
#ifdef SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;
#endif
    }
    else
    {/* 其余信号设置为系统会自动重启 */
#ifdef SA_RESTART
        act.sa_flags |= SA_RESTART;
#endif
    }
    /* 调用 sigaction 函数 */
    if(sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return(oact.sa_handler);
}

使用 select 函数为 recvfrom 设置超时,有关select 函数的讲解可以参考文章《I/O 多路复用》。

#include <sys/select.h>
#include <stdlib.h>
extern void err_sys(const char *,...);

/* 在指定的时间内等待描述符变为可读 */
int readable_timeo(int fd, int sec)
{
	fd_set			rset;
	struct timeval	tv;

    /* 初始化fd_set 结构,并添加描述符 */
	FD_ZERO(&rset);
	FD_SET(fd, &rset);

    /* 设置超时的时间 */
	tv.tv_sec = sec;/* 秒数 */
	tv.tv_usec = 0;/* 微秒 */

    /* 调用select函数,使进程阻塞于select的超时等待描述符变为可读 */
	return(select(fd+1, &rset, NULL, NULL, &tv));
		/* 4> 0 if descriptor is readable */
}
/* end readable_timeo */

int
Read_timeo(int fd, int sec)
{
	int		n;

	if ( (n = readable_timeo(fd, sec)) < 0)
		err_sys("readable_timeo error");
	return(n);
}

使用 使用 SO_RCVTIMEO 和 SO_SNDTIMEO 套接字选项,使用这两个套接字选项设置描述符时,其超时设置将应用于该描述符的所有读或写操作上,SO_RCVTIMEO 套接字选项只能应用于读操作,而 SO_SNDTIMEO 套接字选项只能应用于写操作。这两者仅支持一部分实现,例如在 connect 函数不能使用这两个套接字选项。

recv 和 send 函数

/* 数据传输 */
/*
 * 函数功能:发送数据;
 * 返回值:若成功则返回发送的字节数,若出错则返回-1;
 * 函数原型:
 */
#include <sys/socket.h>

ssize_t send(int sockfd, void *buff, size_t nbytes, int flags);
/*
 * 说明
 * 该函数的功能类似与write函数,除了有标识符flags之外,其他的相同;
 * flags标识符的取值如下:
 * (1)MSG_DONTROUTE   勿将数据路由出本地网络
 * (2)MSG_DONTWAIT    允许非阻塞操作
 * (3)MSG_EOR         如果协议支持,此为记录结束
 * (4)MSG_OOB         如果协议支持,发送带外数据
 *
 * 若send成功返回,并不必然表示连接另一端的进程接收数据,只能说数据已经无错误地发送到网络;
 *
 * 对于支持为报文设限的协议,若报文超过协议所支持的最大尺寸,send失败并将errno设为EMSGSIZE;
 * 对于字节流协议,send会阻塞直到整个数据被传输;
 */

/*
 * 函数功能:接收数据;
 * 返回值:以字节计数的消息长度,若无可用消息或对方已经按序结束则返回0,若出错则返回-1;
 * 函数原型:
 */
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
/*
 * 说明
 * 该函数的功能类似与read函数,除了有标识符flags之外,其他的相同;
 * flags标识符的取值如下:
 * (1)MSG_PEEK        返回报文内容而不真正取走报文,即查看可读取数据
 * (2)MSG_TRUNC       即使报文被截断,要求返回的是报文的实际长度
 * (3)MSG_WAITALL     等待直到所有数据可用
 * (4)MSG_OOB         如果协议支持,发送带外数据
 * (5)MSG_DONTWAIT    允许非阻塞操作
 *
 */

下面是 flags 标志的功能:

  1. MSG_DONTROUTE:本标志告知内核,目的主机在某个直接连接的本地网络上,因而无需执行路由表查找;
  2. MSG_DONTWAIT:本标志在无需打开相应套接字的非阻塞标志的前提下,把单个 I/O 操作临时指定为非阻塞,接着执行 I/O 操作,然后关闭非阻塞标志;
  3. MSG_OOB:若协议支持,则可发送带外数据;
  4. MSG_PEEK :本标志适用于 recv 和 recvfrom 函数,它允许查看已可读取的数据,而且系统不在 recv 或 recvfrom 返回后丢弃这些数据;
  5. MSG_WAITALL:等待所有数据可用;

readv 和 writev 函数

这两个函数类似于 read 和 write 函数,但是它们支持多个缓冲区操作。其定义如下:

/* 读、写多个非连续的缓冲区 */

/*
 * 函数功能:读取数据到多个非连续的缓冲区,或从多个非连续缓冲区写数据到文件;
 * 返回值:若成功则返回已读、写的字节数,若出错则返回-1;
 * 函数原型:
 */
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
/*
 * 说明:
 * iovec的指针结构如下:
 */
struct iovec
{
    void *iov_base;     /* starting address of buffer */
    size_t iov_len;     /* size of buffer */
};

recvmsg 和 sendmsg 函数

这两个函数是通用的 I/O 函数,前面介绍的 I/O 函数都可以使用这两个函数替换。其定义如下:

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
/*
 * 说明
 * 该函数可以使用不止一个的选择来通过套接字发送数据,可以指定多重缓冲区传输数据,类似与readv函数;
 * msghdr结构至少包含以下成员:
 */
struct msghdr
{
    void        *msg_name;      /* optional address */
    socklen_t   msg_namelen;    /* address size in bytes */
    struct iovec *msg_iov;      /* array of IO buffers */
    int         msg_iovlen;     /* number of elements in array */
    void        *msg_control;   /* ancillary data */
    socklen_t   msg_controllen; /* number of ancillary bytes */
    int         msg_flags;      /* flags for recevied message */
};

参考资料:

《Unix 网络编程》

时间: 2024-11-05 22:48:03

《网络编程》高级 I/O的相关文章

《C#网络编程高级篇之网页游戏辅助程序设计(扫描版)》

<C#网络编程高级篇之网页游戏辅助程序设计>通过编写C#网络编程语言中具有代表性的实例,向读者深入细致地讲解了如何利用C#语言进行网页游戏辅助程序设计.本书通过大量的代码引导读者一步步学习和掌握C#的网络应用编程的方法和网页游戏辅助程序的设计技术. <C#网络编程高级篇之网页游戏辅助程序设计>涉及的领域包括多线程编程技术.socket套接字编程.tcp协议编程.http协议编程.远程控制技术.木马技术.模拟键盘和鼠标技术.网页游戏辅助程序设计技术等. <C#网络编程高级篇之网

Unix网络编程 高级IO套接字设置超时

我们知道,对于一个套接字的读写(read/write)操作默认是阻塞的,如果当前套接字还不可读/写,那么这个操作会一直阻塞下去,这样对于一个需要高性能的服务器来说,是不能接受的.所以,我们可以在进行读写操作的时候可以指定超时值,这样就读写操作就不至于一直阻塞下去. 在涉及套接字的I/O操作上设置超时的方法有三种: 1:调用alarm,它在指定的超时期满时产生SIGALRM信号.这个方法涉及信号处理,而信号处理在不同的实现上存在差异,而且可能干扰进程中现有的alarm调用. 2:在select中阻

第13篇-JAVA 网络编程

第13篇-JAVA 网络编程 每篇一句 比我强大的人都在努力我还有什么理由不拼命 初学心得 不去追逐你所渴求你将永远不会拥有 (笔者JEEP/711)[JAVA笔记 | 时间:2017-04-22| JAVA 网络编程 ] 1.网络编程基本概念 1.什么是计算机网络 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备通过通信线路连接起来在网络操作系统网络管理软件及网络通讯协议的管理和协调下实现资源共享和信息传递的计算机系统 把分布在不同地理区域的计算机与专门的外部设备用通讯线路互

Linux C高级编程——网络编程基础(1)

Linux高级编程--BSD socket的网络编程 宗旨:技术的学习是有限的,分享的精神是无限的. 一网络通信基础 TCP/IP协议簇基础:之所以称TCP/IP是一个协议簇,是由于TCP/IP包括TCP .IP.UDP.ICMP等多种协议.下图是OSI模型与TCP/IP模型的对照.TCP/IP将网络划分为4层模型:应用层.传输层.网络层和网络接口层(有些书籍将其分为5层,即网络接口层由链路层和物理层组成) (1)网络接口层:模型的基层.负责数据帧的发送已接收(帧是独立的网络信息传输单元).网络

Unix网络编程(六)高级I/O技术之复用技术 select

I/O复用技术 本文将讨论网络编程中的高级I/O复用技术,将从下面几个方面进行展开: a. 什么是复用技术呢? b. 什么情况下需要使用复用技术呢? c. I/O的复用技术的工作原理是什么? d. select, poll and epoll的实现机制,以及他们之间的区别. 下面我们以一个背景问题来开始: 包括在以前的文章中我们讨论的案例都是阻塞式的I/O包括(fgetc/getc, fgets/gets),即当输入条件未满足时进程会阻塞直到满足之后进行读取,但是这样导致的一个 问题是如果此时进

python高级之网络编程

python高级之网络编程 本节内容 网络通信概念 socket编程 socket模块一些方法 聊天socket实现 远程执行命令及上传文件 socketserver及其源码分析 1.网络通信概念 说到网络通信,那就不得不说TCP/IP协议簇的OSI七层模型了,这个东西当初在学校都学烂了...(PS:毕竟本人是网络工程专业出身...) 简单介绍下七层模型从底层到上层的顺序:物理层(定义物理设备的各项标准),数据链路层(mac地址等其他东西的封装),网络层(IP包头的的封装),传输层(TCP/UD

第8章 面向对象高级编程与网络编程

面向对象的特征 一.多态 程序在运行的过程中,根据传递的参数的不同,执行不同的函数或者操作不同的代码,这种在运行过程中才确定调用的方式成为运行时多态 import abc #多态:同一种事物的多种形态 class Animal: #同一类事物:动物 def talk(self): pass class People(Animal): #动物的形态之一:人 def talk(self): print('say hello') class Dog(Animal): #动物的形态之二:狗 def ta

python基础--面向对象高级、异常处理、网络编程

一.面向对象高级 1.接口与归一化设计 接口只是定义了一些方法,而没有去实现,多用于程序设计时,只是设计需要有什么样的功能,但是并没有实现任何功能,这些功能需要被另一个类(B)继承后,由 类B去实现其中的某个功能或全部功能. 在python中接口由抽象类和抽象方法去实现,接口是不能被实例化的,只能被别的类继承去实现相应的功能 归一化让使用者无需关心对象的类是什么,只需要知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度. 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的

Python自动化开发课堂笔记【Day08】 - Python进阶(面向对象的高级用法,网络编程)

面向对象的高级用法 1. __str__ 只要执行打印对象的操作,就会触发该对象类中的__str__方法(也就是对象的绑定方法)它是一种默认的方法,默认的打印输出为<__main__.Foo object at 0x003EE350>,但是如果将该绑定方法在类中重写的话,要求必须有以字符串类型的返回值,返回形式可以自己设定. class Foo: def __init__(self,name,age): self.name = name self.age = age def __str__(s