socketexception:socket closed,socket常见问题
有限状态机-part的相关来源和参考在具体模块《Linux高性能服务器编程》-优爽定义维基百科中有规定:
在编程中,有限状态机是服务器程序的逻辑单元中的一种有效的编程方法。
个体被理解为控制程序执行的一个变量或一段程序,根据这个变量或程序的有限结果进行相应的操作。有些应用层协议头包含包类型字段,每种类型都可以映射到一个逻辑单元的执行状态,服务器可以据此编写相应的处理逻辑,如下面的代码所示:` `` ` c。
状态机(Package _pack){
PackageType _type=_pack。GetType();
开关(_type){
案例类型_A:
process _ package _ A(_ pack);
打破;
案例tyoe_B:
process _ package _ A(_ pack);
打破;
}
}
-如上图,一个简单的有限状态机,只不过它里面的每个状态都是相互独立的。`状态以前没有相互转移,状态转移需要由状态机在内部驱动。` --具有`状态转换的有限状态机的示例如下所示:``` c state _ machine(package _ pack){ state cur _ state=type _ a;而(cur_State!{ PackageType _type=_pack。GetType();switch(cur _ State){ case type _ A:process _ package _ State _ A(_ pack);cur _ State=type _ B;打破;案例类型_ B:process _ package _ state _ B(_ pack);cur _ State=type _ c;打破;默认:break}}}解释:状态机首先通过getNewPackage方法获得一个新的数据包,然后根据cur_State变量的值判断如何处理这个数据包。处理完成后,cur_State将被赋予一个新的值来实现状态转换。当状态机进入下一个周期时,将执行新状态对应的处理逻辑。示例有限状态机——HTTP请求读取和分析的一个应用实例。
HTTP不提供报头长度字段,报头长度变化很大。根据协议(如下图所示),我们根据遇到空行来判断HTTP头的结尾,空行只包含一对回车换行符。如果一次读取操作没有读取HTTP请求的整个头,也就是没有遇到空行,那么我们需要继续等待数据发送和读入。每次读操作完成后,需要确定是否有空行(空行前面是请求行和头字段),同时可以完成对整个HTTP请求头的分析。在下面的代码中,我们使用主从状态机来读取和分析简单的HTTP请求。我们把一行HTTP请求消息称为一行。
请求消息格式,图片来源——计算机网络微课堂(不带背景音乐的字幕版)。
主状态机负责判断请求行和头字段,调用相关函数进行处理。
处理完请求行后,状态将变为处理页眉字段。
主状态机使用checkstate记录当前状态,其初始状态为CHECK_STATE_REQUESTLINE。它先调用parse_line获取请求行的数据,然后调用parse_requestline进行分析,再将状态改为(状态转义)CHECK_STATE_HEADER(头字段分析),调用parse_line获取行数据,调用parse_headers进行分析。
也就是可以理解,在调用parse_line解析一行数据之前,我们已经知道该行数据是什么类型(请求行数据还是头字段数据)。
` ` c
//主状态机-分析http请求的入口函数
HTTP _ CODE parse _ content(char * buffer,int checked_index,CHECK_STATE checkstate,int read_index,int start_line ){
LINE _ STATUS linestatus=LINE _ OK//从状态机状态
HTTP _ CODE retcode=NO _ REQUEST
//如果没有读到最后,继续处理。
while((line status=parse _ line(buffer,checked _ index,read _ index))==line _ ok){//逐行开始解析。
//成功读取整行,进入此处。
char * szTemp=buffer start _ line//start_index是缓冲区中的起始位置。
start _ line=checked _ index//更新下标,即下一行的起始位置。
Switch (checkstate ){//主状态机的当前状态
check _ state _ request line:{//分析请求行-get/post.
retcode=parse _ request line(SZ temp,checkstate);//处理http请求的结果
if ( retcode==BAD_REQUEST ){
返回坏的请求;
打破;
case _ state _ header:{//分析头字段
retcode=parse _ headers(SZ temp);//每次读取的数据都会更新。
if ( retcode==BAD_REQUEST ){
返回坏的请求;
else if ( retcode==GET_REQUEST)
返回GET _ REQUEST
打破;
默认值://报告错误
返回INTERNAL _ ERROR
If( linestatus==LINE_OPEN ){//尚未读取完整的一行。
返回NO _ REQUEST//返回请求不完整
否则{
返回坏的请求;//客户请求中有语法错误。
}
}
# # #从状态机
-`从状态机负责解析出一行的内容。`
` ` c
//从状态机,用来解析出一行的内容。
LINE _ STATUS parse _ LINE(char * buffer,int checked_index,int read_index)
//read_index指向缓冲区中当前分析数据的下一个字节。
//checked_index指向缓冲区中当前分析数据中正在分析的字节。
//也就是说这里分析的是checked_index~read_index中的数据。
//字节分析
炭化温度;
for(;检查索引读取索引;已检查_索引)
temp=buffer[checked _ index];//获取当前需要分析的字节(字符)
If (temp==\r ){//Current \r表示可以读取一整行。
如果\r是到目前为止读取的最后一个数据,则表示尚未读取完整的一行。
您需要继续读取客户数据以进行进一步分析。
if((检查索引1 )==读取索引){
返回LINE _ OPEN//返回的数据不完整
//下一个是\n,表示我们读取一个完整的行。
else if(buffer[checked _ index 1]== \ n ){
buffer[checked _ index]= \ 0 ;//添加字符串终止符
buffer[checked _ index]= \ 0 ;
//读取一个完整的行,准备交给主状态机处理。
返回LINE _ OK//读取完整的一行
返回LINE _ BAD//否则返回当前行数据出错。
Else if( temp==\n ){//当前字符为\n,表示可以读取完整的一行。
//进一步判断
if((checked _ index 1)buffer[checked _ index-1]== \ r ){
buffer[checked _ index-1]= \ 0 ;
buffer[checked _ index]= \ 0 ;
返回LINE _ OK//是一个完整的行。
返回LINE _ BAD//线路错误
返回LINE _ OPEN//行数据不完整
}
` ` c
#包含sys/socket.h
#包含netinet/in.h
#包括arpa/inet.h
#include assert.h
#包含stdio.h
#包含stdlib.h
#包括unistd.h
#包含错误号h
#包含字符串. h
#包含fcntl.h
#define BUFFER_SIZE 4096 //读取缓冲区大小
//主状态机的2种可能状态
枚举检查状态{
CHECK_STATE_REQUESTLINE=0,//分析请求行
CHECK_STATE_HEADER,//分析头字段
//检查状态内容
};
//从状态机的3种可能状态中读取第3354行的状态
枚举LINE_STATUS {
LINE_OK=0,//读取一个完整的行
LINE_BAD,//line出错
LINE_OPEN //的数据不完整,也就是没有到/r/n /n。
};
//服务器处理http请求的结果,
枚举HTTP_CODE {
NO_REQUEST,//请求不完整,需要继续读取客户数据。
GET_REQUEST,//得到完整的客户请求。
BAD_REQUEST,//客户请求中有语法错误
FORBIDDEN_REQUEST,//客户没有足够的权限访问资源。
INTERNAL_ERROR,//服务器内部错误
CLOSED_CONNECTION //客户端已关闭连接。
};
//为了简化问题,这段代码没有向客户端发送完整的HTTP响应消息,只是根据服务器的处理结果发送以下成功或失败信息。
静态常量char* szret[]={
我得到了正确的结果\n,
有问题\n
};
//从状态机,用来解析出一行的内容。
LINE_STATUS parse_line(字符缓冲区,int checked_index,int read_index)
{
//read_index指向缓冲区中当前分析数据的下一个字节。
//checked_index指向缓冲区中当前分析数据中正在分析的字节。
//也就是说这里分析的是checked_index~read_index中的数据。
//字节分析
炭化温度;
for(;检查索引读取索引;已检查_索引)
{
temp=buffer[checked _ index];//获取当前需要分析的字节(字符)
If (temp==\r ){//Current \r表示可以读取一整行。
/
如果\r是到目前为止读取的最后一个数据,则表示尚未读取完整的一行。
您需要继续读取客户数据以进行进一步分析。
*/
if((检查索引1 )==读取索引){
返回LINE _ OPEN//返回的数据不完整
}
//下一个是\n,表示我们读取一个完整的行。
else if(buffer[checked _ index 1]== \ n ){
buffer[checked _ index]= \ 0 ;//添加字符串终止符
buffer[checked _ index]= \ 0 ;
//读取一个完整的行,准备交给主状态机处理。
返回LINE _ OK//读取完整的一行
}
返回LINE _ BAD//否则返回当前行数据出错。
}
Else if( temp==\n ){//当前字符为\n,表示可以读取完整的一行。
//进一步判断
if((checked _ index 1)buffer[checked _ index-1]== \ r ){
buffer[checked _ index-1]= \ 0 ;
buffer[checked _ index]= \ 0 ;
返回LINE _ OK//是一个完整的行。
}
返回LINE _ BAD//线路错误
}
}
返回LINE _ OPEN//行数据不完整
}
//分析请求行-格式:GET /index.html HTTP/1.1
HTTP _ CODE parse _ request line(char * SZ temp,CHECK_STATE checkstate ){
//szTemp=
//printf(test: %s\n ,SZ temp);
//在sztemp中搜索\t以查找返回位置的指针。
我一开始并没有考虑这是什么意思。有关详细信息,请参见http请求消息。
\t是空格。
char* szURL=strpbrk( szTemp, \ t );
如果(!SzURL ){ //如果请求行中没有空白字符或\t字符,那么HTTP请求一定有问题。
返回坏的请求;//请求中有语法错误
* szURL= \ 0//用\0覆盖\t,然后指向以下内容。
//此时szURL的内容是/index.html HTTP/1.1
char * szMethod=szTemp//保存请求的方法,该方法将被截断,直到\0。
If (strsecmp (szmethod, get )==0){//判断get请求
printf(请求方法为:GET \ n );
}否则{
返回坏的请求;//返回请求错误
//下一个请求标头
//跳过下一部分数据前面的多余空格
szURL=strspn( szURL, \ t );
//先获取http版本,skip/index.html。
char* szVersion=strpbrk( szURL, \ t );
如果(!szVersion ){
返回坏的请求;
* szVersion= \ 0//将\t替换为\0,
//跳过下一部分数据前面的多余空格
//此时szVersion=HTTP/1.1
//跳过http/1.1信息前面多余的空格
szVersion=strspn( szVersion, \ t );
//为什么这里没有长度限制,因为请求行的最后一段是http版本
//是http/1.1吗?
if ( strcasecmp( szVersion, HTTP/1.1 )!=0 ){
返回坏的请求;
//检查url是否合法
if(strncacecmp(szURL, http://,7 )==0 ){
SZ URL=7;
szURL=strchr( szURL,/);
如果(!szURL szURL[ 0 ]!=/ ){
返回坏的请求;
//URL decode(SZ URL);
printf(请求URL为:%s\n ,szURL);
//当处理HTTP请求行时,状态转移到头字段分析。
检查状态=检查状态标题;
返回NO _ REQUEST//NO _ REQUEST的当前返回没有意义。
}
//分析头字段
HTTP _ CODE parse _ headers(char * SZ temp){
If (szTemp[ 0]==\0 ){//遇到空行表示我们得到了正确的http请求。在标题的末尾,还有一个空行。
printf(测试\n
返回GET _ REQUEST
}
else if(strnccasecmp(SZ temp,Host:5 )==0 ){
SZ temp=5;
szTemp=strspn( szTemp,\ t);
printf(请求主机为:%s\n,SZ temp);
}
Else{//其他标头信息
//printf(我处理不了这个头\ n);
printf( %s\n,SZ temp);
}
返回NO _ REQUEST
}
//分析http请求的入口函数——从这里开始
/*
接收缓冲器
目前已经分析了多少字节的数据?
主状态机的初始状态
当前已经读取了多少字节的数据?
接收缓冲区中的起始位置。
*/
//主状态机-分析http请求的入口函数
HTTP _ CODE parse _ content(char * buffer,int checked_index,CHECK_STATE checkstate,int read_index,int start_line ){
LINE _ STATUS linestatus=LINE _ OK//从状态机状态
HTTP _ CODE retcode=NO _ REQUEST
//如果没有读到最后,继续处理。
while((line status=parse _ line(buffer,checked _ index,read _ index))==line _ ok){//逐行开始解析。
//成功读全了一行,进入到这里
char * szTemp=buffer start _ line//开始_索引是在缓冲器中的起始位置
start _ line=checked _ index//更新下标,下一行的起始位置。
开关(检查状态){//主状态机的当前状态
案例检查_状态_请求行:{//分析请求行-获取/发布.
retcode=parse _ request line(SZ temp,checkstate);//处理超文本传送协议(超文本传输协议的缩写)请求的结果
if ( retcode==BAD_REQUEST ){
返回坏的请求;
打破;
case CHECK_STATE_HEADER:{//分析头部字段
retcode=parse _ headers(SZ temp);//每次读取到的数据是会更新的。
if ( retcode==BAD_REQUEST ){
返回坏的请求;
else if ( retcode==GET_REQUEST)
返回获取请求
打破;
默认值://报错
返回内部错误
if( linestatus==LINE_OPEN ){//没有读取到完整的一行
返回NO _ REQUEST//返回请求不完整
否则{
返回坏的请求;//客户请求有语法错误
}
}
int main( int argc,char* argv[] ){
if( argc=2 ){
printf(用法:%s ip地址端口号\n ,basename(argv[0]);
返回1;
const char * IP=argv[1];
int port=atoi(argv[2]);
结构sockaddr_in地址;
bzero(地址,sizeof(地址));
地址. sin _ family=AF _ INET
inet_pton( AF_INET,ip,地址。sin _ addr);
地址。sin _ port=htons(port);
int listenfd=socket( PF_INET,SOCK_STREAM,0);
断言(listen FD=0);
int ret=bind( listenfd,(struct sockaddr* ) address,sizeof(address));
断言(ret!=-1 );
ret=listen( listenfd,5);
断言(ret!=-1 );
struct sockaddr _ in client _ address;
socklen _ t client _ addrlength=sizeof(client _ address);
int fd=accept( listenfd,(struct sockaddr* ) client_address,client _ addr length);
if( fd 0 ){
printf(错误号为:%d\n ,错误号);
否则{
char BUFFER[BUFFER _ SIZE];//接收缓冲区
memset( buffer, \0 ,BUFFER _ SIZE);
int data _ read=0;
int read _ index=0;//当前已经读取了多少字节的客户数据
int checked _ index=0;//当前已经分析完了多少字节的客户数据
int start _ line=0;//在接收缓冲区中的起始位置
检查状态检查状态=检查状态请求行;//设置主状态机的初识状态-分析请求行
while( 1 ){//循环读取数据并进行分析
data_read=recv( fd,buffer read_index,BUFFER_SIZE - read_index,0);
if ( data_read==-1 ){
printf(读取失败\ n’);
打破;
else if ( data_read==0 ){
printf(远程客户端已关闭连接\ n’);
打破;
读取索引=数据_读取;//更新当前读取数据的数量
HTTP _ CODE result=parse _ content(buffer,checked_index,checkstate,read_index,start _ line);//
if( result==NO_REQUEST ){//继续读取数据
继续;
} else if(result==GET _ REQUEST){//获得了一个完整的客户请求
send( fd,szret[0],strlen( szret[0]),0);
打破;
}else{//错误
send( fd,szret[1],strlen( szret[1]),0);
打破;
关闭(FD);
关闭(listenfd);
返回0;
}
### 补充与解释
-我们在主状态机内部调用从状态机,使用从状态机解析一行数据,其可能的状态与状态转移如下图所示:
![image-20221003113025361](https://s 2.51 CTO。com/images/blog/202210/03123259 _ 633 a 65 FB 925 c 787379。png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type _ zm fuz 3 poz w5 nagvpdgk=/resize,m_fixed,w_750)
-使用读取索引、检查索引、开始行、数据读取来控制缓冲器中的数据读取范围。详见代码中的注释。
-主状态机可能的状态以及状态转义如下图所示:
![image-20221003113630471](https://s 2.51 CTO。com/images/blog/202210/03123259 _ 633 a 65 FB 9551d 4469。png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type _ zm fuz 3 poz w5 nagvpdgk=/resize,m_fixed,w_750)
-大致执行流程如下图所示,循环判断等详细信息并未体现。
![image-20221003113747616](https://s 2.51 CTO . com/images/blog/202210/03123259 _ 633 a 65 FB 946 f 260913 . png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type _ zmfuz 3 poz w5 nagvpdgk=/resize,m_fixed,w_750)
# #相关功能补充
### strpbrk
-Function:在字符串s中查找指定字符串accept的第一个匹配项。
-功能原型:
#包含字符串. h
char *strpbrk(const char *s,const char * accept);
//实际被编译器解析为const指针,const char * test
//为什么可以在这段代码中修改,因为szTemp指向buffer!
//按照书写的顺序很容易记住
//const type* xx const pointer(指向常量的指针)指向不能修改的东西,但是所指向的地址可以改变。
//type* const xx指针常量(指针是常量)。可以修改所指向的内容,但不能改变所指向的地址。
用char*直接创建的字符串实际上是一个const指针,比如char * c1= test实际上const char * c1= test不能修改其中指向的内容。如果用指针修改字符串,实际上应该是char c1[],也就是char类型的数组,或者char*指向char类型的数组。
*/
` ` c
//SZ temp:GET/index . html HTTP/1.1
//在sztemp中搜索\t以查找返回位置的指针。
char szURL=strpbrk( szTemp,\ t);
如果(!szURL)返回BAD _ REQUEST
szURL= \ 0//用\0覆盖\t,然后指向以下内容。
char * szMethod=szTemp//保存请求的方法,该方法将被截断,直到\0。
//szMethod: GET
由于多平台发布,发布后如有错误或修改,可能无法及时更新所有平台内容。你可以从我的个人博客获得最新文章3354【半神瓜blog](banshengua.top】。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。