
前言

前段時間群里討論,想實現某個文件定時上傳到服務器要怎么來實現。我記得之前做過 一個項目:為高通的iot模組編寫FOTA功能
:實現模組可以遠程下載升級鏡像包,實現版本升級功能。并當時使用的一個超級強大的工具cURL。心血來潮,決定專門寫一篇文章,送給需要的朋友。
文章分兩部分
- 首先介紹cURL的用法;
- 實現一個定時上傳日志文件的一個簡單的程序。

概念
cURL 是常用的命令行工具,用來請求 Web 服務器。它的名字就是客戶端(client)的 URL 工具的意思。
cURL 的原作者是 Daniel Stenberg (目前是 cURL 的核心開發者),同時也是 IETF HTTPbis 工作組的資深成員。Daniel 在 1998 年創建了 curl 項目,他編寫了最初的 curl 版本,并創建了 libcurl 庫。到目前為止,代碼倉庫包括的 24000 次 commit 有超過一半是 Daniel 本人提交的,他依然是項目的核心開發者。Daniel 表示已將 curl 視為自己的孩子。
作為一款強力工具,支持的協議包括 (DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, TELNET and TFTP),還支持POST、cookies、認證、從指定偏移處下載部分文件等功能,具有用戶代理字符串、限速、文件大小、進度條、cookie支持、用戶認證、斷點續傳等特征。
一、命令的安裝
sudo apt-get install curl
二、cURL命令語法:
curl [options] [URL...]
三、URL格式
URL的格式定義要參考 RFC 1808 。
地址:http://www.w3.org/Addressing/rfc1808.txt
《Relative Uniform Resource Locators 》
URL由三部分組成:資源類型、存放資源的主機域名、資源文件名。 也可認為由4部分組成:協議、主機、端口、路徑
URL的一般語法格式為:
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
(帶方括號[]的為可選項)。
protocol(協議)
指定使用的傳輸協議,下表列出 protocol 屬性的有效方案名稱。 最常用的是HTTP協議,它也是WWW中應用最廣的協議。
- file 資源是本地計算機上的文件。格式file:///,注意后邊應是三個斜杠。
- ftp 通過 FTP訪問資源。格式 FTP://
- gopher 通過 Gopher 協議訪問該資源。
- http 通過 HTTP 訪問該資源。 格式 HTTP://
- https 通過安全的 HTTPS 訪問該資源。 格式 HTTPS://
- mailto 資源為電子郵件地址,通過 SMTP 訪問。 格式 mailto:
- MMS 通過 支持MMS(流媒體)協議的播放該資源。(代表軟件:Windows Media Player)格式 MMS://
- ed2k 通過 支持ed2k(專用下載鏈接)協議的P2P軟件訪問該資源。(代表軟件:電驢) 格式 ed2k://
- Flashget 通過 支持Flashget:(專用下載鏈接)協議的P2P軟件訪問該資源。(代表軟件:快車) 格式 Flashget://
- thunder 通過 支持thunder(專用下載鏈接)協議的P2P軟件訪問該資源。(代表軟件:迅雷) 格式 thunder://
- news 通過 NNTP 訪問該資源。
hostname(主機名)
是指存放資源的服務器的域名系統(DNS) 主機名或 IP 地址。有時,在主機名前也可以包含連接到服務器所需的用戶名和密碼(格式:username:password@hostname)。
port(端口號)
整數,可選,省略時使用方案的默認端口,各種傳輸協議都有默認的端口號,如http的默認端口為80。如果輸入時省略,則使用默認端口號。有時候出于安全或其他考慮,可以在服務器上對端口進行重定義,即采用非標準端口號,此時,URL中就不能省略端口號這一項。
path(路徑)
由零或多個“/”符號隔開的字符串,一般用來表示主機上的一個目錄或文件地址。
parameters(參數)
這是用于指定特殊參數的可選項。
query(查詢)
可選,用于給動態網頁(如使用CGI、ISAPI、PHP/JSP/ASP/http://ASP.NET等技術制作的網頁)傳遞參數,可有多個參數,用“&”符號隔開,每個參數的名和值用“=”符號隔開。
fragment(信息片斷)
字符串,用于指定網絡資源中的片斷。例如一個網頁中有多個名詞解釋,可使用fragment直接定位到某一名詞解釋。
四、curl命令參數詳解:
由于linux curl功能十分強大,所以命令參數十分多,下表只篩選出來部分常用的參數,更多參數請運行“man curl”命令查看。
ble data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">五、Linux curl命令退出碼:
下面是linux curl命令的錯誤代碼和她們的相應的錯誤消息,命令執行錯誤的時候可以通過錯誤碼來查看出錯原因,方便開發調試。
ble data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">六、用法演示:
為節省篇幅,部分操作不再貼上執行結果。
1、查看網頁源碼
直接在curl命令后加上網址,就可以看到網頁源碼。我們以網址http://www.sina.com為例(選擇該網址,主要因為它的網頁代碼較短):
root@ubuntu:/home/peng# curl www.sohu.com
<html>
<head><title>307 Temporary Redirect</title></head>
<body bgcolor="white">
<center><h1>307 Temporary Redirect</h1></center>
<hr><center>nginx</center>
</body>
</html>
執行結果顯示 307 Temporary Redirect
,說明該網址需要重定向。
如果要把這個網頁保存下來,可以使用-o
參數,這就相當于使用wget命令了。
curl -o [文件名] www.sohu.com
2、自動跳轉
有的網址是自動跳轉的。使用-L
參數,curl就會跳轉到新的網址。
curl -L www.sohu.com
鍵入上面的命令,結果就自動跳轉為http://www.sohu.com.cn。
3、顯示頭信息
-i
參數可以顯示http response的頭信息,連同網頁代碼一起。
root@ubuntu:/home/peng/driver/test# curl -i www.sohu.com
HTTP/1.1 307 Temporary Redirect
Content-Type: text/html
Content-Length: 180
Connection: keep-alive
Server: nginx
Date: Tue, 25 Aug 2020 10:10:54 GMT
Location: https://www.sohu.com/
FSS-Cache: from 9790436.18244590.10468709
FSS-Proxy: Powered by 2384755.3433341.3062915<html>
<head><title>307 Temporary Redirect</title></head>
<body bgcolor="white">
<center><h1>307 Temporary Redirect</h1></center>
<hr><center>nginx</center>
</body>
</html>
-I
參數則是只顯示http response的頭信息。
4、顯示通信過程
-v
參數可以顯示一次http通信的整個過程,包括端口連接和http request頭信息。
root@ubuntu:/home/peng/driver/test# curl -v www.sohu.com
* About to connect() to www.sohu.com port 80 (#0)
* Trying 240e:83:201:3700::5... connected
> GET / HTTP/1.1
> User-Agent: curl/7.22.0 (i686-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
> Host: www.sohu.com
> Accept: */*
>
< HTTP/1.1 307 Temporary Redirect
< Content-Type: text/html
< Content-Length: 180
< Connection: keep-alive
< Server: nginx
< Date: Tue, 25 Aug 2020 10:11:49 GMT
< Location: https://www.sohu.com/
< FSS-Cache: from 9855973.18375663.10534247
< FSS-Proxy: Powered by 2450292.3564414.3128453
<
<html>
<head><title>307 Temporary Redirect</title></head>
<body bgcolor="white">
<center><h1>307 Temporary Redirect</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host www.sohu.com left intact
* Closing connection #0
如果你覺得上面的信息還不夠,那么下面的命令可以查看更詳細的通信過程。
curl --trace output.txt www.sohu.com
或者
curl --trace-ascii output.txt www.sohu.com
運行后,請打開output.txt文件查看。
5、發送表單信息
發送表單信息有GET和POST兩種方法。GET方法相對簡單,只要把數據附在網址后面就行。
curl example.com/form.cgi?data=xxx
POST方法必須把數據和網址分開,curl就要用到--data參數。
curl -X POST --data "data=xxx" example.com/form.cgi
如果你的數據沒有經過表單編碼,還可以讓curl為你編碼,參數是--data-urlencode
。
curl -X POST--data-urlencode "date=April 1" example.com/form.cgi
6、HTTP動詞
curl默認的HTTP動詞是GET,使用-X
參數可以支持其他動詞。
curl -X POST www.example.com
curl -X DELETE www.example.com
7、文件上傳
假定文件上傳的表單是下面這樣:
<form method="POST" enctype='multipart/form-data' action="upload.cgi"><input type=file name=upload><input type=submit name=press value="OK">
</form>
你可以用curl這樣上傳文件:
curl --form upload=@localfilename --form press=OK [URL]
8、Referer字段
有時你需要在http request頭信息中,提供一個referer字段,表示你是從哪里跳轉過來的。
curl --referer http://www.example.com http://www.example.com
9、User Agent字段
這個字段是用來表示客戶端的設備信息。服務器有時會根據這個字段,針對不同設備,返回不同格式的網頁,比如手機版和桌面版。
iPhone4的User Agent是
Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7
curl可以這樣模擬:
curl --user-agent "[User Agent]" [URL]
10、cookie
使用--cookie
參數,可以讓curl發送cookie。
curl --cookie "name=xxx" www.example.com
至于具體的cookie的值,可以從http response頭信息的Set-Cookie
字段中得到。
-c cookie-file
可以保存服務器返回的cookie到文件,-b cookie-file
可以使用這個文件作為cookie信息,進行后續的請求。
curl -c cookies http://example.com
curl -b cookies http://example.com
11、增加頭信息
有時需要在http request之中,自行增加一個頭信息。--header
參數就可以起到這個作用。
$ curl --header "Content-Type:application/json" http://example.com
12、認證
使用curl選項 -u 可以完成HTTP或者FTP的認證,可以指定密碼,也可以不指定密碼在后續操作中輸入密碼:
curl -u user:pwd http://man.linuxde.net
curl -u user http://man.linuxde.net
13、FTP
1)、列出ftp服務器上的目錄列表
curl ftp://www.xxx.com/ --user name:passwd
curl ftp://www.xxx.com/ –u name:passwd #簡潔寫法
curl ftp://name:passwd@www.xxx.com #簡潔寫法2
例如:在IP地址192.168.43.117上搭建FTP服務器,并設置用戶名為user
,密碼為123456
現在我們要顯示服務器上根目錄下的所有文件信息,命令如下:
curl -u user:123456 ftp://192.168.43.117
執行結果如下:

簡潔寫法:
curl ftp://user:123456@192.168.43.117
執行結果如下:

2)、只列出目錄,不顯示進度條
curl ftp://www.xxx.com –u name:passwd -s
3)、下載一個文件:
格式
curl ftp://www.xxx.com/size.zip –u name:passwd -o size.zip
示例如下: 從服務器的根目錄下下載文件test.c,保存到本地,本地文件名也為test.c。 【注意】如果沒有-o選項,程序會吧數據流定向到stdout,即直接把文件內容顯示到終端上。
curl ftp://user:123456@192.168.43.117/test.c -o test.c
執行結果如下:

簡潔模式
curl -u user:123456 ftp://192.168.43.117/list.h -o list.h
執行結果如下:

4)、上載一個文件:
curl –u name:passwd -T size.mp3 ftp://www.xxx.com/mp3/
舉例如下:
curl -u user:123456 ftp://192.168.43.117/ -T list.h

可以看到文件并沒有上傳成功,返回錯誤碼是25,參考第五章
25 FTP couldn't STOR file. The server denied the STOR operation, used for FTP uploading.
可知,是因為服務器沒有賦予存儲的權限,所以設置服務器的write權限即可。

5)、從服務器上刪除文件(使用curl傳遞ftp協議的DELE命令):
curl –u name:passwd ftp://www.xxx.com/ -X 'DELE mp3/size.mp3'
6)、另外curl不支持遞歸下載,不過可以用數組方式下載文件,比如我們要下載1-10.gif連續命名的文件:**
curl –u name:passwd ftp://www.xxx.com/img/[1-10].gif –O #O字母大寫
7)、要連續下載多個文件:
curl –u name:passwd ftp://www.xxx.com/img/[one,two,three].jpg –O #O字母大寫
六、實現日志文件定時上傳
功能
- 程序運行時要記錄當前日志文件的最后修改時間;
- 每個10秒鐘就檢查下log文件是否被修改,如果沒有被修改就休眠10秒鐘;
- 如果log文件被修改了,就將當前的日志文件拷貝成備份文件,備份文件名字加上當前時間;
- 通過curl發送給ftp服務器;
- 刪除備份文件,重復步驟2。
程序流程圖如下:

函數功能介紹
init()
首先記錄當前log文件時間,并記錄到全局變量last_mtime
中。
check_file_change()
讀取文件最后修改時間,并和last_mtime
進行比較,如果相同就返回0,不同就返回1.
file_name_add_time()
將當前的日志文件拷貝成備份文件,備份文件名字加上當前時間。
stat()?
得到對應文件的屬性信息,存放到struct stat結構體變量中。
system()
執行參數中字符串對應的命令
代碼如下:
/* Copyright (C) 公眾號: yikoulinux */
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>typedef struct stat ST;
unsigned long last_mtime;/*用戶名密碼暫時寫死,實際應該保存在配置文件*/
char name[32]="user";
char pass[32] ="123456";
char ip[32] ="192.168.43.117";
char filename[32]="t.log";
char dstfile[256] ={0};int init(void)
{//準備結構體ST status;//調用stat函數int res = stat(filename,&status);if(-1 == res){perror("error:open file failn");return 0;}last_mtime = status.st_mtime;printf("init time:%s n",ctime(&last_mtime));return 1;
}int check_file_change(void)
{//準備結構體ST status;//調用stat函數int res = stat(filename,&status);if(-1 == res){perror("error:open file failn");return 0;}
// printf("old:%s new:%s",ctime(&last_mtime),ctime(&status.st_mtime));if(last_mtime == status.st_mtime){printf("file not changen");return 0;}else{printf("file updatedn"); last_mtime = status.st_mtime;return 1;}}
void file_name_add_time(void)
{ST status;time_t t; struct tm *tblock; char cmd[1024]={0};t = time(NULL);tblock = localtime(&t);sprintf(dstfile,"t-%d-%d-%d-%d-%d-%d.log",tblock->tm_year+1900,tblock->tm_mon,tblock->tm_mday,tblock->tm_hour,tblock->tm_min,tblock->tm_sec);sprintf(cmd,"cp %s %s",filename,dstfile);
// printf("cdm=%sn",cmd);system(cmd);
}
int main(void)
{char cmd[1024]={0};init();while(1){ if(check_file_change() == 1){file_name_add_time();sprintf(cmd,"curl -u %s:%s ftp://%s/ -T %s",name,pass,ip,dstfile);// printf("cdm=%sn",cmd);system(cmd);unlink(dstfile);}sleep(10); }
}
運行截圖:
第一步:

因為log文件沒有被修改過,所以程序不會上傳。
第二步: 手動輸入字符串 yikoulinux 到日志文件 t.log中。

第三步: 因為文件發生了改變,所以打印“file updated”,同時可以看到curl上傳文件的log信息。

以下是FTP服務器的根目錄,可以看到,上傳的日志文件:t-2020-7-26-1-19-45.log
。

【補充】
- 配置信息,直接在代碼中寫死,通常應該從配置文件中讀取,為方便讀者閱讀,本代碼沒有增加該功能;
- FTP服務器搭建,本文沒有說明,相關文件比較多,大家可以自行搜索,一口君用的是File zilla;

- 通常這種需要長時間運行的程序,需要設置成守護進程,本文沒有添加相應功能,讀者可以自行搜索。如果強烈要求可以單開一篇詳細介紹。
- 代碼中time的管理函數,請讀者自行搜索相關文章。
- curl也提供了相關的函數庫curl.lib,如果要實現更靈活的功能可以使用對應的api。
- 之所以先把文件拷貝成備份文件,主要是考慮其他模塊隨時可能修改日志文件,起到一定保護作用。
想和博主交流,請關注公眾號「一口Linux」