socket缓冲区

socket缓冲区

每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。

write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。

TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。

read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。
在这里插入图片描述

这些I/O缓冲区特性可整理如下:

  • I/O缓冲区在每个TCP套接字中单独存在;
  • I/O缓冲区在创建套接字时自动生成;
  • 即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
  • 关闭套接字将丢失输入缓冲区中的数据。(问题:为啥部分代码在close之前,要将缓冲区数据读完?)

阻塞模式

对于TCP套接字(默认情况下),当使用 write()/send() 发送数据时:

  1. 首先会检查缓冲区,如果缓冲区的可用空间长度小于要发送的数据,那么 write()/send() 会被阻塞(暂停执行),直到缓冲区中的数据被发送到目标机器,腾出足够的空间,才唤醒 write()/send() 函数继续写入数据。

  2. 如果TCP协议正在向网络发送数据,那么输出缓冲区会被锁定,不允许写入,write()/send() 也会被阻塞,直到数据发送完毕缓冲区解锁,write()/send() 才会被唤醒。

  3. 如果要写入的数据大于缓冲区的最大长度,那么将分批写入。

  4. 直到所有数据被写入缓冲区 write()/send() 才能返回。

当使用 read()/recv() 读取数据时:

  1. 首先会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来。

  2. 如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到有 read()/recv() 函数再次读取。

  3. 直到读取到数据后 read()/recv() 函数才会返回,否则就一直被阻塞。

这就是TCP套接字的阻塞模式。所谓阻塞,就是上一步动作没有完成,下一步动作将暂停,直到上一步动作完成后才能继续,以保持同步性。

TCP套接字默认情况下是阻塞模式,也是最常用的。当然你也可以更改为非阻塞模式。

关于socket阻塞与非阻塞情况下的recv、send、read、write返回值
https://blog.csdn.net/mayue_web/article/details/83018112

阻塞套接字+系统默认API,比如read/recv/send/write带来的问题:线程阻塞

socket缓冲区大小查看与修改

系统限制

root@imx6qdlsabresd:/app/test# cat /proc/sys/net/core/rmem_max
163840
root@imx6qdlsabresd:/app/test# cat /proc/sys/net/core/wmem_max
163840
root@imx6qdlsabresd:/app/test# cat /proc/sys/net/core/rmem_default
163840
root@imx6qdlsabresd:/app/test# cat /proc/sys/net/core/wmem_default
163840

rmem_max:一个socket的读缓冲区可由程序设置的最大值,单位字节;
wmem_max:一个socket的写缓冲区可由程序设置的最大值,单位字节;
rmem_default:一个socket的被创建出来时,默认的读缓冲区大小,单位字节;
wmem_default:一个socket的被创建出来时,默认的写缓冲区大小,单位字节;

程序设置

注意:系统对缓冲区有范围限制,最小值 < 缓冲区大小 < 最大值。

示例:分别查看TCP和UDP的发送、接收缓冲区大小

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>

int main(int argc, char **argv)
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		printf("create socket error=%d(%s)!!!\n", errno, strerror(errno));
		goto error;
	}
	// 查看系统默认的socket接收缓冲区大小
	int defRcvBufSize = -1;
	socklen_t optlen = sizeof(defRcvBufSize);
	if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &defRcvBufSize, &optlen) < 0)
	{
		printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
		goto error;
	}
	printf("OS default udp socket recv buff size is: %d\n", defRcvBufSize);
	if (getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &defRcvBufSize, &optlen) < 0)
	{
		printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
		goto error;
	}
	printf("OS default udp socket send buff size is: %d\n", defRcvBufSize);

	int tcpSockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (tcpSockfd < 0)
	{
		printf("create socket error=%d(%s)!!!\n", errno, strerror(errno));
		goto error;
	}
	// 查看系统默认的socket接收缓冲区大小
	int defTcpRcvBufSize = -1;
	socklen_t optlen2 = sizeof(defTcpRcvBufSize);
	if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &defTcpRcvBufSize, &optlen2) < 0)
	{
		printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
		goto error;
	}
	printf("OS default tcp socket recv buff size is: %d\n", defTcpRcvBufSize);
	if (getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &defTcpRcvBufSize, &optlen2) < 0)
	{
		printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
		goto error;
	}
	printf("OS default tcp socket send buff size is: %d\n", defTcpRcvBufSize);

	close(sockfd);
	close(tcpSockfd);

	exit(0);

error:
	if (sockfd >= 0)
		close(sockfd);
	exit(1);
}

运行结果:

OS default udp socket recv buff size is: 163840
OS default udp socket send buff size is: 163840
OS default tcp socket recv buff size is: 163840
OS default tcp socket send buff size is: 163840

示例:获取和设置UDP SOCKET接收缓冲区大小

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>

int main(int argc, char **argv)
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		printf("create socket error=%d(%s)!!!\n", errno, strerror(errno));
		goto error;
	}
	// 查看系统默认的socket接收缓冲区大小
	int defRcvBufSize = -1;
	socklen_t optlen = sizeof(defRcvBufSize);
	if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &defRcvBufSize, &optlen) < 0)
	{
		printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
		goto error;
	}
	printf("OS default udp socket recv buff size is: %d\n", defRcvBufSize);

	// 设置UDP SOCKET接收缓冲区大小
	int rcvBufSize = 1024*1024*2;
	printf("you want to set udp socket recv buff size to %d\n", rcvBufSize);
	optlen = sizeof(rcvBufSize);
	if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvBufSize, optlen) < 0)
	{
		printf("setsockopt error=%d(%s)!!!\n", errno, strerror(errno));
		goto error;
	}
	printf("set udp socket(%d) recv buff size to %d OK!!!\n", sockfd, rcvBufSize);

	// 查看当前UDP SOCKET接收缓冲区大小
	int curRcvBufSize = -1;
	optlen = sizeof(curRcvBufSize);
	if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &curRcvBufSize, &optlen) < 0)
	{
		printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
		goto error;
	}
	printf("OS current udp socket(%d) recv buff size is: %d\n", sockfd, curRcvBufSize);
	close(sockfd);
	exit(0);

error:
	if (sockfd >= 0)
		close(sockfd);
	exit(1);
}

运行结果:

root@imx6qdlsabresd:/app/test# ./test
OS default udp socket recv buff size is: 163840
you want to set udp socket recv buff size to 2097152
set udp socket(3) recv buff size to 2097152 OK!!!
OS current udp socket(3) recv buff size is: 327680

问题:通过setsockopt设置SO_SNDBUF、SO_RCVBUF这连个默认缓冲区的值,再用getsockopt获取设置的值,发现返回值是设置值的两倍?
https://blog.csdn.net/weixin_28785509/article/details/116689163

对方socket缓冲区大小影响

场景一:对端是TCP Server,建立连接后,一直不读数据。Client端一直循环发送数据,每包10KB,阻塞send后面会卡死,非阻塞后面会发送失败。

tcp_client.c

//
// Created by zyc on 2022/5/5.
//

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>

#define SERVER_PORT 8111
#define MESSAGE_LENGTH 10240

int main(){
    int ret = -1;
    int socket_fd;

    //server addr
    struct sockaddr_in serverAddr;

    char sendbuf[MESSAGE_LENGTH];
    char recvbuf[MESSAGE_LENGTH];

    int data_len;

    if((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket");
        return 1;
    }

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);

    //inet_addr()函数,将点分十进制IP转换成网络字节序IP
//    serverAddr.sin_addr.s_addr = inet_addr("39.105.185.198");
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if(connect(socket_fd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
    {
        perror("connect");
        return 1;
    }
    printf("success to connect server...\n");
	
	// 查看系统默认的socket接收缓冲区大小
	int defTcpRcvBufSize = -1;
	socklen_t optlen2 = sizeof(defTcpRcvBufSize);
	if (getsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &defTcpRcvBufSize, &optlen2) < 0)
	{
		printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
		//goto error;
	}
	printf("OS default tcp socket recv buff size is: %d\n", defTcpRcvBufSize);
	if (getsockopt(socket_fd, SOL_SOCKET, SO_SNDBUF, &defTcpRcvBufSize, &optlen2) < 0)
	{
		printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
		//goto error;
	}
	printf("OS default tcp socket send buff size is: %d\n", defTcpRcvBufSize);

	int num = 0;
    while(1)
    {
        num += MESSAGE_LENGTH;
        memset(sendbuf, 0, MESSAGE_LENGTH);

        printf("<<<<send message, num: %d\n", num);
        memcpy(sendbuf,"123456",strlen("sendbuf"));

       // gets(sendbuf);
        //printf("\n");
        ret = send(socket_fd, sendbuf, MESSAGE_LENGTH, 0);
        if(ret <= 0 ){
            printf("the connection is disconnection!\n");
            break;
        }

        /*if(strcmp(sendbuf, "quit") == 0){
            break;
        }

        printf(">>> echo message:");

        recvbuf[0] = '\0';
        data_len = recv(socket_fd, recvbuf, MESSAGE_LENGTH, 0);

        recvbuf[data_len] = '\0';

        printf("%s\n", recvbuf);*/
		usleep(10000);
    }
    close(socket_fd);
    return 0;
}

tcp_server.c

//
// Created by zyc on 2022/5/5.
//


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#define MESSAGE_SIZE 1024
#define PORT 8111
int main(){
    int ret = 0;

    int socket_fd = -1;
    int accept_fd = -1;

    int curpos = 0;
    int backlog = 10;
    int flag = 1;

    char in_buf[MESSAGE_SIZE] = {0,};

    struct sockaddr_in local_addr, remote_addr;

    //create a tcp socket
    socket_fd = socket(AF_INET,SOCK_STREAM,0);

    if (socket_fd==-1){
        perror("create socket error");
        exit(1);
    }

    //set option of socket
    ret = setsockopt(socket_fd,SOL_SOCKET,SO_REUSEADDR,
                     &flag,sizeof(flag));
    if ( ret == -1 ){
        perror("setsockopt error");
    }

    //set socket address
    local_addr.sin_family = AF_INET;
    local_addr.sin_port = htons(PORT);
    local_addr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(local_addr.sin_zero),8);


    //bind socket
    ret = bind(socket_fd,(struct  sockaddr*)&local_addr,sizeof(struct sockaddr_in));
    if(ret == -1 ) {
        perror("bind error");
        exit(1);
    }

    ret = listen(socket_fd,backlog);
    if ( ret == -1 ){
        perror("listen error");
        exit(1);
    }

    //loop
    for(;;){
        int addr_len = sizeof( struct sockaddr_in );
        //accept an new connection
        accept_fd = accept( socket_fd, (struct sockaddr *)&remote_addr, &addr_len );
        
		// 查看系统默认的socket接收缓冲区大小
		int defTcpRcvBufSize = -1;
		socklen_t optlen2 = sizeof(defTcpRcvBufSize);
		if (getsockopt(accept_fd, SOL_SOCKET, SO_RCVBUF, &defTcpRcvBufSize, &optlen2) < 0)
		{
			printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
			//goto error;
		}
		printf("OS default tcp socket recv buff size is: %d\n", defTcpRcvBufSize);
		if (getsockopt(accept_fd, SOL_SOCKET, SO_SNDBUF, &defTcpRcvBufSize, &optlen2) < 0)
		{
			printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
			//goto error;
		}
		printf("OS default tcp socket send buff size is: %d\n", defTcpRcvBufSize);
        for(;;){
            memset(in_buf, 0, MESSAGE_SIZE);

            //receive network data and print it
          /*  ret = recv( accept_fd ,(void*)in_buf ,MESSAGE_SIZE ,0 );
            if(ret == 0 ){
                break;
            }*/
            printf( "receive message:%s\n", in_buf );
           // send(accept_fd, (void*)in_buf, MESSAGE_SIZE, 0);
           sleep(1);
        }
        printf("close client connection...\n");
        close(accept_fd);
    }

    printf("quit server...\n");
    close(socket_fd);

    return 0;
}

运行结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

TCP Client 发送缓冲区 2626560 接收缓冲区 1061808 应发数据 3584000 已发成功 979456
发送缓冲区剩余数据 2602424 剩余2120没发送 TCP Server 发送缓冲区 2626560 接收缓冲区 1062000
接收缓冲区数据 979456 缓冲区剩余 3584000 != 2602424 + 979456

为什么在linux下socket发送缓冲区没有写满,send就返回错误(非阻塞)/卡死(阻塞)
又看了一下内核源码以及一些资料,才看出来一点头绪。在linux下tcp通信中使用SIOCOUTQ返回的值只是反映了tcp发送缓冲区中未发送的用户数据的大小;而tcp发送缓冲区中存放的数据还需要包含分片信息,长度要大于用户数据。所以虽然SIOCOUTQ返回值小于tcp的发送缓冲区大小,但是在内部其实已经没用空闲空间可用了。这时再送send(非阻塞)发送数据会返回错误。

https://bbs.csdn.net/topics/391856216

参考资料

socket套接字及缓冲区详解
https://blog.csdn.net/daaikuaichuan/article/details/83061726

Socket缓冲区大小修改与系统设置
https://www.cnblogs.com/lidabo/p/13936231.html

socket缓冲区以及阻塞模式详解
http://c.biancheng.net/view/2349.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值