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

标准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在同一进程中复制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值