首頁>技術>

最近專案中需要自己去實現一個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;}

6
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Python 自動控制任何軟體