8.4 基于TCP实现文件传输
基于TCP协议,实现文件传输。约定规则:先传输一个结构体,里面含有“文件名,文件大小”
1
Client:
open文件,得到文件状态(大小),构造结构体,发送结构体。
循环read文件,发送网络数据。
2
Server:
读网络数据,得到结构体,创建文件。
循环read网络数据,写文件,数量足够就关闭文件。
8.4.1 获取文件信息
本节源码位于如下目录:
关键代码:
/* ./file_stat 1.txt */
int main(int argc, char **argv)
{
struct stat statbuf;
if (argc != 2)
{
printf("Usage: %s
\n" , argv[0]);return -1;
}
/* 1. open */
int fd = open(argv[1], O_RDONLY);
if (-1 == fd)
{
printf("can't open %s\n", argv[1]);
return -1;
}
/* 2. fstat */
if (fstat(fd, &statbuf) == -1)
{
printf("can't get stat of %s\n", argv[1]);
return -1;
}
/* 3. printf */
printf("file size = %d\n", statbuf.st_size);
return 0;
}
实验方法:
gcc -o file_stat file_stat.c
echo 123 > 1.txt
./file_stat 1.txt
8.4.2 本地读文件、写文件
本节源码位于如下目录:
关键代码:
/* 4. 创建目标文件 */
int fd2 = open(argv[2], O_CREAT|O_WRONLY|O_TRUNC);
if (-1 == fd2)
{
printf("can't create %s\n", argv[2]);
return -1;
}
while (1)
{
/* 读原文件 */
unsigned char c;
if (read(fd, &c, 1) != 1)
break;
/* 写目标文件 */
if (write(fd2, &c, 1) != 1)
break;
}
上机实验:
编译程序: gcc -o mycp mycp.c
生成大的测试文件:dd if=/dev/zero of=tmp.txt bs=1024 count=10240
测试:date ; ./mycp tmp.txt tmp6.txt ; date
8.4.3 实现网络传输:传输文件信息
本节源码位于如下目录:
06_源码\8.4.2_本地读文件、写文件
源码为“netcp1_info”
netcp程序:
28 int main(int argc, char **argv)
29 {
30 int iSocketClient;
31 struct sockaddr_in tSocketServerAddr;
32
33 int iRet;
34 unsigned char ucSendBuf[1000];
35 int iSendLen;
36
37 if (argc != 4)
38 {
39 printf("Usage:\n");
40 printf("%s
, argv[0]); \n" 41 return -1;
42 }
43
44 iSocketClient = socket(AF_INET, SOCK_STREAM, 0);// 创建一个套接字
45
46 tSocketServerAddr.sin_family = AF_INET;
47 tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short*/
48 //tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
49 if (0 == inet_aton(argv[2], &tSocketServerAddr.sin_addr))
50 {
51 printf("invalid server_ip\n");
52 return -1;
53 }
54 memset(tSocketServerAddr.sin_zero, 0, 8);
55
56
57 iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));// 尝试连接到服务器
58 if (-1 == iRet)
59 {
60 printf("connect error!\n");
61 return -1;
62 }
63
64 /* 获得文件信息 */
65 struct stat statbuf;
66
67 int fd = open(argv[1], O_RDONLY);// 打开第一个参数指定的本地文件
68 if (-1 == fd)
69 {
70 printf("can't open %s\n", argv[1]);
71 return -1;
72 }
73
74 if (fstat(fd, &statbuf) == -1)// 获取文件信息(大小)
75 {
76 printf("can't get stat of %s\n", argv[1]);
77 return -1;
78 }
79
80 /* 发送文件信息 */
81 struct netcp_info info;
82 info.len = statbuf.st_size;
83 strcpy(info.name, argv[3]);
84 //将 info 结构发送到服务器
85 iSendLen = send(iSocketClient, &info, sizeof(info), 0);
86 if (iSendLen <= 0)
87 {
88 close(iSocketClient);
89 return -1;
90 }
91 else
92 {
93 printf("send %d datas, %ld\n", iSendLen, sizeof(info));
94 }
95
96
97 return 0;
98 }
server程序:
43 signal(SIGCHLD,SIG_IGN);// 忽略子进程结束信号,防止出现僵尸进程。
44
45 iSocketServer = socket(AF_INET, SOCK_STREAM, 0);//创建一个 IPv4 的 TCP 套接字
46 if (-1 == iSocketServer)
47 {
48 printf("socket error!\n");
49 return -1;
50 }
51 //结构体设置为接受任何 IP 的连接请求
52 tSocketServerAddr.sin_family = AF_INET;
53 tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short
*/
54 tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
55 memset(tSocketServerAddr.sin_zero, 0, 8);
56
57 iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));// 将套接字与特定的 IP 地址和端口号绑定
58 if (-1 == iRet)
59 {
60 printf("bind error!\n");
61 return -1;
62 }
63
64 iRet = listen(iSocketServer, BACKLOG);// 函数使服务器的套接字进入监听状态
65 if (-1 == iRet)
66 {
67 printf("listen error!\n");
68 return -1;
69 }
70
71 while (1) //服务器将持续运行,等待客户端的连接请求
72 {
73 iAddrLen = sizeof(struct sockaddr);
74 iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);// 等待客户端的连接请求,并创建一个新的套接字用于与客户端通信
75 if (-1 != iSocketClient)
76 {
77 iClientNum++;
78 printf("Get connect from client %d : %s\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
79 if (!fork())//创建子进程来处理客户端请求,父进程继续监听其他客户端的连接请求
80 {
81 /* 读 100 字节的文件信息 */
82 struct netcp_info info;
83 //收客户端发送的文件信息,包括文件长度和文件名
84 iRecvLen = recv(iSocketClient, &info, sizeof(info), 0);
85 if (iRecvLen <= 0)
86 {
87 close(iSocketClient);
88 return -1;
89 }
90 else
91 {
92 printf("Get file info From Client %d: file len =%ld, file name = %s\n", iClientNum, info.len, info.name);
93 }
94
95 }
96 }
97 }
编译:
source /opt/remi-sdk/environment-setup-aarch64-poky-linux
$CC netcp.c -o netcp
$CC server.c -o server
假设设置开发板的IP为:192.168.5.9,上传程序到开发板上。
scp ./server root@192.168.5.9:/mnt/
scp ./netcp root@192.168.5.9:/mnt/
测试:
进入/mnt目录运行程序:
root@myir-remi-1g:~
root@myir-remi-1g:/mnt
server netcp
在运行程序前,请先创建txt文件,写入任意数据至文件中,用于后续文件传输。
root@myir-remi-1g:/mnt
root@myir-remi-1g:/mnt
后台运行服务程序,运行客户端程序进行文件信息传输。
root@myir-remi-1g:/mnt# ./server &
root@myir-remi-1g:/mnt# ./netcp 1.txt 127.0.0.1 2.txt
Get connect from client 0 : 127.0.0.1
send 104 datas, 104
Get file info From Client 0: file len = 4, file name = 2.txt
运行程序后,可以看到终端输出文件信息内容。
注意:如果修改server.c,再次编译、运行前,先关闭之前的sever程序。命令为:killall server。
8.4.4 实现网络传输:传输文件数据
本节源码位于如下目录:
源码为“netcp2_info”
netcp程序:
80 /* 发送文件信息 */
81 struct netcp_info info;
82 info.len = statbuf.st_size;
83 strcpy(info.name, argv[3]);
84
85 iSendLen = send(iSocketClient, &info, sizeof(info), 0);//发送文件信息
86 if (iSendLen <= 0)
87 {
88 close(iSocketClient);
89 return -1;
90 }
91 else
92 {
93 printf("send %d datas, %ld\n", iSendLen, sizeof(info));
94 }
95
96 /* 传输数据 */
97 unsigned char buf[1024];
98 uint64_t total_len = 0;
99 while (1)
100 {
101 /* 读原文件 */
102 //unsigned char c;
103 //if (read(fd, &c, 1) != 1)
104 // break;
105 int len;
106 len = read(fd, buf, 1024);
107 if (len <= 0)
108 break;
109 else
110 {
111 /* 通过网络发送给服务器 */
112 iSendLen = send(iSocketClient, buf, len, 0);
113 if (iSendLen != len)
114 {
115 close(iSocketClient);
116 return -1;
117 }
118 //printf("send ok %d\n", iSendLen);
119
120 }
121 }
122 return 0;
123 }
server程序:
72 while (1)
73 {
74 iAddrLen = sizeof(struct sockaddr);
75 iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
76 if (-1 != iSocketClient)
77 {
78 iClientNum++;
79 printf("Get connect from client %d : %s\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
80 if (!fork())
81 {
82 /* 读 100 字节的文件信息 */
83 struct netcp_info info;
84
85 iRecvLen = recv(iSocketClient, &info, sizeof(info), 0);
86 if (iRecvLen <= 0)
87 {
88 close(iSocketClient);
89 return -1;
90 }
91 else
92 {
93 printf("Get file info From Client %d: file len =%ld, file name = %s\n", iClientNum, info.len, info.name);
94 }
95
96 /* 创建文件 */
97 int fd = open(info.name, O_CREAT|O_WRONLY|O_TRUNC, 066
6);
98 if (-1 == fd)
99 {
100 printf("can't create %s\n", info.name);
101 return -1;
102 }
103 /* 接收网络数据,写文件 */
104 unsigned char buf[1024];
105 uint64_t writelen = 0;
106
107 while (1)
108 {
109 iRecvLen = recv(iSocketClient, buf, 1024, 0);
110 if (iRecvLen <= 0)
111 {
112 close(iSocketClient);
113 return -1;
114 }
115 else
116 {
117 if (write(fd, buf, iRecvLen) != iRecvLen)
118 {
119 printf("can not write file\n");
120 return -1;
121 }
122 writelen += iRecvLen;
123 if (writelen == info.len)
124 {
125 /* 接收到所有数据并且成功写入 */
126 printf("get file %s ok\n", info.name);
127 close(fd);
128 return 0;
129 }
编译:
source /opt/remi-sdk/environment-setup-aarch64-poky-linux
$CC netcp.c -o netcp
$CC server.c -o server
假设设置开发板的IP为:192.168.5.9,上传程序到开发板上。
测试:
进入/mnt目录运行程序:
root@myir-remi-1g:~
root@myir-remi-1g:/mnt
server netcp
在运行程序前,请先创建txt文件,写入任意数据至文件中,用于后续文件传输。
root@myir-remi-1g:/mnt
root@myir-remi-1g:/mnt
后台运行服务程序,运行客户端程序进行文件信息传输。
root@myir-remi-1g:/mnt# ./server &
root@myir-remi-1g:/mnt# ./netcp 1.txt 127.0.0.1 2.txt
send 104 datas, 104
Get connect from client 0 : 127.0.0.1
Get file info From Client 0: file len = 4, file name = 2.txt
get file 2.txt ok
运行程序后,可以看到终端输出文件数据获取成功。
注意:如果修改server.c,再次编译、运行前,先关闭之前的sever程序。命令为:killall server。
8.5 UDP编程示例
本节源码位于如下目录:
Client程序:
34 iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
35
36 tSocketServerAddr.sin_family = AF_INET;
37 tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short*/
38 //tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
39 if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
40 {
41 printf("invalid server_ip\n");
42 return -1;
43 }
44 memset(tSocketServerAddr.sin_zero, 0, 8);
45
46
47 iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
48 if (-1 == iRet)
49 {
50 printf("connect error!\n");
51 return -1;
52 }
53
54
55 while (1)
56 {
57 if (fgets(ucSendBuf, 999, stdin)) //从标准输入读取用户输入的数据。
58 {
59
60 iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
61
62 iAddrLen = sizeof(struct sockaddr);
63 iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf),0,
64 (const struct sockaddr *)&tSocketServerAddr, iAddrLen);// 将数据通过 UDP 发送给服务器
server程序:
45 iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); //将套接字与特定的 IP 地址和端口号绑定
46 if (-1 == iRet)
47 {
48 printf("bind error!\n");
49 return -1;
50 }
51
52
53 while (1) //服务器将持续运行,等待客户端的消息
54 {
55 iAddrLen = sizeof(struct sockaddr);
56 iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr*)&tSocketClientAddr, &iAddrLen);// 接收客户端发送的消息
57 if (iRecvLen > 0)
58 {
59 ucRecvBuf[iRecvLen] = '\0';
60 printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
61 }
62 }
63
64 close(iSocketServer);
65 return 0;
66 }
编译:
source /opt/remi-sdk/environment-setup-aarch64-poky-linux
$CC netcp.c -o netcp
$CC client.c -o client
假设设置开发板的IP为:192.168.5.9,上传程序到开发板上。
scp ./server root@192.168.5.9:/mnt/
scp ./client root@192.168.5.9:/mnt/
测试:
进入/mnt目录运行程序:
root@myir-remi-1g:~
root@myir-remi-1g:/mnt
server netcp
后台运行服务程序,运行客户端程序进行文件信息传输。
root@myir-remi-1g:/mnt# ./server &
root@myir-remi-1g:/mnt# ./client 127.0.0.1
nihao
Get Msg From 127.0.0.1 : nihao
运行程序后,可以在终端输入信息后,可以看到服务端打印接收到的信息。
如您在使用瑞萨MCU/MPU产品中有任何问题,可识别下方二维码或复制网址到浏览器中打开,进入瑞萨技术论坛寻找答案或获取在线技术支持。
https://community-ja.renesas.com/zh/forums-groups/mcu-mpu/
未完待续
推荐阅读
在Framebuffer上显示图片 - RZ MPU工业控制教程连载(15)
网络通信概述 - RZ MPU工业控制教程连载(16)
网络编程主要函数介绍 - RZ MPU工业控制教程连载(17)