【Socket】linux下http服务器开发
2)工作原理
1)客户端
一台客户机与服务器建立连接后,会发送一个请求给服务器,请求方式的格式为:统一资源定位符(URL)、协议版本号,后边是MIME信息,包括请求修饰符、客户机信息和可能的内容。
2)服务器端
1)服务器接收到客户机的请求后,首先解析请求信息,根据不同的请求模式给予相应的响应信息。HTTP中规定了6种请求格式,但最常用到的是GET和POST请求
2)任何服务器除了包括HTML文件以外,还有一个HTTP驻留程序,用于响应用户的请求。
3)服务器端驻留程序接收到请求后,在进行必要的操作后回送所要求的文件,在这一过程中,在网络上发送和接收的数据已经被分成一个或多个数据包(Packet),每个数据包包括:要传送的数据;控制信息,即告诉网络怎样处理数据包。
3)在HTTP请求格式中,最重要的信息有两个,一个是请求的内容,另一个是请求的方法。
4)数据对是由字段组成,其格式为:valuename=value;数据对与数据对之间由&连接,这是HTTP中定义的请求规则。
5)在通常的WEB应用中,大数据量的提交一般选用POST方法,小数据量的提交,如查询操作,一般采用GET方法。
6)请求实体由实体名和实体值组成,它的格式为:Entity:value
2.HTTP服务器设计
1)实现功能1)在HTTP服务器中,经常要处理的请求是GET,MesteryServer中将要实现针对GET请求的处理
服务器并不支持CGI功能,所以针对GET中的CGI部分的请求处理,在本版本中不予支持。
2)利用Gtk编写可视化的界面,三个按钮用于启动、关闭和暂停服务,一个标签用于显示服务状态。
3)文件传输功能是本 服务器最基本的功能,负责可靠传送用户请求的资源文件。
4)具有日志功能,会将客户的请求信息存储到本地的日志文件里,以XML格式进行存储。
2)业务功能
1)针对GET请求功能,在这里只支持资源文件的下载,并且可以断点下载。
2)如果是请求资源,则予以响应;如果涉及CGI的请求,则不予以响应。
3)系统只支持GET请求,对于POST、HEAD、TRACE等请求,都予以忽略。
4)对于HTTP服务器,它的运行模式是基于请求、响应机制,而下面的文件传输功能,其实是请求的具体执行过程,当服务器端成功解析请求命令后,会针对请求的类型,生成相应的响应码,并传送相应的资源文件。
5)当请求工作执行完毕时,会形成一条日志记录,并插入到日志文件中。
6)考虑到服务的关联性,服务器将为每一个请求开启一个单独的线程进行服务,该线程实现客户端请求接受、请求分析、响应码生成与传输、响应文件查找与传输、客户套接字关闭、日志记录写入等功能
3)可视化界面
其设计与业务的逻辑采用松耦合,双方只是通过消息的方式,传递控制命令与状态信息。
4)主服务功能
提供端口绑定(HTTP默认端口是80)、服务侦听、客户端套接字维护、业务线程创建等。
5)界面模块
1)由两个子模块组成:界面显示子模块->绘出程序的运程界面;按钮事件处理子模块
2)界面模块与主服务模块之间的消息传递采用全局变量共享的方式。
6)主服务模块
1)以线程身份存在。
2)不断轮询全局变量gServerStatus,并通过这个状态来动态调整真实服务状态。
7)业务处理模块
1)程序核心部分。
2)由请求分析子模块、响应处理子模块、文件传输子模块、日志添加子模块等几个子模块组成。
3.测试效果
1)本地测试
3)下载数据预览
4.疑问解答
前几天群里有人在讨论TCP和UDP,然后又引出了HTTP服务器,于是我就给他们推荐了这篇文章,但是大家看过之后还是有很多疑问,这里我根据自己的理解简单描述下。
❶主服务模块的设计原理
可以看见,程序界面是用gtk写的,当点击“开始”按钮的时候,会动态创建该线程,其线程回调函数原型为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | void * server_process( void *p) { int serverSocket; 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); if (bind(serverSocket,( struct sockaddr *)&server_addr, sizeof (server_addr)) < 0) { perror ( "error: bind address !!!!" ); exit (1); } if (listen(serverSocket,5)<0) { perror ( "error: listen !!!!" ); exit (1); } gIsRun = 1; printf ( "MesteryServer is running.....\n" ); while (gIsRun) { int clientsocket; clientsocket = accept(serverSocket,( struct sockaddr *)&clientAddr,(socklen_t*)&addr_len); if (clientsocket < 0) { perror ( "error: accept client socket !!!" ); continue ; } if (gServerStatus == 0) { close(clientsocket); } else if (gServerStatus == 1) { pthread_t threadid; int temp; temp = pthread_create(&threadid, NULL, processthread, ( void *)&clientsocket); /*if(threadid !=0) { pthread_join(threadid,NULL); }*/ } } close(serverSocket); } |
从程序中可以看见,当绑定本地服务地址和端口后,便调用listen()函数进行侦听,while(gIsRun)表示主服务模块已经启动;然后采用阻塞式等待用户连接的到来,在连接到来的时候,还需要判断gServerStatus的值,即系统是否允许提供服务,如果允许,则创建服务线程。
pthread_create(&threadid, NULL, processthread, (void *)&clientsocket);该线程的回调函数为processthread(),具体如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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的格式进行逐步解析就可以了。
❸文件传输
文件传输是归在GET_COMMON类的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //解析请求信息并处理请求信息 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()函数实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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响应码,如果有,就返回200响应码,然后首先对prequest->rangeflag进行一个判断,看是否是断点续传,然后便开始传输文件,传输文件函数transferfile()如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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()函数来实现的。
1 2 3 4 5 6 7 8 9 10 | 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; } |
而在sendsegment()函数里面,就是用的socket里面的send()函数来实现的。
❹其它功能
对于其它的功能,比如日志操作的,就是属于文件类的了;响应码则是属于对返回信息的一个格式处理,只要按照HTTP协议来就可以了;界面则是用gtk绘制就行了,这个空间比较大,只有绑定相应按钮和处理函数就行了。
5.源代码
请到我原博客附件下载:http://infohacker.blog.51cto.com/6751239/1155176
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架