Linux IO 读写函数

Linux IO 读取分两种类型,一种是本地文件的读取,一种是网络通信的读取

本地文件的读取函数

对于本地文件的读取,Linux有两种方式,一种是有缓存的,另一种是没有缓存的。

带缓存的文件读取

该类型会在内存开辟一个“缓冲区”,为程序中的每一个文件使用;当执行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”,装满后再从内存“缓冲区”依此读出需要的数据。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”装满后再写入文件。由此可以看出,内存“缓冲区”的大小,影响着实际操作外存的次数,内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件“缓冲区”的大小随机器而定。

主要的函数有:fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind等。

不带缓存的文件读取

依赖于操作系统,通过操作系统的功能对文件进行读写,是系统级的输入输出,它不设文件结构体指针,只能读写二进制文件,但效率高、速度快。主要的函数有:open, close, read, write, getc, getchar, putc, putchar, feof, ferror

两者区别

来源

  1. open是UNIX系统调用函数(包括LINUX等),返回的是文件描述符(File Descriptor),它是文件在文件描述符表里的索引。
  2. fopen是ANSIC标准中的C语言库函数,在不同的系统中应该调用不同的内核api。返回的是一个指向文件结构的指针。

移植性

这一点从上面的来源就可以推断出来,fopen是C标准函数,因此拥有良好的移植性;而open是UNIX系统调用,移植性有限。如windows下相似的功能使用API函数CreateFile

适用范围

  1. open返回文件描述符,而文件描述符是UNIX系统下的一个重要概念,UNIX下的一切设备都是以文件的形式操作。如网络套接字、硬件设备等。当然包括操作普通正规文件(Regular File)。
  2. fopen是用来操纵普通正规文件(Regular File)的。

文件IO层次

如果从文件IO的角度来看,前者属于低级IO函数,后者属于高级IO函数。低级和高级的简单区分标准是:谁离系统内核更近。低级文件IO运行在内核态,高级文件IO运行在用户态。

使用fopen函数,由于在用户态下就有了缓冲,因此进行文件读写操作的时候就减少了用户态和内核态的切换(切换到内核态调用还是需要调用系统调用API:read,write);而使用open函数,在文件读写时则每次都需要进行内核态和用户态的切换;表现为,如果顺序访问文件,fopen系列的函数要比直接调用open系列的函数快;如果随机访问文件则相反。

网络IO的读取

网络IO的读取主要针对的是socket,主要有:

1
2
3
4
5
ssize_t write(int fd, const void*buf,size_t nbytes);
ssize_t read(int fd,void *buf,size_t nbyte)

int recv(int sockfd,void *buf,int len,int flags)
int send(int sockfd,void *buf,int len,int flags)

recv和send与read和write的不同就是函数参数多了一个标志,该标志如果为0的话,那么其作用就相当于read和write函数了。
flag的取值可以为0或是下面的组合:

MSG_DONTROUTE:是send函数使用的标志.这个标志告诉IP.目的主机在本地网络上面,没有必要查找表.这个标志一般用网络诊断和路由程序里面.
MSG_OOB:表示可以接收和发送带外的数据.关于带外数据我们以后会解释的.

MSG_PEEK:是recv函数的使用标志,表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容.这样下次读的时候,仍然是一样的内容.一般在有多个进程读写数据时可以使用这个标志.

MSG_WAITALL是recv函数的使用标志,表示等到所有的信息到达时才返回.使用这个标志的时候recv回一直阻塞,直到指定的条件满足,或者是发生了错误. 1)当读到了指定的字节时,函数正常返回.返回值等于len 2)当读到了文件的结尾时,函数正常返回.返回值小于len 3)当操作发生错误时,返回-1,且设置错误为相应的错误号(errno)

以上四个函数只适合与面向连接的套接字。对于UDP这样的非连接,需要使用下面的函数

1
2
3
4
#include "sys/socket.h"

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

成功时候返回读写字节数,出错时候返回-1.

这2个函数只用于套接口,不能用于普通的I/O读写,参数sockfd则是指明要读写的套接口。
flags用于传入控制信息,一般包括以下几个
MSG_DONTROUTE send可用
MSG_DONWAIT send与recv都可用
MSG_PEEK recv可用
MSG_WAITALL recv可用
MSG_OOB send可用
MSG_EOR send recv可用

返回信息都记录在struct msghdr * msg中。

1
2
3
4
5
6
7
8
9
10
11
struct msghdr {
//协议地址和套接口信息,在非连接的UDP中,发送者要指定对方地址端口,接受方用于的到数据来源,如果不需要的话
//可以设置为NULL(在TCP或者连接的UDP中,一般设置为NULL)。
void * msg_name;
socklen_t msg_namelen;//上面的长度
struct lovec * msg_lov;
ssize_t msg_lovlen;//和readv和writev一样
void * msg_control;
socklen_t msg_controllen;
int msg_flags; //用于返回之前flags的控制信息
}

下面是该函数使用的例子:

下面的源码来自这里

服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define MAXSIZE 100

int main(int argc, char ** argu) {
.......
struct msghdr msg;//初始化struct msghdr
msg.msg_name = NULL; //在tcp中,可以设置为NULL
struct iovec io;//初始化返回数据
io.iov_base = buf; //只用了一个缓冲区
io.iov_len = MAXSIZE; //定义返回数据长度
msg.msg_iov = &io;
msg.msg_iovlen = 1;//只用了一个缓冲区,所以长度为1

...................
ssize_t recv_size = recvmsg(connfd, &msg, 0);
char * temp = msg.msg_iov[0].iov_base;//获取得到的数据
temp[recv_size] = '\0';//为数据末尾添加结束符
printf("get message:%s", temp);

}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define MAXSIZE 100

int main(int argc, char ** argv) {
.................
struct msghdr msg;//初始化发送信息
msg.msg_name = NULL;
struct iovec io;
io.iov_base = send_buff;
io.iov_len = sizeof(send_buff);
msg.msg_iov = &io;
msg.msg_iovlen = 1;

if(argc != 2) {
printf("please input port");
exit(1);
}

ssize_t size = sendmsg(sockfd, &msg, 0);
close(sockfd);
exit(0);
}

这里控制信息都设置成0,主要是初始化返回信息struct msghdr结构。

未连接的UDP套接口
服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "/programe/net/head.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"
#include "sys/wait.h"
#include "sys/select.h"
#include "sys/poll.h"

#define MAXSIZE 100

int main(int argc, char ** argv) {
int sockfd;
struct sockaddr_in serv_socket;
struct sockaddr_in *client_socket = (struct sockaddr_in *) malloc (sizeof(struct sockaddr_in));
char buf[MAXSIZE + 1];

sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&serv_socket, sizeof(serv_socket));
serv_socket.sin_family = AF_INET;
serv_socket.sin_addr.s_addr = htonl(INADDR_ANY);
serv_socket.sin_port = htons(atoi(argv[1]));
bind(sockfd, (struct sockaddr *)&serv_socket, sizeof(serv_socket));

struct msghdr msg;
msg.msg_name = client_socket;
//如果想得到对方的地址和端口,一定要把初始化完毕的内存头指针放入msg之中
msg.msg_namelen = sizeof(struct sockaddr_in);//长度也要指定
struct iovec io;
io.iov_base = buf;
io.iov_len = MAXSIZE;
msg.msg_iov = &io;
msg.msg_iovlen = 1;

ssize_t len = recvmsg(sockfd, &msg, 0);
client_socket = (struct sockaddr_in *)msg.msg_name;
char ip[16];
inet_ntop(AF_INET, &(client_socket->sin_addr), ip, sizeof(ip));
int port = ntohs(client_socket->sin_port);
char * temp = msg.msg_iov[0].iov_base;
temp[len] = '\0';
printf("get message from %s[%d]: %s\n", ip, port, temp);
close(sockfd);
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "/programe/net/head.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "sys/select.h"

#define MAXSIZE 100

int main(int argc, char ** argv) {
int sockfd;
struct sockaddr_in serv_socket;
int maxfdpl;
char send[] = "hello yuna";
if(argc != 2) {
printf("please input port");
exit(1);
}

sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&serv_socket, sizeof(serv_socket));
serv_socket.sin_family = AF_INET;
serv_socket.sin_port = htons(atoi(argv[1]));
inet_pton(AF_INET, "192.168.1.235", &serv_socket.sin_addr);

struct msghdr msg;
msg.msg_name = &serv_socket;
msg.msg_namelen = sizeof(struct sockaddr_in);
struct iovec io;
io.iov_base = send;
io.iov_len = sizeof(send);
msg.msg_iov = &io;
msg.msg_iovlen = 1;

ssize_t send_size = sendmsg(sockfd, &msg, 0);
close(sockfd);
exit(0);
}

sendto和recvfrom

1
2
3
4
5
6
#include <sys/types.h>
#include <sys/socket.h>

int sendto (int s, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);

int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);

对于sendto()函数,成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。

对于recvfrom()函数,成功则返回接收到的字符数,失败则返回-1,错误原因存于errno中。

如果需要在TCP的socket中使用该函数,那么可以直接的对函数最后的两个参数设为NULL。

UDP Server和Client源码实例:

一下源码来自这里
服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define UDP_TEST_PORT 50001

int main(int argC, char* arg[])
{

struct sockaddr_in addr;
int sockfd, len = 0;
int addr_len = sizeof(struct sockaddr_in);
char buffer[256];

/* 建立socket,注意必须是SOCK_DGRAM */
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror ("socket");
exit(1);
}

/* 填写sockaddr_in 结构 */
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(UDP_TEST_PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY) ;// 接收任意IP发来的数据

/* 绑定socket */
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr))<0) {
perror("connect");
exit(1);
}

while(1) {
bzero(buffer, sizeof(buffer));
len = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr *)&addr ,&addr_len);
/* 显示client端的网络地址和收到的字符串消息 */
printf("Received a string from client %s, string is: %s\n",
inet_ntoa(addr.sin_addr), buffer);
/* 将收到的字符串消息返回给client端 */
sendto(sockfd,buffer, len, 0, (struct sockaddr *)&addr, addr_len);
}

return 0;
}

// ----------------------------------------------------------------------------
// End of udp_server.c

UDP 客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define UDP_TEST_PORT 50001
#define UDP_SERVER_IP "127.0.0.1"

int main(int argC, char* arg[])
{

struct sockaddr_in addr;
int sockfd, len = 0;
int addr_len = sizeof(struct sockaddr_in);
char buffer[256];

/* 建立socket,注意必须是SOCK_DGRAM */
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket");
exit(1);
}

/* 填写sockaddr_in*/
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(UDP_TEST_PORT);
addr.sin_addr.s_addr = inet_addr(UDP_SERVER_IP);

while(1) {
bzero(buffer, sizeof(buffer));

printf("Please enter a string to send to server: \n");

/* 从标准输入设备取得字符串*/
len = read(STDIN_FILENO, buffer, sizeof(buffer));

/* 将字符串传送给server端*/
sendto(sockfd, buffer, len, 0, (struct sockaddr *)&addr, addr_len);

/* 接收server端返回的字符串*/
len = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr *)&addr, &addr_len);
printf("Receive from server: %s\n", buffer);
}

return 0;
}

// ----------------------------------------------------------------------------
// End of udp_client.c