网络编程Socket之TCP之read/write

从写一个TCP套接字的write调用成功返回仅仅表示我们可以重新使用原来的应用进程缓冲区,并不代表对端TCP或应用进程已接收到数据。

对端TCP必须确认收到的数据,伴随来自对端的ACK的不断到达,本端TCP至此才能从套接字发送缓冲区中丢弃已确认的数据,TCP必须为已发送的数据保留一个副本,直到它被对端确认为止。

UDP不保存应用进程数据的副本因此无需一个真正的发送缓冲区,write调用成功返回表示所写的数据报或其所有分片已被加入数据链路层的输出队列。

对于read调用(套接字标志为阻塞),如果接收缓冲区中有20字节,请求读100个字节,就会返回20。对于write调用(套接字标志为阻塞),如果请求写100个字节,而发送缓冲区中只有20个字节的空闲位置,那么write会阻塞,直到把100个字节全部交给发送缓冲区才返回,如果write中得套接字标志为非阻塞,则直接返回20,因此我们可以实现自己的readn和writen函数。

每个TCP套接字都有一个发送缓冲区和一个接收缓冲区,每个UDP套接字都有一个接收缓冲区;

先模拟服务端阻塞:

client:

    struct sockaddr_in serverAdd;

    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR);
    serverAdd.sin_port = htons(SERV_PORT);

    connfd = socket(AF_INET, SOCK_STREAM, 0);

    int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (connResult < 0) {
        printf("连接失败,errno = %d\n",errno);
        close(connfd);
        return ;
    }
    else
    {
        printf("连接成功\n");
    }

    ssize_t writeLen;

    char sendMsg[246988] = {0};

    int count = 0;
    while (1)
    {
        count++;
        if (count == 5) {
            exit(0);
        }
        writeLen = write(connfd, sendMsg, sizeof(sendMsg));
        if (writeLen < 0) {
            printf("发送失败\n");
            close(connfd);
            return ;
        }
        else
        {
            printf("发送成功\n");
        }

    }

server:

int main(int argc, const char * argv[])
{

    struct sockaddr_in serverAdd;
    struct sockaddr_in clientAdd;

    bzero(&serverAdd, sizeof(serverAdd));
    serverAdd.sin_family = AF_INET;
    serverAdd.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAdd.sin_port = htons(SERV_PORT);

    socklen_t clientAddrLen;

    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int yes = 1;
    setsockopt(listenfd,
               SOL_SOCKET, SO_REUSEADDR,
               (void *)&yes, sizeof(yes));

    if (listenfd < 0) {
        printf("创建socket失败\n");
        close(listenfd);
        return -1;
    }

    int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
    if (bindResult < 0) {
        close(listenfd);
        printf("绑定端口失败,errno = %d\n",errno);
        return -1;
    }
    else
    {
        printf("绑定端口成功\n");
    }

    listen(listenfd, 20);

    int connfd;
    unsigned char recvMsg[246988];

    clientAddrLen = sizeof(clientAdd);
    connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen);
    if (connfd < 0) {
        close(listenfd);
        printf("连接失败\n");
        return -1;
    }
    else
    {
        printf("连接成功\n");
        int rcvbuf_len;
        socklen_t len = sizeof(rcvbuf_len);

        if( getsockopt( connfd, SOL_SOCKET, SO_RCVBUF, (void *)&rcvbuf_len, &len ) < 0 ){
            perror("getsockopt: ");
        }
        printf("the recv buf len: %d\n", rcvbuf_len );

    }

    ssize_t totalLen = 0;
    while (1)
    {
        sleep(1);
        ssize_t readLen = read(connfd, recvMsg, sizeof(recvMsg));
        printf("readLen:%ld\n",readLen);
        if (readLen < 0) {
            printf("读取失败\n");
            return -1;
        }
        else if (readLen == 0) {
            printf("读取完成-len = %ld\n",totalLen);
            close(listenfd);
            return 0;
        }
        else
        {
            totalLen += readLen;
        }

    }

    close(connfd);

    return 0;
}

先运行服务器,再运行客户端,打印信息如下

client:

连接成功

发送成功

发送成功

发送成功

发送成功

这里调用write时会阻塞,是因为内核从该应用进程的缓冲区中复制所有数据到所写套接字的发送缓冲区;而由于服务器还未处理完数据,没有回复确认给客户端,客户端的套接字发送缓冲区暂时要保留数据,要是数据满了,write就会休眠等待,直到有可用的空间把要写的数据全部写完才返回;

server:

绑定端口成功

连接成功

the recv buf len: 131768

readLen:246988

readLen:246988

readLen:246988

readLen:246988

readLen:0

读取完成-len = 987952

这里read方法读取的长度和套接字接收缓冲区的长度无关

再屏蔽掉服务端的休眠代码,重新运行,服务端打印信息如下

绑定端口成功

连接成功

the recv buf len: 131768

readLen:1448

readLen:1448

......

readLen:1448

readLen:2896

readLen:5792

readLen:10136

readLen:1448

......

readLen:1448

readLen:1040

readLen:0

读取完成-len = 987952

这里read每次读取的时候有多少就读取多少,不会等待读到想要的长度;

参考:

《UNIX Network ProgrammingVolume 1, Third Edition: TheSockets Networking API》

时间: 2024-10-24 16:27:55

网络编程Socket之TCP之read/write的相关文章

java网络编程socket\server\TCP笔记(转)

java网络编程socket\server\TCP笔记(转) 2012-12-14 08:30:04|  分类: Socket |  标签:java  |举报|字号 订阅 1 TCP的开销 a  连接协商三次握手,c->syn->s,s->syn ack->c, c->ack->s b  关闭协商四次握手,c->fin->s, s->ack-c,s->fin->c,c->ack->s c  保持数据有序,响应确认等计算开销 d

网络编程Socket之TCP之close/shutdown详解(续)

接着上一篇网络编程Socket之TCP之close/shutdown详解 现在我们看看对于不同情况的close的返回情况和可能遇到的一些问题: 1.默认操作的close 说明:我们已经知道write操作返回成功只能说明数据已经发送到套接字的发送缓冲区,不能代表对端已经成功收到数据,close的默认返回成功也只是成功发出了一个FIN分节,也不代表对端已经确认 问题1:如果中途网络发生故障,很有可能服务端接收不到这个来自客户端的FIN分节: 问题2:假设服务器忙,那么来自客户端的数据由TCP加入到套

网络编程Socket之TCP之TIME_WAIT状态详解

下面我们用最简单的一对一的客户服务器模型来重现编程中遇到的一些问题: 初学socket的时候在编写socket程序的时候会遇到很多莫名其妙的问题,比如说bind函数返回的常见错误是EADDRINUSE 使用下面的程序重现这个状态: client: int main(int argc, const char * argv[]) { struct sockaddr_in serverAdd; bzero(&serverAdd, sizeof(serverAdd)); serverAdd.sin_fa

网络编程Socket它TCP它TIME_WAIT国家具体解释

下面我们用最简单的一对一的客户server编程模型重现遇到的一些问题: 初学者socket当写作socket名其妙的问题.比方说bind函数返回的常见错误是EADDRINUSE 使用以下的程序重现这个状态: client: int main(int argc, const char * argv[]) { struct sockaddr_in serverAdd; bzero(&serverAdd, sizeof(serverAdd)); serverAdd.sin_family = AF_IN

网络编程Socket之TCP之connect详解

对TCP套接字调用connect会激发三次握手,如下: 客户端是主动打开连接的一端,会发送第一个SYN分节,然后等待确认,此时连接状态为SYN_SENT,当收到服务端的确认后连接建立,状态变为ESTABLISHED: 服务器是被动打开连接的一端,调用listen导致套接字从CLOSED状态变为LISTEN状态,当收到来自客户端的SYN分节以后状态变为SYN_RCVD,然后发送第二个SYN分节,等待客户端的确认,收到客户端的确认以后连接建立,状态变为ESTABLISHED: 三次握手中的两个SYN

二、网络编程-socket之TCP协议开发客户端和服务端通信

知识点:之前讲的udp协议传输数据是不安全的,不可靠不稳定的,tcp协议传输数据安全可靠,因为它们的通讯机制是不一样的.udp是用户数据报传输,也就是直接丢一个数据包给另外一个程序,就好比寄信给别人,信丢了你也不知道,tcp传输需要先和服务端建立连接,当客户端与服务器连接时,服务器会给出应答,我俩连上了,而且数据传过来还会进行一个数据包数量验证,不一致会重新发送,还有其他种种验证,总之保证了数据传输安全可靠   这一章主要介绍使用套接字,编写一个tcp协议客户端和服务端.同样要用到上一章节提到小

网络编程Socket之TCP之close/shutdown详解

close: 当套接字的引用计数为0的时候才会引发TCP的四分组连接终止序列: shutdown: 不用管套接字的引用计数就激发TCP的正常连接终止序列: 这里由一个SO_LINGER套接字选项 struct linger { int l_onoff; /* 0 = off, nozero = on */ int l_linger; /* linger time,POSIX specifies units as seconds */ }; shutdown:SHUT_RD 关闭连接的读这一半,进

网络编程Socket之TCP之select概述

I/O模型: 1.  阻塞式I/O模型 2.  非阻塞式I/O模型:使用fcntl将套接字设置成非阻塞:然后轮询读取数据,这样会耗费大量CPU时间: 3.  I/O复用模型:阻塞在select上: 4.  信号驱动式I/O模型: 5.  异步I/O模型:aio_read,信号直到数据已复制到应用进程缓冲区才产生 select: select的最后一个时间参数: 1.  为空时表示永远等下去: 2.  为具体非0时间时表示不超过该时间返回: 3.  结构体中秒和微秒都为0表示不等待,即轮询: se

Linux程序设计学习笔记----Socket网络编程基础之TCP/IP协议簇

转载请注明出处: ,谢谢! 内容提要 本节主要学习网络通信基础,主要涉及的内容是: TCP/IP协议簇基础:两个模型 IPv4协议基础:IP地址分类与表示,子网掩码等 IP地址转换:点分十进制\二进制 TCP/IP协议簇基础 OSI模型 我们知道计算机网络之中,有各种各样的设备,那么如何实现这些设备的通信呢? 显然是通过标准的通讯协议,但是,整个网络连接的过程相当复杂,包括硬件.软件数据封包与应用程序的互相链接等等,如果想要写一支将联网全部功能都串连在一块的程序,那么当某个小环节出现问题时,整只