最近專案中需要自己去實現一個http的介面。所以趁這個機會跟大家講一下http和socket的關係,以及與TCP又有什麼聯絡。
首先大家一定要明確一點,在網路分層架構當中,HTTP協議是屬於應用層的,tcp協議是屬於傳輸層的,也就是說它們是一種協議,是通訊雙方規定的一種規則,沒有這種規則,兩臺主機就無法完成通訊。
而根據我們曾經所學的知識可以知道,兩臺主機要完成通訊,必須在傳輸層規定一套相同的協議,至於要不要在傳輸層就建立連線,因協議而異,tcp協議是需要建立連線的,而udp就不需要。至於tcp和udp的區別,不在本文的討論範圍,所以暫時不論。因為現在傳輸資料大部分都是使用tcp協議,所以tcp協議是非常重要的,必須要掌握。
傳輸層使用tcp協議傳送資料的話,首先要完成TCP的三次握手過程。為什麼要完成三次握手過程?為了保證資料傳輸的準確性,就是讓資料可以準確無誤的傳輸到另一臺主機。至於如何完成三次握手過程,這個知識點在網上有非常多的資料大家可以去百度看看。
而三次握手建立連線,這更像是一種理論的過程,也就是說我告訴你三次握手的過程,但是你要幫我實現這個過程,那怎麼實現呢?這個具體實現的過程就是靠socket來實現的,socket是作業系統為tcp封裝的一整套建立連線,傳送資料,斷開連線的過程,它是對外提供的一個介面。注意我這裡說的是作業系統,也就是說不同的作業系統封裝的socket介面函式可能有所不同,這一點大家需要注意。在linux上使用最多的socket函式一般有socket()bind()listen()accept()connect()close()這幾個函式,在window上略有不同。
到這裡不知道大家明白了沒有,tcp只是傳輸層上的一個協議,是通訊雙方互相規定的一種協議,而socket就是這種協議的具體實現過程。所以如果你足夠牛逼,你可以自己給通訊雙方的兩臺主機制定一套屬於自己的傳輸層協議,然後自己寫程式碼實現這個過程。但一般沒有人會這麼做,為什麼呢?因為這個工作量非常的恐怖,這個恐怖不是體現在制定協議以及寫程式碼實現的這個過程,這個恐怖是體現在必須為通訊雙方的兩臺機子都適配這種協議。服務端還好說,是都是自己的機子,控制權都在自己手上,而且一般都只使用linux系統,但是到客戶端就徹底宕機了,客戶端肯定不是就一臺的,是千千萬萬臺,而且還有不同的作業系統,你要不就自己去一個個系統去適配你的協議,要不就是去斡旋各大作業系統廠商寫入你的協議。所以這樣的事也只有全球有影響力的企業,有影響力的組織才可以完成的,一般人不可能,也沒必要。
上面說了那麼多,就是告訴同學們,通訊雙方要完成通訊,要先在傳輸層利用tcp協議建立連線。連線建立完成之後,就可以開始傳送資料了,那麼接著問題來了。
一、如果我要傳送不同結構,不同規則的資料的話,我要怎麼發。
二、我發出去的資料,肯定會收到一個回覆,那麼我怎麼處理這個回來的資料。
如果以上兩個問題,大家不是很明白,沒關係,接下去往下看,你可能就明白了。
基於以上兩個問題,就需要在應用層上制定一套屬於通訊雙方自己的協議了,而這套協議是規定雙方傳送接收的資料規則。http就是應用層一個非常經典的協議,它是因特網上應用最為廣泛的一種網路傳輸協議,所有的WWW檔案都必須遵守這個標準。當然應用層協議不僅僅只有http,還有telnet,ftp,smtp等等這些都是非常經典的應用層協議,通訊雙方都必須按照協議規定的資料格式來發送和接收。而且根據雙方傳送資料的需求,還可以制定屬於自己的應用層協議,來滿足自己的本地化需求。只要你有需求,應用層協議隨便你新增。
那麼為什麼新增傳輸層協議難如登天,而新增應用層協議卻那麼簡單呢?
簡單一句話概括就是:傳輸層協議是作業系統級別的,而應用層協議是應用軟體級別的。
所以新增一個傳輸層協議一定是一個浩大的工程,因為要在作業系統級別上更新。而新增一個應用層協議就比較簡單了,因為只是新增在你所開發的軟體或者app的客戶端和服務端上。
用最生活化的例子來比喻,假如要從A地到B地,那麼怎麼過去呢,肯定需要修建一條路,那麼修建這條路所需要的設計圖紙就相當於tcp,而工人們修建的過程就相當於socket,不能盲目修建,必須基於設計圖紙來修建,而socket也必須基於tcp協議的理論,而修建一條道路是耗資巨大的工程,所以不可能隨便的新增傳輸層協議。一旦道路修建完成,你可以採用各種方式過去,走路、跑步、騎腳踏車、開小車等等都可以,只要你開心,你要爬過去都可以,而採用何種方式過去就是應用層協議,http是其中的一種過去方式。
總的來說,tcp是傳輸層的一個協議,而socket是這個協議的封裝,可以對外提供介面,讓應用程式呼叫,而http是應用層的一個協議,是一種對資料的封裝。發起http請求的時候,底層的傳輸層要完成兩臺機子的連線,就是tcp三次握手完成連線。
以下我給出了一個http封裝的例子,只有客戶端,是當時做專案的時候寫,大家可以參考參考
//http介面int http_post_openapi(const char *pIP, const char *pServ, int port, const char *pSendValue, char *pRecvValue){ int sockfd, ret, i, h; struct sockaddr_in servaddr; char szHttpHead[1024], buf[8192], szSendValueLength[128],szSendBuffer[4096]; int iLen = 0; fd_set t_set1; struct timeval tv; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { printf("建立網路連線失敗,本執行緒即將終止---socket error!\n"); exit(0); }; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port); if (inet_pton(AF_INET, pIP, &servaddr.sin_addr) <= 0 ){ printf("建立網路連線失敗,本執行緒即將終止--inet_pton error!\n"); exit(0); }; if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0){ printf("連線到伺服器失敗,connect error!\n"); exit(0); } printf("與遠端建立了連線\n"); if (pSendValue == NULL || pRecvValue == NULL) { close(sockfd); printf("\n傳入的pSendValue或者pRecvValue為空!!!\n"); return -1; } char szSendConver[4096] = {0}; TrimAll(pSendValue, szSendConver); iLen = strlen(szSendConver); memset(szSendValueLength, 0, 128); sprintf(szSendValueLength, "%d", iLen); printf("szSendValueLength after sprintf is :%s\n", szSendValueLength); memset(szHttpHead, 0, 256); strcat(szHttpHead, "POST "); strcat(szHttpHead, pServ); strcat(szHttpHead, " HTTP/1.1\r\n"); strcat(szHttpHead, "Host: "); strcat(szHttpHead, pIP); strcat(szHttpHead, ":"); char cPort[6]; sprintf(cPort,"%ld",port); strcat(szHttpHead, cPort); strcat(szHttpHead, "\r\n"); strcat(szHttpHead, "User-Agent: Apache-HttpClient/4.1.1\r\n"); strcat(szHttpHead, "Accept: */*\r\n"); strcat(szHttpHead, "Content-Length: "); strcat(szHttpHead, szSendValueLength); strcat(szHttpHead, "\r\n"); strcat(szHttpHead, "Content-Type: application/json; charset=UTF-8"); printf("szSendValueLength is :%s\n", szSendValueLength); strcat(szHttpHead, "\r\n\r\n"); memset(szSendBuffer, 0, 4096); strcat(szSendBuffer, szHttpHead); strcat(szSendBuffer, szSendConver); strcat(szSendBuffer, "\r\n\r\n"); printf("Print SendBuffer before write :\n%s\n",szSendBuffer); ret = write(sockfd,szSendBuffer,strlen(szSendBuffer)); if (ret < 0) { printf("傳送失敗!錯誤程式碼是%d,錯誤資訊是'%s'\n",errno, strerror(errno)); exit(0); }else{ printf("訊息傳送成功,共傳送了%d個位元組!\n\n", ret); } FD_ZERO(&t_set1); FD_SET(sockfd, &t_set1); memset(buf, 0, sizeof(buf)); i= read(sockfd, buf, sizeof(buf)-1); if (i==0) { close(sockfd); printf("讀取資料報文時發現遠端關閉,該執行緒終止!\n"); return -1; } close(sockfd); //在此處找到HTTP的RESPONSE結果碼,如果為200,則成功,擷取包體賦值到pRecvBuff;否則返回-1。 char *pRet = NULL; char *pStart = NULL; char *pEnd = NULL; //pRet = strstr(buf, "HTTP/1.1 200 OK"); pRet = strstr(buf, "HTTP/1.1 200"); if(!pRet) { cout << "HTTP Response Error!!!" << endl; return -1; } string sRecv; utf82gb(buf, sRecv); printf("sRecv is :\n%s\n", sRecv.c_str()); memset(buf, 0, sizeof(buf)); strncpy(buf, sRecv.c_str(), sRecv.length()); pStart = strstr(buf, "{"); printf("pStart address is :0x%x\n", pStart); pEnd = strrchr(buf, '}') + 1; printf("pEnd address is :0x%x\n", pStart); // if(pStart != NULL && pEnd != NULL) { strncpy(pRecvValue, pStart, (int)(pEnd - pStart)); //userlog("訊息返回成功-訊息,請求訊息:\n%s\n ******** 返回訊息:\n%s\n", szSendBuffer, buf); } else { printf("\nbuf中未找到匹配的資料!!!\n"); userlog("訊息返回失敗-訊息,請求訊息:\n%s\n ******** 返回訊息:\n%s\n", szSendBuffer, buf); return -2; } return 0;}