linux read socket时产生阻塞

本文详细介绍了TCP套接口的阻塞与非阻塞特性,包括发送缓冲区的工作原理及其如何影响write操作的行为。同时,还探讨了read函数在不同情况下的阻塞行为,并解释了如何通过特定标志或函数实现对特定数量数据的读取。

read函数只是一个通用的读文件设备的接口。是否阻塞需要由设备的属性和设定所决定。一般来说,读字符终端、网络的socket描述字,管道文件等,这些文件的缺省read都是阻塞的方式。如果是读磁盘上的文件,一般不会是阻塞方式的。但使用锁和fcntl设置取消文件O_NOBLOCK状态,也会产生阻塞的read效果。


每一个TCP套接口有一个发送缓冲区,可以用SO_SNDBUF套接口选项来改变这个缓冲区的大小。当应用进程调用 write时,内核从应用进程的缓冲区中拷贝所有数据到套接口的发送缓冲区。如果套接口的发送缓冲区容不下应用程序的所有数据(或是应用进程的缓冲区大于套接口发送缓冲区,或是套接口发送缓冲区还有其他数据),应用进程将被挂起(睡眠)。这里假设套接口是阻塞的,这是通常的缺省设置。内核将不从write系统调用返回,直到应用进程缓冲区中的所有数据都拷贝到套接口发送缓冲区。因此从写一个TCP套接口的write调用成功返回仅仅表示我们可以重新使用应用进程的缓冲区。它并不告诉我们对端的 TCP或应用进程已经接收了数据。

  TCP取套接口发送缓冲区的数据并把它发送给对端TCP,其过程基于TCP数据传输的所有规则。对端TCP必须确认收到的数据,只有收到对端的ACK,本端TCP才能删除套接口发送缓冲区中已经确认的数据。TCP必须保留数据拷贝直到对端确认为止。

  1 输入操作: read、readv、recv、recvfrom、recvmsg

  如果某个进程对一个阻塞的TCP套接口调用这些输入函数之一,而且该套接口的接收缓冲区中没有数据可读,该进程将被投入睡眠,直到到达一些数据。既然 TCP是字节流协议,该进程的唤醒就是只要到达一些数据:这些数据既可能是单个字节,也可以是一个完整的TCP分节中的数据。如果想等到某个固定数目的数据可读为止,可以调用readn函数,或者指定MSG_WAITALL标志。

  既然UDP是数据报协议,如果一个阻塞的UDP套接口的接收缓冲区为空,对它调用输入函数的进程将被投入睡眠,直到到达一个UDP数据报。

  对于非阻塞的套接口,如果输入操作不能被满足(对于TCP套接口即至少有一个字节的数据可读,对于UDP套接口即有一个完整的数据报可读),相应调用将立即返回一个EWOULDBLOCK错误。

  2 输出操作:write、writev、send、sendto、sendmsg

  对于一个TCP套接口,内核将从应用进程的缓冲区到该套接口的发送缓冲区拷贝数据。对于阻塞的套接口,如果其发送缓冲区中没有空间,进程将被投入睡眠,直到有空间为止。

### Linux Socket `read` 函数使用方法 在 Linux 环境下,`read` 是一种基础的文件操作函数,它可以用来从文件描述符(如套接字)中取数据。对于基于套接字的应用程序来说,`read` 可以被用于接收来自远程主机的数据流。 #### 基础语法 以下是 `read` 的标准定义: ```c ssize_t read(int fd, void *buf, size_t count); ``` - **fd**: 文件描述符,表示要从中取数据的目标资源,在这里是指向已建立连接的套接字。 - **buf**: 数据缓冲区指针,存储所到的内容。 - **count**: 要取的最大字节数。 成功执行返回实际取的字节数;如果到达文件结束或者没有更多数据,则可能返回零;发生错误则会返回负值并设置 errno[^2]。 #### 示例代码 下面是一个简单的例子展示如何利用 `read` 来获取通过 TCP 连接发送过来的信息: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* For close(), read() */ #include <arpa/inet.h> /* For sockaddr_in */ #define BUFFER_SIZE 1024 int main(void){ int sockfd; struct sockaddr_in server_addr; char buffer[BUFFER_SIZE]; // Create a socket. if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ){ perror("Socket creation error"); exit(EXIT_FAILURE); } memset(&server_addr, '0', sizeof(server_addr)); server_addr.sin_family = AF_INET; // IPv4 protocol family server_addr.sin_port = htons(8080); // Port number to connect on the remote host server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // IP address of the target machine // Connect to the server at specified port and ip-address if (connect(sockfd , (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0 ) { perror("Connection Failed "); exit(EXIT_FAILURE); } printf("Connected successfully.\n"); ssize_t bytes_received = read(sockfd, buffer, BUFFER_SIZE - 1); if(bytes_received > 0){ buffer[bytes_received] = '\0'; // Null terminate string after reading data into it printf("Message from Server : %s\n",buffer ); }else{ perror("Read failed."); } close(sockfd); return EXIT_SUCCESS; } ``` 上述示例展示了如何创建一个客户端应用程序来尝试与本地服务器通信,并使用 `read()` 接收消息。 #### 注意事项 当调用 `read` 对于非阻塞型套接字需要注意其行为特性可能会有所同。另外还需要考虑超机制以及异常情况下的处理逻辑等问题[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值