`
fishermen
  • 浏览: 374829 次
社区版块
存档分类
最新评论

HTTP协议与sockt读取http包

阅读更多
很好的http介绍:)

一、超文本传输协议及HTTP包
    HTTP协议用于在Internet上发送和接收消息。HTTP协议是一种请求-应答式的协议——客户端发送一个请求,服务器返回该请求的应答,所有的请求与应答都是HTTP包。HTTP协议使用可靠的TCP连接,默认端口是80。HTTP的第一个版本是HTTP/0.9,后来发展到了HTTP/1.0,现在最新的版本是HTTP/1.1。HTTP/1.1由RFC 2616 定义。
    在HTTP中,Client/Server之间的会话总是由客户端通过建立连接和发送HTTP请求包初始化,服务器不会主动联系客户端或要求与客户端建立连接。浏览器和服务器都可以随时中断连接,例如,在浏览网页时你可以随时点击“停止”按钮中断当前的文件下载过程,关闭与Web服务器的HTTP连接。
  1 HTTP请求包
  HTTP请求包(GET、POST等请求方法)由三个部分构成,分别是:方法-URI-协议/版本,请求头,请求正文。下面是一个HTTP请求包(GET)的例子:
GET /index.jsp HTTP/1.1
Accept-Language: zh-cn
Connection: Keep-Alive 
Host: 192.168.0.106
Content-Length: 37 
userName=new_andy&password=new_andy

    请求包的第一行是方法-URI-协议/版本:
    GET就是请求方法,根据HTTP标准,HTTP请求可以使用多种请求方法。HTTP 1.1支持七种请求方法:GET、POST、HEAD、OPTIONS、PUT、DELETE和TRACE等,常用的为请求方法是GET和POST。
    /index.jsp表示URI。URI指定了要访问的网络资源。
    HTTP/1.1是协议和协议的版本。
    最后一行userName=new_andy&password=new_andy为正文,正文与HTTP头部有一个空行(\r\n)分隔。这里需要说明的一点,其中Content-Length说明正文的长度,有的正文长度没有在头部说明,只是标明Transfer-Encoding: chunked。关于chunked类型的长度计算方法,见RFC 1626。
    请求包的头部还会包含许多有关客户端环境和请求正文的有用信息,这里不再描述。
  2 HTTP应答包

  和HTTP请求包相似,由三个部分构成,分别是:协议-状态代码-描述,应答头,应答正文。下面是一个HTTP应答的例子:

HTTP/1.1 200 OK
Server: Microsoft-IIS/4.0
Date: Mon, 3 Jan 2005 13:13:33 GMT
Content-Type: text/html
Last-Modified: Mon, 11 Jan 2004 13:23:42 GMT
Content-Length: 90

<html>
<head>
<title>解读HTTP包示例</title></head><body>
Hello WORLD!
</body>
</html>


  HTTP应答包的第一行类似于HTTP请求的第一行,表示所用的协议是HTTP 1.1,服务器处理请求的状态码200。
  应答头也和请求头一样包含许多有用的信息,例如服务器类型、日期时间、内容类型和长度等。应答的正文就是服务器返回的HTML页面。应答头和正文之间也用CRLF分隔。
二、Socket类与ServerSocket类
  在Java中,通信端点由java.net.Socket类(客户端)或java.net.ServerSocket类(服务器端)表示。应用程序通过端点向网络发送或从网络读取数据。位于两台不同机器上的应用软件通过网络连接发送和接收字节流,从而实现通信。要把HTTP包发送给另一个应用,首先要知道对方的IP地址以及其通信端点的端口号。
   Socket类代表的是客户端,它是一个连接远程服务器应用时临时创建的端点。
   ServerSocker类代表的是服务器端,它启动后等待来自客户端的连接请求;一旦接收到请求,ServerSocket创建一个Socket实例来处理与该客户端的通信。对于服务器应用,我们不知道客户端应用什么时候会试图连接服务器,服务器必须一直处于等待连接的状态。

  下面是ServerSocket提供了四个构造函数,常用的构造函数的的一种形式为:
  public ServerSocket(int port, int backLog, InetAddress bindingAddress);
  参数:port指定服务器端监听客户端的端口;
  backlog为连接请求的最大队列长度,一旦超越这个长度,服务器端点开始拒绝客户端的连接请求。
  bindingAddress是一个java.net.InetAddress的实例,指定绑定IP地址。
   创建好ServerSocket实例之后,调用它的accept方法,要求它等待传入的连接请求。只有出现了连接请求时,accept方法才会返回,它的返回值是一个Socket类的实例。随后,这个Socket对象就可以用来与客户端应用通信。
 
  Socket类有许多构造函数,常用的为:
  public Socket(String host, int port)。参数是主机名称(IP地址或域名)和端口号。
   参数host是远程机器的名字或IP地址,port是远程应用的端口号。
  成功创建了Socket类的实例之后,我们就可以用它来发送和接收字节流形式的数据,数据一般为HTTP包。
  
   要发送字节流,首先要调用Socket类的getOutputStream方法获得一个java.io.OutputStream对象;要从连接的另一端接收字节流,首先要调用Socket类的getInputStream方法获得一个java.io.InputStream对象。
  下面的代码片断创建一个与本地HTTP服务器(127.0.0.1代表本地主机的IP地址)通信的Socket,发送一个HTTP请求包,准备接收服务器的应答。
  Socket socket    = new Socket("127.0.0.1", "80");
  OutputStream os  = socket.getOutputStream();
  InputStream  ins = socket.getInputStream();
  StringBuffer sb=new StringBuffer();
  sb.append("GET /index.jsp HTTP/1.1\r\n");//注意\r\n为回车换行
  sb.append("Accept-Language: zh-cn\r\n");
  sb.append("Connection: Keep-Alive\r\n");
  sb.append("Host: 192.168.0.106\r\n");
  sb.append("Content-Length: 37\r\n");
  sb.append("\r\n");
  sb.append("userName=new_andy&password=new_andy\r\n");
  sb.append("\r\n");
 
  //向Web服务器发送一个HTTP请求包
  os.write(sb.toString().getBytes()); 
 
  服务器端的代码在大致结构为:
  while (!shutdown) {
        Socket socket = null;
        try {
            socket = serverSocket.accept(); //等待客户以送HTTP请求包
            // 创建HTTP请求包处理线程
            RequestThread request = new RequestThread(socket);
            request.start();
            if(shutdown) System.exit(0);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
  RequestThread线程分析HTTP请求包,跟根据请求包内容在服务端生成一个HTTP应答包。下一节说明怎样分析HTTP包。
  InputStream  input = socket.getInputStream();  //从此字节数据流获得HTTP请求包内容
    OutputStream output= socket.getOutputStream(); //向此字节流写入HTTP应答包内容
   
三、读取HTTP包
  以下我自己设计的一个读取HTTP包的类SocketRequest。
  public class SocketRequest {  //从指定的Socket的InputStream中读取数据

  private InputStream  input;
  private String     uri;
  private StringBuffer  request=new StringBuffer();  //用于保存所有内容
  private int       CONTENT_LENGTH=0;  //实际包内容数据长
  private boolean    bePost = false;
  private boolean    beHttpResponse = false;
  private boolean    beChucked = false;
  private boolean    beGet = false;
  private byte       crlf13 = (byte)13; //'\r'
  private byte       crlf10 = (byte)10;  //'\n'

  public SocketRequest(InputStream input) {
    this.input = input;
  }
public SocketRequest(Socket socket) {
    this.input = socket.getInputStream();
  }

  public void ReadData() {  //解析 获得InputStream的数据

  ReadHeader();  //头部

  if(beChucked) //为Chucked
  {
   int ChuckSize=0;
   while((ChuckSize=getChuckSize())>0) //多个Chucked
   {
    readLenData(ChuckSize+2);//读取定长数据
   }
   readLenData(2); //最后的2位 
  }
 
  if(CONTENT_LENGTH>0)
  {
   readLenData(CONTENT_LENGTH);//读取定长数据
  }
 
    uri = "";//parseUri(new String(request));
  }
 
  private void readLenData(int size)  //读取定长数据
  {
   int readed=0;  //已经读取数
   try{
    int available=0;//input.available(); //可读数
    if(available>(size-readed)) available=size-readed;
    while( readed<size )
     {
       while(available==0){  //等到有数据可读
         available = input.available(); //可读数
        }
        if(available>(size-readed)) available= size-readed; //size-readed--剩余数
        if(available>2048) available= 2048; //size-readed--剩余数
       byte[] buffer = new byte[available];
       int reading = input.read(buffer);
       request=request.append(new String(buffer,0,reading));  //byte数组相加
        readed+=reading;  //已读字符
   }
   }catch(IOException e){
     System.out.println("Read readLenData Error!");
   }
}

   private void  ReadHeader() //读取头部 并获得大小
   {
    byte[]  crlf   = new byte[1];
   int     crlfNum= 0;   //已经连接的回车换行数 crlfNum=4为头部结束
    try{
     while( input.read(crlf)!=-1 )   //读取头部
     {
      if(crlf[0]==crlf13 || crlf[0]==crlf10)
      {
         crlfNum++;
      }
      else
      {  crlfNum=0;  } //不是则清
      request=request.append(new String(crlf,0,1));  //byte数组相加
      if(crlfNum==4) break;
     }
   }catch(IOException e){
     System.out.println("Read Http Header Error!");
     return;
    }
  
    String tempStr=(new String(request)).toUpperCase();
   
    //这里我只处理了GET与POST方法
    String  strMethod  = tempStr.substring(0,4);
    if(strMethod.equals("GET ")) //前
    {  beGet=true;   
    }
    else if(strMethod.equals("POST"))
    {
     bePost=true;
     getContentlen_Chucked(tempStr);
    }
    else {
     System.out.println("不支持的HTTP包类型");
    
    } //其它的其它类型 暂不支持
  }

  private void getContentlen_Chucked(String tempStr)  //获得长度 CONTENT-LENGTH 或 是否为CHUNKED型
  {
   String ss1="CONTENT-LENGTH:";
   String ss2=new String("TRANSFER-ENCODING: CHUNKED");
  
   int clIndex   = tempStr.indexOf(ss1);
    int chuckIndex = tempStr.indexOf(ss2);  //为CHUNKED型
    byte requst[]= tempStr.getBytes();
    if(clIndex!=-1)
    { //从clIndex+1起至\r\n
       StringBuffer sb=new StringBuffer();
      
       for(int i=(clIndex+16);;i++)
       {
        if(requst[i]!=(byte)13 && requst[i]!=(byte)10 )
        {
          sb.append((char)requst[i]);
        }
        else
         break;
       }
      
       CONTENT_LENGTH=Integer.parseInt(sb.toString());  //正式的HTML文件的大小
       //System.out.println("CONTENT_LENGTH==  "+CONTENT_LENGTH);
   }
   if(chuckIndex!=-1) beChucked=true;
  }
  
  private int  getChuckSize() //Chuck大小
   {
    byte[]  crlf   = new byte[1];
    StringBuffer  sb1   = new StringBuffer();

    int     crlfNum= 0;   //已经连接的回车换行数 crlfNum=4为头部结束
   
    try{
     while(input.read(crlf)!=-1)   //读取头部
     {
      if(crlf[0]==crlf13 || crlf[0]==crlf10)
      {  crlfNum++; }
      else
      {  crlfNum=0;  } //不是则清
      sb1.append((char)crlf[0]);
      request=request.append(new String(crlf,0,1));  //byte数组相加
      if(crlfNum==2) break;
     }
   }catch(IOException e){
     System.out.println("Read Http Package Error!");
     return 0;
    }
  
   return Integer.parseInt((sb1.toString()).trim(),16); //16进控制
}
  //通过此来进行过滤,是否为发至目标服务器的HTTP包
  private String parseUri(String requestString) {
    int index1, index2;
    index1 = requestString.indexOf(' ');
    if (index1 != -1) {
      index2 = requestString.indexOf(' ', index1 + 1);
      if (index2 > index1)
        return requestString.substring(index1 + 1, index2);
    }
    return null;
  }

  public String getData() {
    return request.toString();
  }
}

使用此类:
SocketRequest request = new SocketRequest(socket); //socket为ServerSocket.accept()返回的Socket实例
request.ReadData();  //读取数据
request.getData();
为什么我要用这么大的力量去读取呢,尤其是在因为Socket连接在发送数据时,由于网络的原因经常会发生延迟现象,可能在服务器端开始接收数据时可能只有部分数据可以从InputStream中获得,在一些地方处理不当时,可能只能获得不完整的数据或是错误的数据。
从InputStream读取字节时有多种办法:
常用int read()与int read(byte[] b)。在用read(byte[])时,程序员经常会犯错误,因为在网络环境中,读取的数据量不一定等于参数的大小。

希望我的这篇文章能给你带来一些帮助。
分享到:
评论
2 楼 fishermen 2008-07-29  
http本来不存在所谓长连接,这仅仅是一种特殊情况下的需求,然后有一些模拟解决技术。
理论上讲,一般有两种解决方案,你已经说了,其中后面那种可以参考开源的comet:pushlet。
在实际使用中,其实还有一些其他的折中方案,如:使用HttpClient作为请求端,然后使用对象池缓存一些请求对象,需要的时候从对象池取;或者直接缓存httpConnection,使用head请求,貌似在一些广告系统中常用。
使用常连接,压力主要在server端,要注意监控和均衡负载。
1 楼 jonson 2008-07-14  
兄台,我想问一句:http协议中 浏览器发送一个 http请求,与服务器建立链接,服务器返回请求结果,服务器结束链接。        这个过程就是普通的请求过程。    

但是如何建立一个所谓的 长链接呢   。现在所谓的commet应用原理如何呢。

如果是用ajax来实现长链接有两种方式:一种是 当ajax处于状态 3时(数据预备状态时)进行传送数据。因为没有转变为 状态4(完成)那么链接将在一段时间内不被释放。  或者使用 轮询发送请求的方式,即当一次请求结束了后,马上发起一次新的请求。   
观察此种种,长链接是模拟出来的一个链接概念。 只要持续的请求数据,是否就可以称之谓长链接

还有一种就是实现一种特殊的服务器,当每次请求结果返回后不结束本次链接。 我想这也是长链接的一种形式吧


看到兄台写的关于HTTP的文章很不错。所以想和你讨论下。

相关推荐

    C# 读写西门子PLC数据,包含S7协议,s7支持200smart,300PLC,1200PLC,1500PLC

    C# 读写西门子PLC数据,包含S7协议,s7支持S7-200,S7-200smart,S7-300PLC,S7-400PLC,S7-1200PLC,S7-1500PLC 使用一个开源的技术来读写西门子plc数据,使用的是基于以太网的TCP/IP实现,不需要额外的组件,读取...

    【二】Opencv结合socket进行视频传输(TCP协议)——附件包

    本附件包配套博文,博文详见:http://blog.csdn.net/hujingshuang/article/details/43193747

    c_socket编程入门

    其实,Socket可以象流Stream一样被视为一个数据通道,这个通道架设在应用程序端(客户端)和远程服务器端之间,而后,数据的读取(接收)和写入(发送)均针对这个通道来进行。 可见,在应用程序端或者服务器端...

    java与Omron Fins通信源码 java与欧姆龙PLC通信 全开源 springboot与欧姆龙PLC fins通信

    主要功能: 本实例基于OMRON Fins TCP协议,采用JAVA语言编写上位机软件实现Socket与OMRON CP系列PLC通讯,实例中通过发送指令实现与PLC的通讯握手,PLC寄存器数据的读取、PLC寄存器数据的写入等功能。 适合人群:...

    Delphi网络通信协议分析与应用实现pdf清晰

    2.4.2 通过读取系统状态参数检测网络状态 2.5 获取DNS信息 2.5.1 Windows NT系统中获取DNS信息 2.5.2 Windows 9x系统中获取DNS信息 2.6 网卡信息的获取 2.6.1 使用GUID获取网卡地址 2.6.2 NetBIOS来获得MAC...

    jnaCan:Java 的 CAN 总线,使用 JNA 访问 Linux SocketCan API

    Java 的 CAN 总线,使用 JNA 访问 Linux SocketCan API。 本软件为Alpha品质,请勿用于生产。 通过使用Java Native Access,无需编写本机代码即可访问SocketCan API。 这个项目是纯 Java 的(从技术上讲,JNA jar ...

    西门子S7协议案例.zip

    上位机软件通过Socket方式与西门子1200交换数据,PLC做服务器端,读取DB1的数据; 打开Socket,发送握手03 00 00 16 11 E0 00 00 00 01 00 C0 01 0A C1 02 01 00 C2 02 01 00,回复03 00 00 16 11 D0 00 01 00 01 00 ...

    微信小程序聊天室,基于java、socket

    通过与服务器建立连接,来进行客户端与客户端的信息交流。其中用到了局域网通信机制的原理,通过直接继承Thread类来建立多线程。开发中利用了计算机网络编程的基本理论知识,如TCP/IP协议、客户端/服务器端模式...

    Qt TCP相关的一些整理:客户端常见操作 socket 通信 network代码示例

    由于Qt中的收包是异步非阻塞的,还得需要配合收包信号来处理一下才可以,需要自定义槽来配合信号处理收包。有两种写法,但是前2个参数都是目标计算机的IP、Port,其他值可以不屑,都是有缺省值的。char buffer[] = ...

    socket linux2

    if(pid&gt;0) //父进程用于接受消息并读取 { while(1) { read(sockfd,shmaddr,SIZE); if(*(shmaddr+i*100)!=0) { printf("%s\n",(shmaddr+i*100)) ; i++; } usleep(1000); } } close(sockfd); ...

    UDP单播 、组播、广播,使用Qt实现,工程文件包,下载解压缩直接导入工程即可

    UDP是轻量的、不可靠的、面向数据报、无连接的协议,它可以用于对可靠性要求不高的场合,和TCP通信不同,两个程序之间进行UDP通信无需预先建立持久的socket连接,UDP每次发送数据报都需要指定目标地址和端口。...

    实验报告模版_实验2.doc

    计算机网络实验报告 一、 实验目的 深入掌握HTTP协议规范,学习如何编写标准的互联网应用服务器。... 本实验可以在前一个Socket编程实验的基础上继续,也可以使用第三方封装好的TCP类进行网络数据的收发

    PowerTCP Web Tool – 支持网络通信与传输的专业控件

    该工具可以使应用程序与web服务器通信,它具有HTTP控件的特点,可以通过工业标准的HTTP和HTTPS(Secure Socket Layer)协议同各种web服务器通信。该工具可以用来自动提交web forms、读取web页面内容、在web服务器上...

    Java网络编程(第三版)中文版.part11.rar

    通过GET方法与服务器端程序通信 233 访问受口令保护的网站 237 第八章 Swing中的HTML 245 组件上的HTML 245 JEditorPane 247 解析HTML 256 cookie 274 第九章 客户端Socket 283 socket基础 283 用Telnet...

    Java网络编程(第三版)高清中文版.part01.rar

    通过GET方法与服务器端程序通信 233 访问受口令保护的网站 237 第八章 Swing中的HTML 245 组件上的HTML 245 JEditorPane 247 解析HTML 256 cookie 274 第九章 客户端Socket 283 socket基础 283 用Telnet...

    Java网络编程(第三版)中文版.part06.rar

    通过GET方法与服务器端程序通信 233 访问受口令保护的网站 237 第八章 Swing中的HTML 245 组件上的HTML 245 JEditorPane 247 解析HTML 256 cookie 274 第九章 客户端Socket 283 socket基础 283 用Telnet...

    Java网络编程(第三版)中文版.part07.rar

    通过GET方法与服务器端程序通信 233 访问受口令保护的网站 237 第八章 Swing中的HTML 245 组件上的HTML 245 JEditorPane 247 解析HTML 256 cookie 274 第九章 客户端Socket 283 socket基础 283 用Telnet...

    Java网络编程(第三版)中文版.part09.rar

    通过GET方法与服务器端程序通信 233 访问受口令保护的网站 237 第八章 Swing中的HTML 245 组件上的HTML 245 JEditorPane 247 解析HTML 256 cookie 274 第九章 客户端Socket 283 socket基础 283 用Telnet...

    Java网络编程(第三版)中文版.part01.rar

    通过GET方法与服务器端程序通信 233 访问受口令保护的网站 237 第八章 Swing中的HTML 245 组件上的HTML 245 JEditorPane 247 解析HTML 256 cookie 274 第九章 客户端Socket 283 socket基础 283 用Telnet...

    Java网络编程(第三版)中文版.part03.rar

    通过GET方法与服务器端程序通信 233 访问受口令保护的网站 237 第八章 Swing中的HTML 245 组件上的HTML 245 JEditorPane 247 解析HTML 256 cookie 274 第九章 客户端Socket 283 socket基础 283 用Telnet...

Global site tag (gtag.js) - Google Analytics