TCP/IP通信协议我们做项目编程的时候,经常都会用到,它的底层原理其实有点复杂,但我们实际编程应用的时候,主要都是调用一些接口,了解了这些接口的原理,实现起来也不是很难。
今天就结合源码讲讲基于TCP实现文件传输与UDP编程方法。
基于TCP协议,实现文件传输。约定规则:先传输一个结构体,里面含有“文件名,文件大小”
1、Client:
- open文件,得到文件状态(大小),构造结构体,发送结构体。
- 循环read文件,发送网络数据。
2、Server:
- 读网络数据,得到结构体,创建文件。
- 循环read网络数据,写文件,数量足够就关闭文件。
获取文件信息
本节源码位于如下目录:
关键代码:
/* ./file_stat 1.txt */
int main(int argc, char **argv)
{
struct stat statbuf;
if (argc != 2)
{
printf("Usage: %s <file>\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
本地读文件、写文件
本节源码位于如下目录:
关键代码:
/* 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
实现网络传输:传输文件信息
本节源码位于如下目录:
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 <local_file> <server_ip> <remote_file>\n", argv[0]);
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:~# cd /mnt/
root@myir-remi-1g:/mnt# ls
server netcp
在运行程序前,请先创建txt文件,写入任意数据至文件中,用于后续文件传输。
root@myir-remi-1g:/mnt# touch 1.txt
root@myir-remi-1g:/mnt# echo 123 > 1.txt
后台运行服务程序,运行客户端程序进行文件信息传输。
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。
实现网络传输:传输文件数据
本节源码位于如下目录:
源码为“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 else110 {
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程序:
72while (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, 0666);
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:~# cd /mnt/
root@myir-remi-1g:/mnt# l
sserver netcp
在运行程序前,请先创建txt文件,写入任意数据至文件中,用于后续文件传输。
root@myir-remi-1g:/mnt# touch 1.txt
root@myir-remi-1g:/mnt# echo 123 > 1.txt
后台运行服务程序,运行客户端程序进行文件信息传输。
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。
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 #if 0
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 #endif
54
55 while (1)
56 {
57 if (fgets(ucSendBuf, 999, stdin)) //从标准输入读取用户输入的数据。
58 {
59 #if 0
60 iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
61 #else
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:~# cd /mnt/
root@myir-remi-1g:/mnt# l
sserver 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
运行程序后,可以在终端输入信息后,可以看到服务端打印接收到的信息。
END
作者:百问网
来源:嵌入式专栏
推荐阅读
欢迎大家点赞留言,更多Arm技术文章动态请关注极术社区嵌入式客栈专栏欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。