从网络套接字接收数据(最常见)
这是“接收数据”最典型的应用,通常指从TCP或UDP连接中读取信息。

(图片来源网络,侵删)
核心函数:recv() 和 read()
对于TCP套接字,最常用的函数是 recv()。
#include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数详解:
sockfd: 套接字描述符,这是通过socket()函数创建,并通过connect()(客户端) 或accept()(服务器) 建立连接后得到的文件描述符,它就像一个“通道号”,告诉操作系统你要从哪个连接收数据。buf: 缓冲区,一个指向内存区域的指针,用于存放接收到的数据,你需要提前分配好这个空间。len: 缓冲区长度,告诉操作系统你的缓冲区有多大,最多能接收多少字节的数据。flags: 接收标志,通常设置为0,表示使用默认的接收方式,其他常用标志有:MSG_WAITALL: 等待直到请求的字节数都接收到,或者发生错误/连接关闭。MSG_DONTWAIT: 非阻塞模式,如果数据不可用,函数会立即返回而不是等待。MSG_PEEK: 查看数据但不从套接字缓冲区中移除它,下次recv()时还能读到同样的数据。
返回值:
- 成功:返回实际接收到的字节数。
- 连接关闭:返回
0,这是客户端主动调用close()或服务器主动断开连接的标志。 - 出错:返回
-1,并设置errno来表示具体的错误原因(EAGAIN或EWOULDBLOCK表示非阻塞模式下暂时没有数据)。
一个完整的TCP服务器接收数据示例
下面是一个简单的TCP服务器,它监听端口,接受客户端连接,然后循环接收客户端发来的数据,直到客户端关闭连接。

(图片来源网络,侵删)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // for close()
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h> // for errno
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// 1. 创建套接字文件描述符
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 2. 设置套接字选项,允许地址重用
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; // 监听所有可用的网络接口
address.sin_port = htons(PORT); // 将端口号从主机字节序转换为网络字节序
// 3. 绑定套接字到指定端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 4. 开始监听连接
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d...\n", PORT);
// 5. 接受一个新连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
printf("Client connected. IP: %s, Port: %d\n",
inet_ntoa(address.sin_addr), ntohs(address.sin_port));
// 6. 循环接收数据
int valread;
while ((valread = recv(new_socket, buffer, BUFFER_SIZE, 0)) > 0) {
// 将接收到的数据原样发回给客户端 (回显服务器)
send(new_socket, buffer, valread, 0);
printf("Received %d bytes: %s", valread, buffer);
// 清空缓冲区,为下一次接收做准备
memset(buffer, 0, BUFFER_SIZE);
}
// 检查是否是客户端主动关闭了连接
if (valread == 0) {
printf("Client disconnected.\n");
} else if (valread == -1) {
perror("recv failed");
}
// 7. 关闭套接字
close(new_socket);
close(server_fd);
return 0;
}
从文件接收数据(读取文件)
从文件中读取数据也常被泛称为“接收数据”,核心函数是 fread() 或 read()。
核心函数:fread() (高级I/O,带缓冲)
#include <stdio.h> size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr: 指向存储读取数据的缓冲区。size: 每个数据项的大小(单位是字节)。nmemb: 要读取的数据项的数量。stream:FILE*指针,由fopen()返回。- 返回值:成功返回实际读取到的数据项的数量(不是字节数),如果到达文件末尾或出错,可能返回小于
nmemb的值,甚至0。
核心函数:read() (底层I/O,文件描述符)
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
fd: 文件描述符,由open()返回。buf: 缓冲区。count: 要读取的字节数。- 返回值:成功返回读取的字节数,到达文件末尾返回
0,出错返回-1。
一个从文件读取数据的示例
#include <stdio.h>
#include <stdlib.h>
#define FILENAME "example.txt"
#define BUFFER_SIZE 256
int main() {
FILE *fp;
char buffer[BUFFER_SIZE];
size_t items_read;
// 使用 fopen 打开文件
fp = fopen(FILENAME, "r"); // "r" 表示只读模式
if (fp == NULL) {
perror("Error opening file");
return EXIT_FAILURE;
}
printf("Reading from file: %s\n", FILENAME);
// 循环读取文件,直到文件结束
while ((items_read = fread(buffer, 1, BUFFER_SIZE, fp)) > 0) {
// fwrite 将读取到的数据打印到标准输出
fwrite(buffer, 1, items_read, stdout);
}
// 检查是否是因错误而停止
if (ferror(fp)) {
perror("Error reading file");
}
// 关闭文件
fclose(fp);
printf("\nFile reading complete.\n");
return EXIT_SUCCESS;
}
从串口接收数据
在嵌入式系统或与硬件设备通信时,会用到串口,串口在Linux下被视为一种特殊的文件,因此也使用 read() 函数来读取数据。
**
- 打开串口:使用
open()函数,以读写模式打开设备文件(如/dev/ttyS0或/dev/ttyUSB0)。 - 配置串口:这是最关键的一步,需要使用
termios结构体来设置波特率、数据位、停止位、校验位、流控等。 - 读取数据:使用
read()函数从打开的文件描述符中读取数据。 - 关闭串口:使用
close()函数。
一个伪代码示例(串口配置是重点)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h> // 串口设置的头文件
#include <errno.h>
#define SERIAL_PORT "/dev/ttyUSB0"
#define BAUD_RATE B9600
int setup_serial_port(int fd) {
struct termios options;
// 获取当前串口设置
tcgetattr(fd, &options);
// 设置波特率
cfsetispeed(&options, BAUD_RATE);
cfsetospeed(&options, BAUD_RATE);
// 设置数据位为8位
options.c_cflag &= ~CSIZE; // 清除数据位设置
options.c_cflag |= CS8; // 设置为8位数据位
// 设置无奇偶校验
options.c_cflag &= ~PARENB;
// 设置1位停止位
options.c_cflag &= ~CSTOPB;
// 启用接收
options.c_cflag |= (CLOCAL | CREAD);
// 原始输入模式
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 禁用软件流控
options.c_iflag &= ~(IXON | IXOFF | IXANY);
// 原始输出模式
options.c_oflag &= ~OPOST;
// 设置超时:100ms (10 deciseconds)
options.c_cc[VMIN] = 0; // 不阻塞,有数据就读,没有就返回0
options.c_cc[VTIME] = 1; // 等待0.1秒
// 应用设置
if (tcsetattr(fd, TCSANOW, &options) != 0) {
perror("tcsetattr failed");
return -1;
}
return 0;
}
int main() {
int serial_fd;
char buffer[256];
// 1. 打开串口
serial_fd = open(SERIAL_PORT, O_RDWR);
if (serial_fd < 0) {
perror("Error opening serial port");
return EXIT_FAILURE;
}
// 2. 配置串口
if (setup_serial_port(serial_fd) < 0) {
close(serial_fd);
return EXIT_FAILURE;
}
printf("Serial port %s opened and configured.\n", SERIAL_PORT);
// 3. 循环读取数据
while (1) {
int bytes_read = read(serial_fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0'; // 确保字符串以空字符结尾
printf("Received: %s\n", buffer);
} else if (bytes_read < 0) {
perror("read error");
break;
}
// bytes_read == 0,表示超时,继续循环
}
// 4. 关闭串口
close(serial_fd);
return EXIT_SUCCESS;
}
总结与关键点
| 场景 | 核心函数/方法 | 关键概念 | 关键参数/结构体 |
|---|---|---|---|
| 网络 | recv(), send() |
套接字、TCP/UDP、字节序、IP地址、端口 | socket(), bind(), listen(), accept(), struct sockaddr_in |
| 文件 | fread(), fwrite() |
文件指针、缓冲区、文件结束 | fopen(), fclose(), FILE* |
| 串口 | read(), write() |
设备文件、波特率、数据位、停止位、校验位、流控 | open(), close(), struct termios |
无论哪种“接收数据”,其核心思想都是相似的:
- 建立通道:创建套接字、打开文件或设备。
- 准备缓冲区:在内存中预留一块空间来存放数据。
- 调用读取函数:将通道和缓冲区交给操作系统,让它填充数据。
- 处理返回值:根据返回的字节数判断数据是否成功接收,并处理可能的错误(如连接关闭、文件结束)。
- 清理资源:完成后,关闭通道,释放资源。
希望这个详细的解释能帮助你全面理解C语言中的“receivedata”操作!

(图片来源网络,侵删)
