08-优雅地断开套接字连接

参考书籍:《TCP/IP》网络编程,作者【韩】尹圣雨

测试环境:Ubuntu 10.10

GCC版本:4.4.5

 

一、单方面断开连接带来的问题

问题:如果主机A发送完数据后,调用close函数断开连接,之后主机A将无法接收主机B传输的数据。

解决办法:只关闭一部分数据交换中使用的流(半关闭)。

 

二、为何需要半关闭

考虑如下情况实现过程:客户端连接到服务器端,服务器端将约定的文件传给客户端,客户端接收到后发送字符串“Thank You”给服务器端。

问题1:服务器端连续向客户端传输文件,而客户端则无法知道需要接收数据到何时。客户端也无法无休止地调用输入函数,因为这样有可能会导致程序阻塞(调用的函数未返回)。

尝试办法:服务器端和客户端约定一个代表文件尾的字符。

 

问题2:约定的文件尾字符不能出现在文件中。

解决办法:服务器端应最后向客户端传递EOF表示文件传输结束。

 

问题3:服务器如何传递EOF?

解决办法:断开输出流时向对方主机传输EOF。

 

问题4:服务器端发送完毕后调用close函数会产生EOF,但是客户端接收到EOF后,再向服务端发送数据时,服务端已断开连接,无法接收“Thank You”!

解决办法:服务器发送完文件后,调用shutdown函数关闭输出流(半关闭)。这样既可以发出EOF,还可以保留输入流(正常接收数据)。

       

三、基于半关闭的文件传输程序

服务器端:

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

#define BUF_SIZE 30
void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int servSock, clientSock;
    FILE* file;
    char buf[BUF_SIZE] = {0};
    int readCount;

    struct sockaddr_in servAddr, clientAddr;
    socklen_t clientAddrSize;

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

    file = fopen("main.c", "rb");
    servSock = socket(PF_INET, SOCK_STREAM, 0);

    if(-1 == servSock)
        errorHandling("socket() error");

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

    if(-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHandling("bind() error");

    if(-1 == listen(servSock, 5))
        errorHandling("listen() error");

    clientAddrSize = sizeof(clientAddr);

    clientSock = accept(servSock, (struct sockaddr*)&clientAddr, &clientAddrSize);
    if(-1 == clientSock)
        errorHandling("accept() error");

    while(1)
    {
        readCount = fread((void*)buf, 1, BUF_SIZE, file);
        if(readCount < BUF_SIZE)
        {
            write(clientSock, buf, readCount);
            break;
        }

        write(clientSock, buf, BUF_SIZE);
    }

    shutdown(clientSock, SHUT_WR);
    read(clientSock, buf, BUF_SIZE);
    printf("Message from client: %s\n", buf);

    fclose(file);
    close(clientSock);
    close(servSock);

    return 0;
}

void errorHandling(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 30
void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int sock;
    FILE* file;

    char buf[BUF_SIZE] = {0};
    int readCount = 0;
    struct sockaddr_in servAddr;

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

    file = fopen("receive.dat", "wb");

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

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

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

    while((readCount = read(sock, buf, BUF_SIZE)) != 0)
        fwrite((void*)buf, 1, readCount, file);

    puts("Received file data");
    write(sock, "Thank you", 10);
    fclose(file);
    close(sock);

    return 0;
}

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

操作:

1)服务器端:gcc main.c -o fileServer.out,运行:./fileServer.out 9190

Message from client: Thank you

2)客户端:gcc main.c -o fileClient.out,运行:./fileClient.out 127.0.0.1 9190

Received file data

分析:

        当服务器读取到的文件小于30个字节时,说明读取到文件尾,使用半关闭函数shutdown关闭了服务器端输出流。服务器端接收到“Thank You”字符,证明了服务器半关闭后,输入流还能正常接收数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值