有時候會有人來問怎麼用C語言寫程式來跟網頁伺服器溝通,
關於這個問題,其實我也不會,
因為我使用了libcurl來處理麻煩的HTTP通訊協定。
libcurl是一個功能強大的網路通訊程式庫,
支援的通訊協定有FTP,FTPS,HTTP,HTTPS,SCP,SFTP,TFTP,TELNET,
DICT,LDAP,LDAPS,FILE,IMAP,SMTP,POP3,RTMP及RTSP。
詳細的介紹就請自行到它的官網看囉~
再來就說一下怎麼用libcurl來模擬登入Facebook吧。
一、libcurl的編譯方式,請參考編譯各Library。
二、由於表達能力不太好,直接釋出悠閒農夫的使用者登入程式碼。
User.h
#ifndef USER_H
#define USER_H
#include <wx/string.h>
#include <curl/curl.h>
class User
{
public:
User();
bool Login();
void SetFBID(wxString &value);
void SetLoginName(wxString value);
void SetPassword(wxString value);
wxString GetFBID(){return wxString(fbid, wxConvUTF8);};
virtual ~User();
protected:
private:
CURL *handle;
CURLcode code;
char lsd[128];
char fbid[128];
//FILE *headerfile;
wxString _name;
char c_name[128];
wxString _pwd;
char c_pwd[128];
};
#endif // USER_H
User.cpp
#include "User.h"
#include <wx/wx.h>
static size_t write_data(char *data, size_t size, size_t nmemb, wxString *writerData)
{
return nmemb;
}
void getCookies(CURL *handle, wxString name, char *data)
{
struct curl_slist *cookies;
struct curl_slist *nc;
curl_easy_getinfo(handle, CURLINFO_COOKIELIST, &cookies);
nc = cookies;
int index = wxNOT_FOUND;
int len = name.Len() + 2;
wxString search;
search += _T("\t") + name + _T("\t");
while (nc) {
wxString msg(nc->data, wxConvUTF8);
index = msg.Find(search);
if(index != wxNOT_FOUND)
{
strcpy(data, (const char*)msg.Mid(index + len, msg.Len() - index - len).mb_str(wxConvUTF8));
break;
}
nc = nc->next;
}
curl_slist_free_all(cookies);
}
User::User()
{
//ctor
//初始化
curl_global_init(CURL_GLOBAL_ALL);
//取得handle
handle = curl_easy_init();
code = curl_easy_setopt(handle, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-TW; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 GTB6 (.NET CLR 3.5.30729)");
code = curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_data);
code = curl_easy_setopt(handle, CURLOPT_ENCODING, "gzip, deflate");
code = curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
code = curl_easy_setopt(handle, CURLOPT_URL, "http://apps.facebook.com/farmgame_tw/");
code = curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1L);
code = curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
}
User::~User()
{
curl_easy_cleanup(handle);
curl_global_cleanup();
}
bool isTest = false;
bool isLogin = false;
void User::SetFBID(wxString &value)
{
isTest = true;
long res_code = 0;
char *new_url;
strcpy(fbid, value.mb_str(wxConvUTF8));
wxString fname = value;
fname.Append(_T(".cks"));
code = curl_easy_setopt(handle, CURLOPT_COOKIEFILE, (const char*)fname.mb_str(wxConvUTF8)); /* just to start the cookie engine */
code = curl_easy_setopt(handle, CURLOPT_COOKIEJAR, (const char*)fname.mb_str(wxConvUTF8));
code = curl_easy_perform(handle);
curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &res_code);
curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &new_url);
wxString msg(new_url, wxConvUTF8);
isLogin = (res_code == 200 && msg.StartsWith(_T("http://apps.facebook.com/farmgame_tw/")));
if(!isLogin) getCookies(handle, _T("lsd"), lsd);
}
bool User::Login()
{
if(isLogin) {
return true;
}
if(!isTest) {
code = curl_easy_setopt(handle, CURLOPT_COOKIEFILE, ""); /* just to start the cookie engine */
code = curl_easy_perform(handle);
getCookies(handle, _T("lsd"), lsd);
}
struct curl_httppost *formpost=NULL;
struct curl_httppost *lastptr=NULL;
//塞Form的值
curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "charset_test",
CURLFORM_COPYCONTENTS, "€,´,€,´,水,Д,Є", CURLFORM_END);
curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "next",
CURLFORM_COPYCONTENTS, "http://apps.facebook.com/farmgame_tw/", CURLFORM_END);
curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "version",
CURLFORM_COPYCONTENTS, "1.0", CURLFORM_END);
curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "api_key",
CURLFORM_COPYCONTENTS, "c1f92c58896a86359ac815b2dc7c708a", CURLFORM_END);
curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "return_session",
CURLFORM_COPYCONTENTS, "0", CURLFORM_END);
curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "session_key_only",
CURLFORM_COPYCONTENTS, "0", CURLFORM_END);
curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "charset_test",
CURLFORM_COPYCONTENTS, "€,´,€,´,水,Д,Є", CURLFORM_END);
curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "lsd",
CURLFORM_COPYCONTENTS, lsd, CURLFORM_END);
curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "email",
CURLFORM_COPYCONTENTS, c_name, CURLFORM_END);
curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "pass",
CURLFORM_COPYCONTENTS, c_pwd, CURLFORM_END);
curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "login",
CURLFORM_COPYCONTENTS, "登入", CURLFORM_END);
if(handle)
{
code = curl_easy_setopt(handle, CURLOPT_URL, "https://login.facebook.com/login.php?login_attempt=1&canvas=1");
code = curl_easy_setopt(handle, CURLOPT_HTTPPOST, formpost);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
code = curl_easy_perform(handle);
curl_formfree(formpost);
if(code != CURLE_OK) throw wxString::Format(_T("錯誤:%s"), wxString(curl_easy_strerror(code), wxConvUTF8).c_str());
long res_code = 0;
char *new_url;
curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &res_code);
curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &new_url);
wxString msg(new_url, wxConvUTF8);
isLogin = (res_code == 200 && msg.StartsWith(_T("http://apps.facebook.com/farmgame_tw/")));
if(isLogin) {
getCookies(handle, _T("c_user"), fbid);
wxString fname(fbid, wxConvUTF8);
fname.Append(_T(".cks"));
code = curl_easy_setopt(handle, CURLOPT_COOKIEJAR, (const char*)fname.mb_str(wxConvUTF8));
}
else getCookies(handle, _T("lsd"), lsd);
//msg.Clear();
//content.Clear();
}
return isLogin;
}
void User::SetPassword(wxString value)
{
_pwd = value;
strcpy(c_pwd, (const char*)value.mb_str(wxConvUTF8));
}
void User::SetLoginName(wxString value)
{
_name = value;
strcpy(c_name, (const char*)value.mb_str(wxConvUTF8));
}
大部份的網頁伺服器在判斷是不是合法的網頁使用都是靠cookies,
Facebook也不例外,
因此在登入後必須把cookies記起來,
在後續與Facebook的溝通都必須把cookies傳回去,
而libcurl在這部份已經都處理好了,
完全不用自己控制,
只須要把cookies存成檔案後,
叫libcurl去使用就可以了。
三、登入程式的使用方式
User user;
user.SetFBID(_("Facebook編號"));
user.SetLoginName(_("登入帳號"));
user.SetPassword(_("登入密碼"));
user.Login();
- 由於太過頻繁的同帳號登入,
會被Facebook擋掉不給登,
因此在登入成功後,
會產生一個以Facebook編號為檔名的.cks檔案來儲存cookies,
若有這個檔案存在,在登入前請先用SetFBID,
把Facebook編號傳入,
程式會先用cookies去跟facebook溝通,
如果cookies還沒過期,
那自然就不用再透過帳號、密碼的驗證了。
- 把登入帳號及登入密碼傳入
- call ser.Login()做登入的動作,若登入成功會回傳true。
四、使用libcurl的重點
- 使用前必需先呼叫curl_global_init(CURL_GLOBAL_ALL);做初始化的動作。
- 使用curl_easy_init();取得handle
- 如果網頁內容很大,
必需使用curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_data); curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
讓它的內容有地方輸出,不然會因為buffer不足而出錯。
- 如果libcurl的method所回傳的狀態碼不是CURLE_OK時,
可以用curl_easy_strerror(code)
來取得錯誤訊息
若要知道libcurl還有哪些用法,
可以去看它提供的example及API說明
不過全都是英文的,不要跟我說看不懂,我英文也很差的!!
libcurl 真是好物,雖然我不是寫 C 的,不過有機會讀人家程式這種事我可不會放過,後來在老外的網站找到 libcurl.vb(封裝給 vb6 的版本),玩了一下... libcurl 真是太變態了,就像您說的很多麻煩事都給處理好了,以前自己用 Winsock + 一堆 API 收回來一些火星文,看了就先軟一半...
回覆刪除大大你好,先感謝你無私的分享!
回覆刪除小弟目前想做一個task,就是希望能在embedded system (without browser)上利用curl來做facebook login並且取得個人網頁上朋友的動態訊息,並顯示在embedded system上.
不過在網路上爬了很多文,都沒找到可以在不用browser下,成功login的方式(開發論壇上也說基於安全問題,facebook必須要透過browser login),但我看你開發的悠閒農夫,似乎就沒有透過browser就可以login,讓我看到一線希望.不過我借用你上面c"url的code,執行完curl_easy_perform()後,結果都得到"CURLE_UNSUPPORTED_PROTOCOL"的返回值.無法成功登錄.
因此有些問題想請教你,希望你不吝回答,謝謝~~
1.不曉得只用CURL在沒有browser的情況下,是否真的可以執行login的動作,也就是說,大大覺得我這個task可行性如何?
2.對於user.SetFBID(_("Facebook編號")); 這個function中Facebook編號,應該是要填什麼,是個人的ID,還是指開心農場的ID?
感謝你抽空閱讀,也希望能夠得到你的回應,謝謝~~~
給Hsu
回覆刪除1.可行,不過最好了解什麼是HTTP通訊協定及其運作方式。
2.user.SetFBID的用處請看「登入程式的使用方式」的第一點,Facebook編號不是農場的ID,基本上不填也是可以的。
這個 libcurl 是 ssl 版本嗎?
回覆刪除to sega:
回覆刪除它可以有ssl也可以沒有ssl
主要是看你怎麼編譯它。
現在這個 code 還可以用嗎?
回覆刪除我修改了來用似乎遇到問題
curl_easy_strerror 回傳
Unsupported protocol
殘念
to sega:
回覆刪除你用的protocol是什麼呢?
如果是https,那在編譯libcurl時要把openssl編譯進去才能用唷
不是也..我目前想寫的是http登入facebook
回覆刪除沒寫過 http 的東西
公司要我寫在 online game 裡面
可以打卡 po 留言以及照片到 facebook 的機制
到處找資料
所以找到你這
但似乎沒有很順利
你這麼一提我倒是看到..
回覆刪除你用"https://login.facebook.com/login.php?login_attempt=1&canvas=1"
那表示要用ssl版本的是吧
我來重新安裝一下相關元件
用了有 ssl 的 libcurl 好像成功了...感恩
回覆刪除