首先,简单介绍基于HTTP协议的客户/服务器模式的信息交换过程,它分四个过程(建发响关),建立连接、发送请求信息、发送响应信息、关闭连接;在WWW中,“客户”与“服务器”是一个相对的概念,只存在于一个特定的连接期间,即在某个连接中的客户在另一个连接中可能作为服务器。WWW服务器运行时,一直在TCP 80端口(WWW的缺省端口)监听-listen,等待连接的出现。
其次。http是基于tcp的协议(tcp是传输层协议-osi第四层,http是osi第7层),所以具备tcp协议的特征,为了方便理解,查看之前文章https://blog.csdn.net/xiaoxilang/article/details/80839797;故而:
tcp的服务端(sbla(撕逼啦) rwrc)和客户端(sc wrc)的代码过程:
常常结合json 格式进行传输https://mp.csdn.net/editor/html/105176546
1 服务器端:代码如下:sbla如此:
pthread_create-->processthread()-->GET_COMMON类-->processgetcommon()解析函数-->传输文件函数transferfile() 应答-->传输文件,是调用sendsegment()函数来实现-->就是用的socket里面的send()函数
void* server_process(void *p)
{
int serverSocket; //**** 1-s-socket初始化-创建一个套接字 ****
struct sockaddr_in server_addr;
struct sockaddr_in clientAddr;
int addr_len = sizeof(clientAddr);
if((serverSocket = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
perror( "error: create server socket!!!");
exit(1);
}
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family =AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//**** 2-b-bind绑定-设置地址族和端口号等****
if(bind(serverSocket,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
{
perror("error: bind address !!!!");
exit(1);
}
//**** 3-l-listen 监听来自客户端的请求--客户端主动发连接请求 ****
if(listen(serverSocket,5)<0)
{
perror("error: listen !!!!");
exit(1);
}
gIsRun = 1;
printf("MesteryServer is running.....\n");
while(gIsRun)
{
int clientsocket;
//**** 4-a-accept 监听到 马上接收数据 ****
clientsocket = accept(serverSocket,(struct sockaddr *)&clientAddr,(socklen_t*)&addr_len);
if(clientsocket < 0)
{
perror("error: accept client socket !!!");
continue;
}
if(gServerStatus == 0) //本服务器状态如果不良可以直接关闭socket的服务
{
close(clientsocket);
}
else if(gServerStatus == 1)
{
pthread_t threadid;
int temp;
//**** 5-wrc-创建一个线程处理接收到的数据--这是按http协议解析!****
temp = pthread_create(&threadid, NULL, processthread, (void *)&clientsocket);
/*if(threadid !=0)
{
pthread_join(threadid,NULL);
}*/
}
}
close(serverSocket);
}
程序中可以看见,初始化套接字S后,当绑定B本地服务地址和端口后,便调用listen()函数L进行侦听A,while(gIsRun)表示主服务模块已经启动;然后采用阻塞式等待用户连接的到来,在连接到来的时候,还需要判断gServerStatus的值,即系统是否允许提供服务,如果允许,则创建服务线程。pthread_create(&threadid, NULL, processthread, (void *)&clientsocket);该线程的回调函数为processthread()
void* processthread(void *para)
{
int clientsocket;
char buffer[1024];
int iDataNum =0;
int recvnum=0;
clientsocket = *((int *)para);
printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<BEGIN [%d]>>>>>>>>>>>>>>>>>>>>>>>\n",clientsocket);
struct HttpRequest httprequest;
httprequest.content = NULL;
httprequest.path = NULL;
httprequest.path = (char *)malloc(1024);
httprequest.rangeflag = 0;
httprequest.rangestart = 0;
while(1)
{
iDataNum = recv(clientsocket,buffer+recvnum,sizeof(buffer)-recvnum-1,0);
if(iDataNum <= 0)
{
close(clientsocket);
pthread_exit(NULL);
return 0;
}
recvnum += iDataNum;
buffer[recvnum]='\0';
if(strstr(buffer,"\r\n\r\n")!=NULL || strstr(buffer,"\n\n")!=NULL)
break;
}
printf("request: %s\n",buffer);
//解析请求信息并处理请求信息
switch(getrequest(buffer,&httprequest))
{
case GET_COMMON:
processgetcommon(clientsocket,&httprequest);
break;
case GET_CGI:
processgetcgi(clientsocket,&httprequest);
break;
case POST:
processpost(clientsocket,&httprequest);
break;
case HEAD:
processhead(clientsocket,&httprequest);
break;
default:
break;
}
insertlognode(pfilelog,&httprequest);
if(httprequest.path != NULL)
free(httprequest.path);
if(httprequest.content != NULL)
free(httprequest.content);
close(clientsocket);
printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<END [%d]>>>>>>>>>>>>>>>>>>>>>>>\n",clientsocket);
pthread_exit(NULL);
}
可以看见,在这个线程里面,便开始对请求进行业务分析了。
❷协议解析 这个比较简单,
根据 HTTP 标准,HTTP 请求可以使用多种请求方法。
HTTP1.0 定义了三种请求方法: GET(文件传输内容处理), POST 和 HEAD方法。
HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。
因为HTTP协议的格式是固定的,所以只用对其按照HTTP的格式进行逐步解析就可以了。
❸文件传输
文件传输是归在GET_COMMON类的
//解析请求信息并处理请求信息
switch(getrequest(buffer,&httprequest))
{
case GET_COMMON:
processgetcommon(clientsocket,&httprequest); // 文件传输
break;
case GET_CGI:
processgetcgi(clientsocket,&httprequest);
break;
case POST:
processpost(clientsocket,&httprequest);
break;
case HEAD:
processhead(clientsocket,&httprequest);
break;
default:
break;
}
processgetcommon()函数实现如下:
void processgetcommon(int s,struct HttpRequest *prequest)
{
//先判断文件是否存在
FILE *fp = isexistfile(prequest->path);
printf("%s\n",prequest->path);
struct stat finfo;
if(fp == NULL)
{
responsecode(s,404,prequest);
}
else
{
if(prequest->rangeflag == 0)
{
stat(prequest->path,&finfo);
prequest->rangetotal = finfo.st_size;
}
responsecode(s,200,prequest);
transferfile(s,fp,prequest->rangeflag,prequest->rangestart,prequest->rangetotal);
fclose(fp);
}
}
它先会判断有没有这个文件,如果没有,就生成404响应码,其他
当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含HTTP状态码的信息头(server header)用以响应浏览器的请求。
HTTP状态码的英文为HTTP Status Code。
下面是常见的HTTP状态码:(其他的状态码见:https://www.runoob.com/http/http-status-codes.html)
- 200 - 请求成功
- 301 - 资源(网页等)被永久转移到其它URL
- 404 - 请求的资源(网页等)不存在
- 500 - 内部服务器错误
如果有,就返回200响应码,然后首先对prequest->rangeflag进行一个判断,看是否是断点续传,然后便开始传输文件,传输文件函数transferfile()如下
int transferfile(int s,FILE *fp,int type,int rangstart,int totallength)
{
if(type == 1)
{
//为1,则表示当前从指定的位置传送文件
fseek(fp,rangstart,0);
}
int sendnum = 0;
int segment = 1024;
while(!feof(fp)&&sendnum < totallength)
{
char buf[segment];
memset(buf,0,1024);
int i = 0;
while(!feof(fp) && i < segment && sendnum+i < totallength)
{
buf[i++] = fgetc(fp);
}
if(sendsegment(s,buf,i) == 0)
return 0;
sendnum += i;
}
return 1;
}
可以看见,具体的传输文件,是调用sendsegment()函数来实现的;而在sendsegment()函数里面,就是用的socket里面的send()函数来实现的
int sendsegment(int s, char *buffer,int length)
{
if(length <= 0)
return 0;
printf("%s\n",buffer);
int result = send(s,buffer,length,0);
if(result < 0)
return 0;
return 1;
}
2.客户端代码 实现如下
sc wrc
#include <stdio.h>
#include "winsock2.h"
#pragma comment(lib, "ws2_32.lib")
#pragma warning(disable:4996)
int main()
{
SOCKET sSocket = INVALID_SOCKET;
SOCKADDR_IN stSvrAddrIn = { 0 }; /* 服务器端地址 */
char sndBuf[524] = { 0 };
char rcvBuf[2048] = { 0 };
char* pRcv = rcvBuf;
int num = 0;
int nRet = SOCKET_ERROR;
WSADATA wsaData;
/* HTTP 消息构造开始,这是程序的关键之处 */
sprintf_s(sndBuf, 1024, "GET / HTTP/1.1\n");
strcat_s(sndBuf, 1024, "Host: www.baidu.com\r\n\r\n");
/* HTTP 消息构造结束 */
/* socket DLL初始化 */
WSAStartup(MAKEWORD(2, 0), &wsaData);
stSvrAddrIn.sin_family = AF_INET;
stSvrAddrIn.sin_port = htons(80);
stSvrAddrIn.sin_addr.s_addr = inet_addr("14.215.177.39");
sSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
/* 连接 */
nRet = connect(sSocket, (SOCKADDR*)&stSvrAddrIn, sizeof(SOCKADDR));
if (SOCKET_ERROR == nRet)
{
printf("connect fail!/n");
return -1;
}
/* 发送HTTP请求消息 */
send(sSocket, (char*)sndBuf, sizeof(sndBuf), 0);
/* 接收HTTP响应消息 */
while (1)
{
num = recv(sSocket, pRcv, 548, 0);
pRcv += num;
if ((0 == num) || (-1 == num))
{
break;
}
}
/* 打印响应消息 */
printf("%s/n", rcvBuf);
return 0;
}
找到ip地址,咋vs2019上运行、stSvrAddrIn.sin_addr.s_addr = inet_addr("14.215.177.39");
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 14615
Content-Type: text/html
Date: Thu, 14 Jan 2021 14:40:45 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Pragma: no-cache
Server: BWS/1.1
Set-Cookie: BAIDUID=49AC41EE41B4E747F9263F40C5AF2C7B:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=49AC41EE41B4E747F9263F40C5AF2C7B; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1610635245; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BAIDUID=49AC41EE41B4E7479BE1D7C055B046A4:FG=1; max-age=31536000; expires=Fri, 14-Jan-22 14:40:45 GMT; domain=.baidu.com; path=/; version=1; comment=bd
Traceid: 1610635245047215053811254122749023909846
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1
<!DOCTYPE html><!--STATUS OK-->
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<link rel="dns-prefetch" href="//s1.bdstatic.com"/>
<link rel="dns-prefetch" href="//t1.baidu.com"/>
<link rel="dns-prefetch" href="//t2.baidu.com"/>
<link rel="dns-prefetch" href="//t3.baidu.com"/>
<link rel="dns-prefetch" href="//t10.baidu.com"/>
<link rel="dns-prefetch" href="//t11.baidu.com"/>
<link rel="dns-prefetch" href="//t12.baidu.com"/>
<link rel="dns-prefetch" href="//b1.bdstatic.com"/>
<title>鐧惧害涓€涓嬶紝浣犲氨鐭ラ亾</title>
<link href="http://s1.bdstatic.com/r/www/cache/static/home/css/index.css" rel="stylesheet" type="text/css" />
<!--[if lte IE 8]><style index="index" >#content{height:480px\9}#m{top:260px\9}</style><![endif]-->
<!--[if IE 8]><style index="index" >#u1 a.mnav,#u1 a.mnav:visited{font-family:simsun}</style><![endif]-->
<script>var hashMatch = document.location.href.match(/#+(.*wd=[^&].+)/);if (hashMatch && hashMatch[0] && hashMatch[1]) {document.location.replace("http:/R$+location.host+"/s?"+hashM[1]);}var ns_c = function(){};</script>
<script>function h(obj){obj.style.behavior='urWinSock 2.0/n