标准I/O VS 网络IO

  • 标准I/O又称为标准I/O流,从某种意义上讲是全双工的,因为程序能够在同一个流上执行输入和输出。
  • Unix/Linux对网络的抽象是一种称为套接字的文件类型。和任何Unix/Linux文件一样,套接字也是用文件描述符来引用的,在这种情况下称为套接字描述符。引用进程通过读写套接字描述符来与运行在其他计算机上的进程通信。

然而对流的限制和对套接字的限制,有时候会相互冲突。(However, there are restrictions on full-duplex streams that interact badly with restrictions on sockets:):

  • 限制一:跟在输出函数之后的输入函数。 如果中间没有插入对fflush, fseek, fsetpos或者rewind的调用,一个输入函数不能跟随在一个输出函数之后。 fflush 函数清空与流相关的缓冲区。后三个函数使用 Unix I/O lseek 函数来重置当前的文件位置。
  • 限制二:跟在输入函数之后的输出函数。 如果中间没有插入对 fseek, fsetpos或者rewind的调用,一个输出函数不能跟随在一个输入函数之后, 除非该输入函数遇到了一个EOF。

这些限制给网络应用带来了一个问题, 因为对套接字使用 lseek 函数是非法的。 对流 I/O 的第一个限制能够通过采用在每个输入操作前刷新缓冲区这样的规则来满足。然而,要满足第二个限制的唯一办法是,对同一个打开的套接字描述符打开两个流,一个用来读,一个用来写:

FILE *fpin, *fput; 

fpin = fdopen(sockfd, "r");
fput = fdopen(sockfd, "w");

但是这种方法也有问题,因为它要求应用程序在两个流上都要调用fclose, 这样才能释放与每个流相关联的存储器资源,避免存储器泄漏:

fclose(fpin);
fclose(fpout);

这些操作中的每一个都试图关闭同一个底层的套接字描述符,所以第二个close操作会失败。对顺序的程序来说,这并不是问题,但是在一个线程化的程序中关闭一个已经关闭了的描述符是会导致灾难的。

Linux网络IO的实现

头文件: rio.h

#ifndef __RIO_H__
#define __RIO_H__ #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h> // Simplifies calls to bind(), connect(), and accept()
typedef struct sockaddr SA; // Persistent state for the robust I/O (Rio) package
#define RIO_BUFSIZE 8192
typedef struct {
int rio_fd; // Descriptor for this internal buf
int rio_cnt; // Unread bytes in internal buf
char *rio_bufptr; // Next unread byte in internal buf
char rio_buf[RIO_BUFSIZE]; // Internal buffer
} rio_t; // External variables
extern int h_errno; // Defined by BIND for DNS errors
extern char **environ; // Defined by libc // Misc constants
#define MAXLINE 8192 // Max text line length
#define MAXBUF 8192 // Max I/O buffer size
#define LISTENQ 1024 // Second argument to listen() // Our own error-handling functions
void unix_error(char *msg);
void posix_error(int code, char *msg);
void dns_error(char *msg);
void app_error(char *msg); // Rio (Robust I/O) package
ssize_t rio_readn(int fd, void *usrbuf, size_t n);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
void rio_readinitb(rio_t *rp, int fd);
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n);
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); // Wrappers for Rio package
ssize_t Rio_readn(int fd, void *usrbuf, size_t n);
void Rio_writen(int fd, void *usrbuf, size_t n);
void Rio_readinitb(rio_t *rp, int fd);
ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n);
ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen); // Client/server helper functions
int open_clientfd(char *hostname, int portno);
int open_listenfd(int portno); // Wrappers for client/server helper functions
int Open_clientfd(char *hostname, int port);
int Open_listenfd(int port); #endif // __RIO_H__

网络IO函数及其包裹函数:

#include "rio.h"

/*********************************************************************
* The Rio package - robust I/O functions
**********************************************************************/
/*
* rio_readn - robustly read n bytes (unbuffered)
*/
// rio_readn
ssize_t rio_readn(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nread;
char *bufp = usrbuf; while (nleft > 0) {
if ((nread = read(fd, bufp, nleft)) < 0) {
if (errno == EINTR) // Interrupted by sig handler return
nread = 0; // and call read() again
else
return -1; // errno set by read()
}
else if (nread == 0)
break; // EOF
nleft -= nread;
bufp += nread;
}
return (n - nleft); // return >= 0
} /*
* rio_writen - robustly write n bytes (unbuffered)
*/
// rio_writen
ssize_t rio_writen(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nwritten;
char *bufp = usrbuf; while (nleft > 0) {
if ((nwritten = write(fd, bufp, nleft)) <= 0) {
if (errno == EINTR) // Interrupted by sig handler return
nwritten = 0; // and call write() again
else
return -1; // errno set by write()
}
nleft -= nwritten;
bufp += nwritten;
}
return n;
} /*
* rio_read - This is a wrapper for the Unix read() function that
* transfers min(n, rio_cnt) bytes from an internal buffer to a user
* buffer, where n is the number of bytes requested by the user and
* rio_cnt is the number of unread bytes in the internal buffer. On
* entry, rio_read() refills the internal buffer via a call to
* read() if the internal buffer is empty.
*/
/* $begin rio_read */
static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)
{
int cnt; while (rp->rio_cnt <= 0) { /* Refill if buf is empty */
rp->rio_cnt = read(rp->rio_fd, rp->rio_buf,
sizeof(rp->rio_buf));
if (rp->rio_cnt < 0) {
if (errno != EINTR) /* Interrupted by sig handler return */
return -1;
}
else if (rp->rio_cnt == 0) /* EOF */
return 0;
else
rp->rio_bufptr = rp->rio_buf; /* Reset buffer ptr */
} /* Copy min(n, rp->rio_cnt) bytes from internal buf to user buf */
cnt = n;
if (rp->rio_cnt < n)
cnt = rp->rio_cnt;
memcpy(usrbuf, rp->rio_bufptr, cnt);
rp->rio_bufptr += cnt;
rp->rio_cnt -= cnt;
return cnt;
} /*
* rio_readinitb - Associate a descriptor with a read buffer and reset buffer
*/
/* $begin rio_readinitb */
void rio_readinitb(rio_t *rp, int fd)
{
rp->rio_fd = fd;
rp->rio_cnt = 0;
rp->rio_bufptr = rp->rio_buf;
} /*
* rio_readnb - Robustly read n bytes (buffered)
*/
/* $begin rio_readnb */
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nread;
char *bufp = usrbuf; while (nleft > 0) {
if ((nread = rio_read(rp, bufp, nleft)) < 0)
return -1; /* errno set by read() */
else if (nread == 0)
break; /* EOF */
nleft -= nread;
bufp += nread;
}
return (n - nleft); /* return >= 0 */
} /*
* rio_readlineb - robustly read a text line (buffered)
*/
/* $begin rio_readlineb */
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
{
int n, rc;
char c, *bufp = usrbuf; for (n = 1; n < maxlen; n++) {
if ((rc = rio_read(rp, &c, 1)) == 1) {
*bufp++ = c;
if (c == '\n') {
n++;
break;
}
} else if (rc == 0) {
if (n == 1)
return 0; /* EOF, no data read */
else
break; /* EOF, some data was read */
} else
return -1; /* Error */
}
*bufp = 0;
return n-1;
} /**********************************
* Wrappers for robust I/O routines
**********************************/
ssize_t Rio_readn(int fd, void *ptr, size_t nbytes)
{
ssize_t n; if ((n = rio_readn(fd, ptr, nbytes)) < 0)
unix_error("Rio_readn error");
return n;
} void Rio_writen(int fd, void *usrbuf, size_t n)
{
if (rio_writen(fd, usrbuf, n) != n)
unix_error("Rio_writen error");
} void Rio_readinitb(rio_t *rp, int fd)
{
rio_readinitb(rp, fd);
} ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n)
{
ssize_t rc; if ((rc = rio_readnb(rp, usrbuf, n)) < 0)
unix_error("Rio_readnb error");
return rc;
} ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
{
ssize_t rc; if ((rc = rio_readlineb(rp, usrbuf, maxlen)) < 0)
unix_error("Rio_readlineb error");
return rc;
}

TCP 连接函数包装

#include "rio.h"

/********************************
* Client/server helper functions
********************************/
/*
* open_clientfd - open connection to server at <hostname, port>
* and return a socket descriptor ready for reading and writing.
* Returns -1 and sets errno on Unix error.
* Returns -2 and sets h_errno on DNS (gethostbyname) error.
*/
/* $begin open_clientfd */
int open_clientfd(char *hostname, int port)
{
int clientfd;
struct hostent *hp;
struct sockaddr_in serveraddr; if ((clientfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return -1; /* Check errno for cause of error */ /* Fill in the server's IP address and port */
if ((hp = gethostbyname(hostname)) == NULL)
return -2; /* Check h_errno for cause of error */
bzero((char *) &serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
bcopy((char *)hp->h_addr_list[0],
(char *)&serveraddr.sin_addr.s_addr, hp->h_length);
serveraddr.sin_port = htons(port); /* Establish a connection with the server */
if (connect(clientfd, (SA *) &serveraddr, sizeof(serveraddr)) < 0)
return -1;
return clientfd;
} /*
* open_listenfd - open and return a listening socket on port
* Returns -1 and sets errno on Unix error.
*/
/* $begin open_listenfd */
int open_listenfd(int port)
{
int listenfd, optval=1;
struct sockaddr_in serveraddr; /* Create a socket descriptor */
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return -1; /* Eliminates "Address already in use" error from bind */
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval , sizeof(int)) < 0)
return -1; /* Listenfd will be an endpoint for all requests to port
on any IP address for this host */
bzero((char *) &serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons((unsigned short)port);
if (bind(listenfd, (SA *)&serveraddr, sizeof(serveraddr)) < 0)
return -1; /* Make it a listening socket ready to accept connection requests */
if (listen(listenfd, LISTENQ) < 0)
return -1;
return listenfd;
} /******************************************
* Wrappers for the client/server helper routines
******************************************/
int Open_clientfd(char *hostname, int port)
{
int rc; if ((rc = open_clientfd(hostname, port)) < 0) {
if (rc == -1)
unix_error("Open_clientfd Unix error");
else
dns_error("Open_clientfd DNS error");
}
return rc;
} int Open_listenfd(int port)
{
int rc; if ((rc = open_listenfd(port)) < 0)
unix_error("Open_listenfd error");
return rc;
}

错误处理函数 -- 包装

#include "rio.h"

/**************************
* Error-handling functions
**************************/
// Unix-style error
void unix_error(char *msg)
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(0);
} // Posix-style error
void posix_error(int code, char *msg)
{
fprintf(stderr, "%s: %s\n", msg, strerror(code));
exit(0);
} // DNS-style error
void dns_error(char *msg)
{
fprintf(stderr, "%s: DNS error %d\n", msg, h_errno);
exit(0);
} // Application error
void app_error(char *msg)
{
fprintf(stderr, "%s\n", msg);
exit(0);
}

### 参考资料
- 《深入理解计算机系统 原书2th》(CSAPP)

Linux网络IO函数以及TCP连接函数包装的更多相关文章

  1. Linux网络编程二、tcp连接API

    一.服务端 1.创建套接字: int socket(int domain, int type, int protocol); domain:指定协议族,通常选用AF_INET. type:指定sock ...

  2. linux网络编程之shutdown() 与 close()函数详解

    linux网络编程之shutdown() 与 close()函数详解 参考TCPIP网络编程和UNP: shutdown函数不能关闭套接字,只能关闭输入和输出流,然后发送EOF,假设套接字为A,那么这 ...

  3. 【深入浅出Linux网络编程】 “实践 -- TCP & UDP”

    通过上一篇博客的学习,你应该对基于epoll的事件触发机制有所掌握,并且通过阅读sio.c/sio.h应该也学会了如何封装epoll以及如何通过设计令epoll更加实用(用户回调,用户参数). 简单回 ...

  4. Linux配置支持高并发TCP连接(socket最大连接数)

    Linux配置支持高并发TCP连接(socket最大连接数) Linux配置支持高并发TCP连接(socket最大连接数)及优化内核参数 2011-08-09 15:20:58|  分类:LNMP&a ...

  5. Socket-IO 系列(一)Linux 网络 IO 模型

    Socket-IO 系列(一)Linux 网络 IO 模型 一.基本概念 在正式开始讲 Linux IO 模型前,先介绍 5 个基本概念. 1.1 用户空间与内核空间 现在操作系统都是采用虚拟存储器, ...

  6. 转:Linux网络IO并行化技术概览

    转:http://codinginet.com/articles/view/201605-linux_net_parallel?simple=1&from=timeline&isapp ...

  7. Linux网络编程中tcp_server和tcp_client函数的封装

    本文的主要目的是将server套接字和client套接字的获取,做一个简易的封装,使用C语言完成.   tcp_server   服务器端fd的获取主要分为以下几步: 1.创建socket,这一步仅仅 ...

  8. Linux网络编程:基于TCP的程序开发回顾篇《转》

    面向连接的TCP程序设计 基于TCP的程序开发分为服务器端和客户端两部分,常见的核心步骤和流程: 其实按照上面这个流程调用系统API确实可以完全实现应用层程序的开发,一点问题没有.可随着时间的推移,你 ...

  9. 修改Linux内核参数,减少TCP连接中的TIME-WAIT

    一台服务器CPU和内存资源额定有限的情况下,如何提高服务器的性能是作为系统运维的重要工作.要提高Linux系统下的负载能力,当网站发展起来之后,web连接数过多的问题就会日益明显.在节省成本的情况下, ...

随机推荐

  1. (转)使用.NET Reflector 查看Unity引擎里面的DLL文件

    当你查看unity里面API的时候,是不是有时候追踪了一两步就碰到DLL文件走不下去了呢?很是不爽吧. 这种问题我也是经常碰到.这是人家商业引擎不想让你看到底层代码啦,所以着急不得. 不过,今天我终于 ...

  2. python3----生成器generator(yield)

    # 列表推导式a = [i for i in range(100) if not(i % 2) and (i % 3)]print(a)# 字典推导式b = {i: i % 2 == 0 for i ...

  3. leetcode 326 Power of Three (python)

    原题: Given an integer, write a function to determine if it is a power of three. Follow up: Could you ...

  4. nodejs 循环中操作需要同步执行解决方案

    最近用nodejs做了个针对某网站的小爬虫.干坏事得低调对吧,不能同时开太多的网络访问,结果各种回调/循环虐的心力交瘁. 经过了n次的百度\哥哥后终于拼出了自己要的功能.不敢独享分享出来以供大家参考. ...

  5. 网络模型+三次握手+四次挥手+DNS+HTTPS

    网络模型+三次握手+四次挥手+DNS+HTTPS 这篇文章十分精华,所以整理一下: 一.网络模型 OSI七层模型,和TCP/IP五层模型(更为普遍) TCP/IP 协议集: 二.TCP协议(传输层)建 ...

  6. Linux中Oracle的sqlplus下退格和Del键无效的问题解决

    利用rlwrap工具解决方法 1.安装rlwrap和readline库 CentOS下可以用EPEL的yum源直接安装,步骤如下: (1)RHEL/CentOS/SL Linux 6.x 下安装 EP ...

  7. 微信支付 php发送POST请求

    https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=20_1 <_xml> <mch_id>132</mc ...

  8. 三报文握手而不是三次握手 wireshark 封包详细信息 (Packet Details Pane) wireshark与对应的OSI七层模型 TCP包的具体内容 分析TCP三次握手过程

    总结: 1.tcp报文非数据部分4*6字节 2.RFC 973 <计算机网络> 谢希仁 three way (three message) handshake 只是一次握手 同步位SYN. ...

  9. DKLang Translation Editor

    https://yktoo.com/en/software/dklang-traned Features Translation using a dictionary (so-called Trans ...

  10. python学习之路-第六天-一个简单的脚本

    现在有一个需求:把某个目录下的文件备份到指定到另外一个目录下,而且压缩后文件为zip文件 # -*- coding:utf-8 -*- #! /usr/bin/python # Filename:ba ...