您的位置:首页 > 其它

套接字与标准IO及IO流分离

2017-01-06 08:05 155 查看
标准I/O的优点

1、具有良好的可移植性(遵从ANSI C标准)

2、可以利用缓冲避免频繁的系统调用从而提高性能

 

使用标准I/O编写操作套接字时的缺点

1、  可能频繁调用fflush函数(保证I/O缓冲中的数据及时进入套接字输出缓冲)

2、  需要将创建套接字时返回的文件描述符转化为FILE指针

 

将文件描述符转化为FILE指针

FILE *fdopen(int fildes, const char *mode); //成功返回转换的FILE结构体指针,失败返回NULL
参数一:需要转换的文件描述符

参数二:转化后的FILE结构体指针的模式(同fopen函数的第二个参数)

 

将FILE结构体指针转化为文件描述符

int fileno(FILE *stream); //成功返回转化的文件描述符,失败返回-1
参数:需要转化的FILE结构体指针

 

I/O流分离:利用函数fdopen创建读模式指针与写模式指针,分离输入/输出工具(通过区分FILE指针的方式分离I/O流可以提高I/O缓冲性能)

实例代码:

服务器端:#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(const char *message);

int main(int argc, const char * argv[])
{
int serv_sock, clnt_sock;
FILE *readfp;
FILE *writefp;

struct sockaddr_in serv_adr, clnt_adr;
socklen_t clnt_adr_sz;
char buf[BUF_SIZE] = {0, };

if( 2 != argc )
{
printf("Usage: %s <port> \n", argv[0]);
exit(1);
}

serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if( -1 == serv_sock )
error_handling("socket() error");

memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));

if( -1 == bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) )
error_handling("bind() error");

if( -1 == listen(serv_sock, 5) )
error_handling("listen() error");

clnt_adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);

//通过将描述符转化为读/写模式FILE结构指针,实现I/O分离。通过FILE结构体指针像使用文件一样使用套接字(linux下一切皆是文件)
readfp = fdopen(clnt_sock, "r");
writefp = fdopen(dup(clnt_sock), "w"); //dup复制文件描述符,通过复制出的文件描述符能访问同一文件或套接字
// writefp = fdopen(clnt_sock, "w"); //可以注释上一行使用这一行,同样是分离I/O流,不同的是,这样使用的话下面的代码中的fclose(writefp)也该注释起来,因为close()会关闭参数所指文件,也就关闭了writefp指针相关文件描述符,由于这个文件描述符没有被dup,是唯一的,因此套接字也会被关闭,这样的话fclose(writefp)之后的fgets()将不能正常工作

fputs("FROM SERVER: Hi~ client? \n", writefp);
fputs("I love all of the world \n", writefp);
fputs("You are awesome! \n", writefp);
fflush(writefp); //将标准I/O缓冲中的内容刷新到writefp所指处

shutdown(fileno(writefp), SHUT_WR); //关闭套接字输出流,并发送EOF(无论复制了多少个描述符,调用shutdown均半关闭且发送EOF)
fclose(writefp); //关闭dup复制出的文件描述符所转换的FILE指针相关的文件描述符,这样只是间接关闭了dup复制出来的文件描述符,由于还存在原来的那个文件描述符(被dup复制的文件描述符),所以套接字并不会被关闭,下面fgets()正常工作

fgets(buf, sizeof(buf), readfp);
fputs(buf, stdout);
fclose(readfp); //关闭readfp相关的文件描述符(剩下的那个唯一的文件描述符),这样也就关闭了套接字
//注:1、通过fclose()关闭了套接字后无需再使用close()去关闭,且fclose关闭套接字后套接字的文件描述符已经没有意义
// 2、shutdown()只能关闭流并发送EOF,并不能让操作系统销毁套接字,所以要关闭套接字是需要使用fclose()或close()直接或间接
//的关闭唯一的文件描述符
return 0;
}

void error_handling(const char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(const char *message);

int main(int argc, const char * argv[]) {
int sock;
char buf[BUF_SIZE];
struct sockaddr_in serv_adr;
FILE *readfp;
FILE *writefp;

if( 3 != argc )
{
printf("Usage: %s <IP> <port> \n", argv[0]);
exit(1);
}

sock = socket(PF_INET, SOCK_STREAM, 0);
if( -1 == sock )
error_handling("socket() error");

memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
serv_adr.sin_port = htons(atoi(argv[2]));

if( -1 == connect(sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) )
error_handling("connect() error");

readfp = fdopen(sock, "r");
writefp = fdopen(sock, "w");

while (1)
{
if (fgets(buf, sizeof(buf), readfp) == NULL) //fgets()收到EOF时返回NULL
break;
fputs(buf, stdout);
fflush(stdout);
}

fputs("FROM CLIENT: Thank you! \n", writefp);
fflush(writefp);

fclose(writefp);
fclose(readfp);
return 0;
}

void error_handling(const char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}

 

文件描述符的复制

int dup(int fildes);
int dup2(int fildes, int fildes2);

成功返回复制出来的文件描述符,失败返回-1

参数fildes为需要被复制的文件描述符

参数fildes2指定复制出来的文件描述符的描述符号(指定的描述符号需大于等于0且小于进程能生成的最大描述符值)

注:1、复制出来的文件描述符与被复制的文件描述符指向同一文件,即使用复制出来的文件描述符与使用被复制的文件描述符是一样的

2、fork在子进程中复制主进程中的文件描述符,dup与dup2在同一进程中复制
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: