目录
fdopen(3)函数
使用标准IO发送数据
使用标准IO接收数据
注意
先看一个简单的TCP客户端程序:
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(void){
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1){
perror("socket");
exit(1);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8000);
addr.sin_addr.s_addr = inet_addr("192.168.1.14");
if(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1){
perror("connect");
exit(1);
}
// 准备写入
char message[] = "Hello World!";
char *buf = message;
int ret, len;
len = strlen(message);
while(len != 0 && (ret = write(fd, buf, len)) != 0){
if(ret == -1){
if(errno == EINTR) continue; // 写入时收到信号,可以重新写入
perror("write");
break;
}
len -= ret;
buf += ret;
}
close(fd);
return 0;
}
可以看到,在发送信息时,使用了这样的代码:
char message[] = "Hello World!";
char *buf = message;
int ret, len;
len = strlen(message);
while(len != 0 && (ret = write(fd, buf, len)) != 0){
if(ret == -1){
if(errno == EINTR) continue; // 写入时收到信号,可以重新写入
perror("write");
break;
}
len -= ret;
buf += ret;
}
这是因为,使用write(2)函数时,可能会出现写入不全的情况(部分写),因此,需要通过while循环,确保它能全部写完。
但是,这样的代码太麻烦了,有没有简单的办法?
fdopen(3)函数
我们知道,在标准IO中,有一个fdopen(3)函数:
#include <stdio.h>
FILE *fdopen(int fd, const char *mode);
这个函数将会把一个文件描述符转换成一个文件指针。参数中,fd是文件描述符,mode是操作模式,如"r+"。可以设置为"w"或"w+",但不会清空文件。如果把socket(2)函数返回的文件描述符改成文件指针,就可以通过fprintf等方法直接输出了。
使用标准IO发送数据
我们可以修改代码如下:
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdlib.h>
int main(void){
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1){
perror("socket");
exit(1);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8000);
addr.sin_addr.s_addr = inet_addr("192.168.1.14");
if(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1){
perror("connect");
exit(1);
}
FILE *fp = fdopen(fd, "r+");
fprintf(fp, "Hello World!");
fclose(fp);
return 0;
}
可以看到,采用标准IO,代码就简单了很多。而且,使用标准IO,也可以避免操作底层的时候出现一些问题(如部分写)
我们可以用网络调试助手看一下使用了这种方法是否可以正常发送:
可以看到,它能够正常发送数据,并正常关闭套接字。
使用标准IO接收数据
我们还可以让它能够读取数据:
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdlib.h>
int main(void){
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1){
perror("socket");
exit(1);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8000);
addr.sin_addr.s_addr = inet_addr("192.168.1.14");
if(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1){
perror("connect");
exit(1);
}
FILE *fp = fdopen(fd, "r+");
char buf[1024];
fgets(buf, 1024, fp);
printf("data is %s\n", buf);
fclose(fp);
return 0;
}
使用网络调试助手发送数据(注意要在数据后面加入换行符):
终端输出:
data is Hello Client.
注意
标准IO的设计主要是为了文件读写,将其应用与网络编程并不总是最佳选择。另外需要注意,使用了fdopen(3)函数后,不应直接操作文件描述符(虽然这是合法的),这可能会导致数据竞争等未定义行为。