socketexception-socket closed,socket常见问题

  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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • office2010激活密钥大全 怎么永久激活office2010
  • project2010产品密钥免费_project2010激活密钥永久激活码
  • c语言调用退出函数 c语言退出整个程序怎么写
  • c语言中怎么给函数初始化 c语言的初始化语句
  • c语言编写函数计算平均值 c语言求平均函数
  • chatgpt是什么?为什么这么火?
  • ChatGPT为什么注册不了?OpenAI ChatGPT的账号哪里可以注册?
  • OpenAI ChatGPT怎么注册账号?ChatGPT账号注册教程
  • chatgpt什么意思,什么是ChatGPT ?
  • CAD中怎么复制图形标注尺寸不变,CAD中怎么复制图形线性不变
  • cad中怎么创建并使用脚本文件,cad怎么运行脚本
  • cad中快速计算器的功能,cad怎么快速计算
  • cad中快速修改单位的方法有哪些,cad中快速修改单位的方法是
  • cad中心点画椭圆怎么做,cad轴测图怎么画椭圆
  • CAD中常用的快捷键,cad各种快捷键的用法
  • 留言与评论(共有 条评论)
       
    验证码: