最近项目中须要本身去实现一个http的接口。因此趁这个机会跟你们讲一下http和socket的关系,以及与TCP又有什么联系。linux
首先你们必定要明确一点,在网络分层架构当中,HTTP协议是属于应用层的,tcp协议是属于传输层的,也就是说它们是一种协议,是通讯双方规定的一种规则,没有这种规则,两台主机就没法完成通讯。json
而根据咱们曾经所学的知识能够知道,两台主机要完成通讯,必须在传输层规定一套相同的协议,至于要不要在传输层就创建链接,因协议而异,tcp协议是须要创建链接的,而udp就不须要。至于tcp和udp的区别,不在本文的讨论范围,因此暂时不论。由于如今传输数据大部分都是使用tcp协议,因此tcp协议是很是重要的,必需要掌握。api
传输层使用tcp协议发送数据的话,首先要完成TCP的三次握手过程。为何要完成三次握手过程?为了保证数据传输的准确性,就是让数据能够准确无误的传输到另外一台主机。至于如何完成三次握手过程,这个知识点在网上有很是多的资料你们能够去百度看看。服务器
而三次握手创建链接,这更像是一种理论的过程,也就是说我告诉你三次握手的过程,可是你要帮我实现这个过程,那怎么实现呢?这个具体实现的过程就是靠socket来实现的,socket是操做系统为tcp封装的一整套创建链接,发送数据,断开链接的过程,它是对外提供的一个接口。注意我这里说的是操做系统,也就是说不一样的操做系统封装的socket接口函数可能有所不一样,这一点你们须要注意。在linux上使用最多的socket函数通常有socket()bind()listen()accept()connect()close()这几个函数,在window上略有不一样。网络
到这里不知道你们明白了没有,tcp只是传输层上的一个协议,是通讯双方互相规定的一种协议,而socket就是这种协议的具体实现过程。因此若是你足够牛逼,你能够本身给通讯双方的两台主机制定一套属于本身的传输层协议,而后本身写代码实现这个过程。但通常没有人会这么作,为何呢?由于这个工做量很是的恐怖,这个恐怖不是体如今制定协议以及写代码实现的这个过程,这个恐怖是体如今必须为通讯双方的两台机子都适配这种协议。服务端还好说,是都是本身的机子,控制权都在本身手上,并且通常都只使用linux系统,可是到客户端就完全宕机了,客户端确定不是就一台的,是千千万万台,并且还有不一样的操做系统,你要不就本身去一个个系统去适配你的协议,要不就是去斡旋各大操做系统厂商写入你的协议。因此这样的事也只有全球有影响力的企业,有影响力的组织才能够完成的,通常人不可能,也不必。架构
上面说了那么多,就是告诉同窗们,通讯双方要完成通讯,要先在传输层利用tcp协议创建链接。链接创建完成以后,就能够开始发送数据了,那么接着问题来了。app
1、若是我要发送不一样结构,不一样规则的数据的话,我要怎么发。socket
2、我发出去的数据,确定会收到一个回复,那么我怎么处理这个回来的数据。tcp
若是以上两个问题,你们不是很明白,不要紧,接下去往下看,你可能就明白了。函数
基于以上两个问题,就须要在应用层上制定一套属于通讯双方本身的协议了,而这套协议是规定双方发送接收的数据规则。http就是应用层一个很是经典的协议,它是因特网上应用最为普遍的一种网络传输协议,全部的WWW文件都必须遵照这个标准。固然应用层协议不只仅只有http,还有telnet,ftp,smtp等等这些都是很是经典的应用层协议,通讯双方都必须按照协议规定的数据格式来发送和接收。并且根据双方发送数据的需求,还能够制定属于本身的应用层协议,来知足本身的本地化需求。只要你有需求,应用层协议随便你添加。
那么为何添加传输层协议难如登天,而添加应用层协议却那么简单呢?
简单一句话归纳就是:传输层协议是操做系统级别的,而应用层协议是应用软件级别的。
因此添加一个传输层协议必定是一个浩大的工程,由于要在操做系统级别上更新。而添加一个应用层协议就比较简单了,由于只是添加在你所开发的软件或者app的客户端和服务端上。
用最生活化的例子来比喻,假如要从A地到B地,那么怎么过去呢,确定须要修建一条路,那么修建这条路所须要的设计图纸就至关于tcp,而工人们修建的过程就至关于socket,不能盲目修建,必须基于设计图纸来修建,而socket也必须基于tcp协议的理论,而修建一条道路是耗资巨大的工程,因此不可能随便的添加传输层协议。一旦道路修建完成,你能够采用各类方式过去,走路、跑步、骑自行车、开小车等等均可以,只要你开心,你要爬过去均可以,而采用何种方式过去就是应用层协议,http是其中的一种过去方式。
总的来讲,tcp是传输层的一个协议,而socket是这个协议的封装,能够对外提供接口,让应用程序调用,而http是应用层的一个协议,是一种对数据的封装。发起http请求的时候,底层的传输层要完成两台机子的链接,就是tcp三次握手完成链接。
如下我给出了一个http封装的例子,只有客户端,是当时作项目的时候写,你们能够参考参考
1 //http接口 2 int http_post_openapi(const char *pIP, const char *pServ, int port, const char *pSendValue, char *pRecvValue) 3 { 4 int sockfd, ret, i, h; 5 struct sockaddr_in servaddr; 6 char szHttpHead[1024], buf[8192], szSendValueLength[128],szSendBuffer[4096]; 7 int iLen = 0; 8 fd_set t_set1; 9 struct timeval tv; 10 11 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { 12 printf("建立网络链接失败,本线程即将终止---socket error!\n"); 13 exit(0); 14 }; 15 16 memset(&servaddr, 0, sizeof(servaddr)); 17 servaddr.sin_family = AF_INET; 18 servaddr.sin_port = htons(port); 19 if (inet_pton(AF_INET, pIP, &servaddr.sin_addr) <= 0 ){ 20 printf("建立网络链接失败,本线程即将终止--inet_pton error!\n"); 21 exit(0); 22 }; 23 24 if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0){ 25 printf("链接到服务器失败,connect error!\n"); 26 exit(0); 27 } 28 printf("与远端创建了链接\n"); 29 30 if (pSendValue == NULL || pRecvValue == NULL) 31 { 32 close(sockfd); 33 printf("\n传入的pSendValue或者pRecvValue为空!!!\n"); 34 return -1; 35 } 36 37 char szSendConver[4096] = {0}; 38 TrimAll(pSendValue, szSendConver); 39 40 iLen = strlen(szSendConver); 41 42 memset(szSendValueLength, 0, 128); 43 sprintf(szSendValueLength, "%d", iLen); 44 printf("szSendValueLength after sprintf is :%s\n", szSendValueLength); 45 46 memset(szHttpHead, 0, 256); 47 48 strcat(szHttpHead, "POST "); 49 strcat(szHttpHead, pServ); 50 strcat(szHttpHead, " HTTP/1.1\r\n"); 51 strcat(szHttpHead, "Host: "); 52 strcat(szHttpHead, pIP); 53 strcat(szHttpHead, ":"); 54 char cPort[6]; 55 sprintf(cPort,"%ld",port); 56 strcat(szHttpHead, cPort); 57 strcat(szHttpHead, "\r\n"); 58 strcat(szHttpHead, "User-Agent: Apache-HttpClient/4.1.1\r\n"); 59 strcat(szHttpHead, "Accept: */*\r\n"); 60 strcat(szHttpHead, "Content-Length: "); 61 strcat(szHttpHead, szSendValueLength); 62 strcat(szHttpHead, "\r\n"); 63 strcat(szHttpHead, "Content-Type: application/json; charset=UTF-8"); 64 printf("szSendValueLength is :%s\n", szSendValueLength); 65 66 strcat(szHttpHead, "\r\n\r\n"); 67 memset(szSendBuffer, 0, 4096); 68 strcat(szSendBuffer, szHttpHead); 69 strcat(szSendBuffer, szSendConver); 70 71 strcat(szSendBuffer, "\r\n\r\n"); 72 printf("Print SendBuffer before write :\n%s\n",szSendBuffer); 73 74 ret = write(sockfd,szSendBuffer,strlen(szSendBuffer)); 75 if (ret < 0) { 76 printf("发送失败!错误代码是%d,错误信息是'%s'\n",errno, strerror(errno)); 77 exit(0); 78 }else{ 79 printf("消息发送成功,共发送了%d个字节!\n\n", ret); 80 } 81 82 FD_ZERO(&t_set1); 83 FD_SET(sockfd, &t_set1); 84 85 memset(buf, 0, sizeof(buf)); 86 i= read(sockfd, buf, sizeof(buf)-1); 87 if (i==0) 88 { 89 close(sockfd); 90 printf("读取数据报文时发现远端关闭,该线程终止!\n"); 91 return -1; 92 } 93 94 close(sockfd); 95 //在此处找到HTTP的RESPONSE结果码,若是为200,则成功,截取包体赋值到pRecvBuff;不然返回-1。 96 char *pRet = NULL; 97 char *pStart = NULL; 98 char *pEnd = NULL; 99 //pRet = strstr(buf, "HTTP/1.1 200 OK"); 100 pRet = strstr(buf, "HTTP/1.1 200"); 101 if(!pRet) 102 { 103 cout << "HTTP Response Error!!!" << endl; 104 return -1; 105 } 106 107 string sRecv; 108 utf82gb(buf, sRecv); 109 printf("sRecv is :\n%s\n", sRecv.c_str()); 110 111 memset(buf, 0, sizeof(buf)); 112 strncpy(buf, sRecv.c_str(), sRecv.length()); 113 114 pStart = strstr(buf, "{"); 115 printf("pStart address is :0x%x\n", pStart); 116 117 pEnd = strrchr(buf, '}') + 1; 118 printf("pEnd address is :0x%x\n", pStart); 119 // 120 121 if(pStart != NULL && pEnd != NULL) 122 { 123 strncpy(pRecvValue, pStart, (int)(pEnd - pStart)); 124 //userlog("消息返回成功-消息,请求消息:\n%s\n ******** 返回消息:\n%s\n", szSendBuffer, buf); 125 } 126 else 127 { 128 printf("\nbuf中未找到匹配的数据!!!\n"); 129 userlog("消息返回失败-消息,请求消息:\n%s\n ******** 返回消息:\n%s\n", szSendBuffer, buf); 130 return -2; 131 } 132 133 return 0; 134 }
更多精彩内容,请关注同名公众:一点月光(alittle-moon)