我有一個視頻編碼器,可以通過發送一個URL來開始/停止錄制。
開始錄制。http://192.168.1.5/blah-blah/record/record.cgi
停止錄音。http://192.168.1.5/blah-blah/record/stop.cgi
我不必擔心會得到回應。我只需發送它。
我正在探索GET方法,還試圖使用HappyHTTP庫。 有什么簡單的方法可以在不使用任何庫的情況下做到這一點嗎?
我在Windows上使用visual studios.
。uj5u.com熱心網友回復:
你可以使用TCP連接到服務器,連接后,你可以發送純HTTP請求。 下面的程式顯示了這一點,用TCP協議連接到google,發送HTTP get請求,并獲得回應的前2K。
注意:參見下面 @Chase 的精彩評論,該程式并不完整:應避免使用 gethostbyname,因為該方法已被廢棄,錯誤處理應改為使用 WSAGetLastError 而不是 errno,程式結束前應呼叫 WSACleanup。
我現在沒有時間來修復這個程式,我讓它保持原樣,因為有人可能會從它現在的樣子中受益。
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <winsock.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
#define SERVER_PORT((u_short )80)
#define SERVER_HOST "www.google.com"/span>
int main(int argc, char* argv[])
{
int ret;
SOCKET sd;
struct hostent* he = { 0 };
struct sockaddr_in addr_info = { 0 };
WSADATA wsaData = { 0 };
WSAStartup(MAKEWORD(2, 2), &wsaData)。
he = gethostbyname(SERVER_HOST);
if (he == NULL)
{
printf(" failed to get host information for '%s': %s
", SERVER_HOST, strerror(errno))。)
exit(-1)。
}
sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)。
if (sd == INVALID_SOCKET)
{
printf(" failed to create socket: %s
", strerror(errno))。)
exit(-1)。
}
addr_info.sin_family = AF_INET;
addr_info.sin_port = htons(SERVER_PORT);
addr_info.sin_addr = *((struct in_addr*)he-> h_addr)。
memset(&(addr_info.sin_zero), 0, sizeof(addr_info.sin_zero))。
printf("連接到%s ....
", SERVER_HOST )。
ret = connect(sd, (struct sockaddr*)&addr_info, sizeof(struct sockaddr) )。
if ( ret == -1)
{
printf(" failed to connect: %s
", strerror(errno))。)
exit(-1)。
}
printf("sending GET /index.html request ....
")。)
char send_buffer[] = "get /index.html HTTP/1.1
Host : " SERVER_HOST "
"。
ret = send(sd, send_buffer, sizeof(send_buffer), 0) 。
if ( ret == -1)
{
printf(" failed to send request: %s
", strerror(errno))。)
exit(-1)。
}
printf("getting response (first 2K)
")。)
char response_buffer[2048] = "";
//使用sizeof(response_buffer)-1來使緩沖區以0結束,即使它被recv呼叫完全消耗。這是因為我想在以后列印它。
ret = recv(sd, response_buffer, sizeof( response_buffer)-1, 0)。
if (ret == -1)
{
printf(" failed to get response: %s
", strerror(errno))。)
exit(-1)。
}
printf("收到(前2KB):%s
", response_buffer)。)
closesocket(sd)。
return 0;
}
程式輸出是:
連接到 www.google.com ....
發送GET /index.html請求 ....
獲得回應 (第一個2K)
收到。http/1.1 200 ok
Date: Tue, 29 Dec 2020 08:56:34 GMT
過期了。-1
快取控制: private, max-age=0
Content-Type: text/html; charset=ISO-88591
P3P: CP="這不是一個P3P政策! 請參閱g.co/p3phelp了解更多資訊。"。
服務器:gws
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Set-Cookie: 1P_JAR=2020122908; expires=Thu, 28-Jan-2021 08: 56:34 GMT; path=/; domain=. google.com; 安全
Set-Cookie: NID=205=a7W2c7M39ojimAWRRgn7nwedmdouUrZfo8uBa1wJeEwo8DUk7ibclM-xwp5ozhKO2BYmcRnQ1l4wwjb_DYfOVsDoi- UdtqBmgySL_KlcG6zMjembghO8OL81e2iHee0_cnlDZvSCCGvPnaC0LHNzFtqeWaYSELF7-1t5Khuv4Yc; expires=Wed, 30-Jun-2021 08: 56:34 GMT; path=/; domain=. google.com; HttpOnly
接受范圍: none
Vary: 接受-編碼
傳輸-編碼: chunked
...
uj5u.com熱心網友回復:
向服務器發送請求的最基本方式是"""相當簡單"""。
首先,我假設你知道url的確切主機和埠。因為我無法訪問你提供的鏈接,所以我假設如此。在大多數情況下,你知道主機是你在url中輸入的域名,埠是http的80,或https的443--但我無法確認給定鏈接的情況。
在任何情況下,首先要做的是--你在windows上為套接字編程所需的頭檔案是--
#include <WinSock2.h>
#include <WS2tcpip.h>
你還需要鏈接winsock2庫,你可以通過在你的頭檔案后添加#pragma comment(lib, "Ws2_32.lib")來實作。或者你可以把Ws2_32.lib放在Visual Studio的Project Configuration -> Linker -> Input -> Additional Dependencies中。
現在,winsock2要求你啟動該庫(同時也要關閉它)。你可以在你的 main 函式中做到這一點,因為任何套接字連接的建立都需要啟動 -
/* Initialize wsock2 2.2 */
WSADATA wsadat。
if (WSAStartup(MAKEWORD(2, 2), & wsadat) != 0)
{
printf("WSAStartup failed with error %d
", WSAGetLastError())。)
return 1。
}
而在一切都完成后--
/* Cleanup wsock2 */
if (WSACleanup() == SOCKET_ERROR)
{
printf("WSACleanup failed with error %d
", WSAGetLastError())。)
return 1。
}
現在你需要獲得你想連接的服務器的資訊,我們使用getaddrinfo。(相關的 posix docs)
static struct addrinfo const hints ={ . ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM };
struct addrinfo* addrs;
int gai_retcode = getaddrinfo("192.168.1.5", "http", &hints, & addrs);
if (gai_retcode != 0)
{
fprintf(stderr, "getaddrinfo時遇到的錯誤。%s
"/span>, gai_strerrorA(gai_retcode))。
return 1。
讓我們稍微瀏覽一下,getaddrinfo需要一個node引數--它是一個主機名字串或一個IP地址字串。
第二個引數是 service - 這是一個字串,指定要連接的埠或正在使用的服務(http, https, telnet等等 - 你可以在IANA埠串列或你的linux盒子的/etc/services中找到一個服務串列)。
第三個引數是一個addrinfo結構的地址,包含 "提示",以幫助選擇正確的地址。對于連接到服務器,你通常只需要設定ai_family和ai_socktype成員,其他的都應該是0初始化(這就是復合初始化器自動做的)。在ai_family上的AF_UNSPEC意味著 "為我找到ipv4或ipv6地址--沒有特別要求",在ai_socktype上的SOCK_STREAM意味著 "為我找到流服務器地址"(又稱TCP服務器,不是Datagram服務器)。HTTP是一個基于流的協議。
最后一個引數是指向 addrinfo結構的指標的地址。這就是查找結果的存盤位置。
getaddrinfo成功時回傳0,并填充指向addrs指標的指標。addrs現在是一個從查詢中回傳的地址的鏈接串列 - 串列中的第一個可以直接訪問,下一個可以通過.ai_next訪問,以此類推,直到你到達NULL。常規的詭異行為。
對于在世界各地擁有多個服務器的網站,getaddrinfo將回傳許多地址。它們被安排在由你的機器決定的最相關的方式。也就是說,第一個是最有可能運行良好的地址。但是你應該最好嘗試鏈接串列中的下一個地址,以防第一個地址失敗。
對于非零回傳,你可以使用gai_strerror來列印錯誤資訊。這在windows上被命名為gai_strerrorA,不管什么原因。在windows下也有一個gai_strerror,但是它回傳一個WCHAR*,而不是一個char*。在linux上,你將使用gai_strerror,而不是gai_strerrorA。相關的linux檔案在gai_strerror
現在你有了一個地址串列,你可以創建一個套接字并連接到其中一個地址-
int sfd = socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol) 。
if (sfd == INVALID_SOCKET)
{
fprintf(stderr, "套接字呼叫中遇到的錯誤:%d
", WSAGetLastError())。)
return 1。
}
if ((connect(sfd, addrs->ai_addr, addrs->ai_addrlen)) == 1)
{
fprintf(stderr, "套接字呼叫中遇到的錯誤:%d
", WSAGetLastError())。)
closesocket(sfd)。
return 1。
注意:這只是在嘗試鏈接串列中的第一個地址,如果第一個地址失敗了--它就放棄了。這對于現實世界的用例來說并不理想。但對于你的特定用例,這可能并不重要。
相當自我解釋,socket從你的addrinfo結構中獲取ai_family、ai_socktype和ai_protocol值并創建一個 socket。它在失敗時回傳INVALID_SOCKET(在linux上這只是-1)。相關的linux檔案在socket.
connect采用你剛剛從socket得到的套接字描述符,以及你的addrinfo結構中的ai_addr>和ai_addrlen值來連接到服務器。它在失敗時回傳SOCKET_ERROR(同樣,在linux上只是-1)。在socket上的相關linux檔案。
現在,所有的低級套接字的詭計都完成了。接下來就是HTTP的詭計了。要進行 HTTP 請求,你需要了解 HTTP 規范,你可以在這里閱讀整個規范。
開玩笑,這里有一個更容易消化的版本 - courtesy of MDN。
但基本上,資訊的格式有點像這樣--
<HTTP-VERB> <REQUEST-PATH> HTTP/<VERSION>
[headers-terminated-with-crlf]。
[BODY]
因此,一個使用HTTP 1.0和沒有頭資訊的GET請求到/blah-blah/record/record.cgi(也沒有body,因為這是一個GET請求)看起來像-
GET /blah-blah/record/record.cgi HTTP/1.0。
在繼續之前,我需要提到為什么我演示的是HTTP/1.0,而不是在這個時代更合理的HTTP/1.1。因為不是所有的服務器實際上都完全支持1.0。兩者之間的真正區別在于,1.1需要一個Host:頭,并且它允許保持連接的活力--這對于向同一服務器的多個請求來說是更有效率。
關于Host:頭,如果你知道服務器在請求頭中期望的主機名,那就好了!將其改為HTTP:頭。把它改成HTTP/1.1,然后把Host: hostname放進去。其中hostname是...嗯,主機名。據我所知,你的服務器可能只是接受192.168.1.5作為主機名,但它也可能不接受。
第二部分是 keep-alive 連接部分。保持連接在阻塞套接字中變得有點復雜。如果你決定使用 keep-alive 連接并試圖 recv 從服務器上讀取回應,當沒有更多的東西可以從流中讀取時,recv 將繼續阻塞,直到有東西出現為止 - 因為連接仍然是活的。當然,有一些方法可以解決這個問題,其行為在linux和windows中有所不同。所以我建議如果使用HTTP/1.1,只需將Connection頭設定為close。我想這對你的使用情況來說是 "足夠好 "的。然后,HTTP請求將看起來像......
GET /blah-blah/record/record.cgi HTTP/1.1
主機:主機名
連接:關閉
現在,要發送請求--
char const reqmsg[] = "GET /blah-blah/record/record.cgi HTTP/1.0
"。
if (send(sfd, reqmsg, sizeof reqmsg, 0) == SOCKET_ERROR)
{
fprintf(stderr, "發送呼叫中遇到的錯誤:%d
", WSAGetLastError())。)
closesocket(sfd)。
return 1。
send接收套接字描述符、訊息和訊息長度,并回傳發送的位元組數或在失敗時回傳SOCKET_ERROR(在linux上為-1)。在send
這里有一些需要注意的特殊性。send 可能發送的位元組數少于完整訊息的長度。這對流式套接字來說是正常的。當這種情況發生時,你必須再次呼叫send并從send離開的地方開始發送剩下的部分(將回傳值添加到訊息字串指標中以獲得繼續點)。為了簡潔起見,我們省略了這一點,但對于這么短的訊息來說,它是極不可能實際發生的。如果你有任何一臺過去十年的機器--send將完整地發送那條小小的訊息。
盡管如此,請不要在實際專案中依賴它。
恭喜你!你已經成功地向服務器發送了一個請求。現在你可以接收它的回應。回應的格式是這樣的-
HTTP/<VERSION> <STATUS-CODE> <STATUS-MSG>
[headers-terminated-with-crlf]。
[BODY]
如果你不想接收,你也不必接收,資料將只是坐在流緩沖區中,當你關閉套接字時,它將 "消失"。但這里有一個關于決議出狀態代碼的小例子--
。int status。
char stats[13] 。
if (recv(sfd, stats, 13, 0) != 13)
{
fprintf(stderr, "recv呼叫中遇到的錯誤:%d
", WSAGetLastError())。)
closesocket(sfd)。
return 1;
}
sscanf(stats, "HTTP/1.0 %d" , &status)。
recv,作業原理很像send,只是反過來了。它填補了你作為第二個引數傳遞的緩沖區,它將讀取的最大位元組數可以在其第三個引數中提到。recv的回傳值是讀取的位元組數。關于recv
之前提到的奇特現象在這里也存在。recv可能會讀取少于你在第三個引數中提到的位元組數。但是在任何半正式的網路連接中,13個位元組肯定會被一次性讀取。
為什么是 13 位元組?那是HTTP/1.0的長度 一個空格 3位數的狀態代碼 一個空格。我們只對這個狀態代碼感興趣。sscanf然后將決議出狀態代碼(假設服務器回應正確),并將其放入status。
如果你想讀取任何稍大的尺寸(和/或執行多個recv呼叫),請確保在一個回圈中進行,并使用讀取位元組數的回傳值來適當地使用緩沖區。
在所有這些之后,一定要在addrs上呼叫freeaddrinfo,在sfd上呼叫closesocket。
進一步閱讀。MS docs on Winsock2
在 linux 上,一切都非常相似 - 但卻少了很多麻煩 :) - 如果你想在 linux 上實作這個目標,我建議你查看beej 的網路編程指南。
(事實上,它對于windows程式員來說也是一本不錯的讀物!)。最新的現代功能無縫地支持 ipv4 和 ipv6,沒有手動和痛苦的結構填充等)
一切都將成為現實。
一切都將是超級相似的,你只需要改變標題 - 擺脫愚蠢的windows特定的啟動器,并將WSAGetLastError內的fprintf替換為一個普通的perror。
編輯:替換復合字面(static struct addrinfo const hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM };)-
struct addrinfo hints;
memset(&hints, 0, sizeof hints)。
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM。
我想說的是,如果你能使用支持C99及以上版本的編譯器--使用復合字元來代替--這樣做會更好:)
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/317262.html
標籤:
