标签 TCP/IP 下的文章

理解TIME_WAIT

前言

TIME_WAIT 是在TCP协议中很模糊的概念,它可能使socke能陷入的一种时间相对比较长的状态,过多的TIME_WAIT会影响新socket的建立。TIME_WAIT为什么会存在?它的作用又是什么?下面我们就来理解下TIME_WAIT。

这张图详细的列出了TCP建立连接和断开连接的各个TCP状态之间的转换。红色的代表server,蓝色的代表client。下面列出各自的TCP状态转换条件

TCP建立连接

  1. Client: 向server发送 SYN 包,表示请求建立连接,进入 SYN_SENT 状态;
  2. Server: 接收来自client的 SYN 包,发送 SYN/ACK 包,代表client->server单向tcp连接已经建立, 进入 SYN_RCVD 状态;
  3. Client: 接收到来自server的 SYN/ACK 包,发送给server ACK 包,进入 Established 状态;
  4. Server: 收到client的 ACK 包,代表 server->client 的单向tcp连接也建立,此时进入 Established 状态;

- 阅读剩余部分 -

CRC 校验的基本过程

采用 CRC 校验时,发送方和接收方用同一个生成多项式 g(x) , g(x) 是一个 GF(2) 多项式,并且 g(x) 的首位和最后一位的系数必须为 1 。

CRC 的处理方法是:发送方用发送数据的二进制多项式 t(x) 除以 g(x) ,得到余数 y(x) 作为 CRC 校验码。校验时,以计算的校正结果是否为 0 为据,判断数据帧是否出错。设生成多项式是 r 阶的(最高位是 x^r )具体步骤如下面的描述。

发送方:

1 )在发送的 m 位数据的二进制多项式 t(x) 后添加 r 个 0 ,扩张到 m+ r 位,以容纳 r 位的校验码,追加 0 后的二进制多项式为  T(x) ;

2 )用 T(x) 除以生成多项式 g(x) ,得到 r 位的余数 y(x) ,它就是 CRC 校验码;

3 )把 y(x) 追加到 t(x) 后面,此时的数据 s(x) 就是包含了 CRC 校验码的待发送字符串;由于 s(x) = t(x) y(x) ,因此 s(x) 肯定能被 g(x) 除尽。

接收方:

1 )接收数据 n(x) ,这个 n(x) 就是包含了 CRC 校验码的 m+r 位数据;

2 )计算 n(x) 除以 g(x) ,如果余数为 0 则表示传输过程没有错误,否则表示有错误。从 n(x) 去掉尾部的 r 位数据,得到的就是原始数据。

自己动手学TCP/IP–http协议(http报文格式)

HTTP(HyperText Transport Protocol,超文本传送协议)

HTTP请求报文


http请求数据包的格式:头部(request line + header)+  数据(data)

头部和数据包体通过一个空行来隔开,头部的格式主要包括请求行+请求头部。如下图

HTTP请求头

请求行


请求行由请求方法字段URL字段HTTP协议版本字段3个字段组成,它们用空格分隔如:

GET /index.html HTTP/1.1。

HTTP协议的请求方法有GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT。这里介绍最常用的GET方法和POST方法。

GET方式:在URL里面就说明要请求的资源,URL里面包含参数,“?”后面就是参数,而“?”前面就是URL的结束。“?ip=192.168.156.11&active=on”这种就是GET方式的包,而服务器把客户端请求的内容在数据段里面发回给客户端。

POST方式:传输的数据不在URL里面出现,而是在数据段里面出现。但是请求头部多了Content-Type和Content-Length两个字段。

请求头部


请求头部由(关键字:<空格>值)对组成,每行一对,关键字和值用英文冒号“:<空格>”分隔。请求头部通知服务器有关于客户端请求的信息,典型的请求头有:

User-Agent:产生请求的浏览器类型。

Accept:客户端可识别的内容类型列表。

Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机。

下面是GET包的一个例子:传输的数据在URL里

再看看POST包的例子:传输的数据在数据段里面


HTTP响应报文


HTTP响应也由两个个部分组成,分别是:响应头(状态行+消息报头)+响应正文

状态行格式如下:

HTTP-Version Status-Code Reason-Phrase CRLF

HTTP-Version表示服务器HTTP协议的版本;Status-Code表示服务器发回的响应状态代码;Reason-Phrase表示状态代码的文本描述。状态代码由三位数字组成,第一个数字定义了响应的类别,且有五种可能取值。

1xx:指示信息--表示请求已接收,继续处理。

2xx:成功--表示请求已被成功接收、理解、接受。

3xx:重定向--要完成请求必须进行更进一步的操作。

4xx:客户端错误--请求有语法错误或请求无法实现。

5xx:服务器端错误--服务器未能实现合法的请求。

下面是http响应包的例子

自己动手学TCP/IP--tftp协议

TFTP(Trivial File Transfer Protocol,简单文件传输协议

1.tftp的服务端口号是69

2.tftp是基于udp协议的

3.tftp是明文传输的,是一种比较轻量型的协议,一般用于bootloader加载内核

TFTP工作流程

服务端开启tftp服务,tftp是一种stand_alone服务,不是常驻内存的,是在有需要的时候才去调用的。首先,客户端发送一个读(RRQ:2个字节)或者写(WRQ:2个字节)的请求,数据包的目标端口是69。对于读或者写的报文格式如下:

RRQ/WRQ(2个字节)+文件名(N字节)+0(1字节)+模式(N字节)+0(1字节)

目前模式字段主要有2种:netascii,这是8位的ASCII码形式;另一种是octet,这是8位源数据类型。对于netascii是把回车和换行(CR/LF)解释成两个字节的。可以查看http://www.firefoxbug.com/?p=1041

tftp-server接收到数据包:如果是发现是读(RRQ),就重新随机分配一个端口,直接发送数据(DATA:2个字节)+块编号(2个字节),然后是0~512字节数据包。客户端接收到数据包,发给服务端(ACK:2个字节)+块编号(2个字节)。如果是普通的数据包,那么数据段的大小一定是512字节,如果是最后一个数据包,肯定是小于512字节的。tftp就是通过发现了一个数据段小于512字节的数据包来声明结束文件的传输了。那么一个要传输的文件刚还是512字节的整数倍怎么办呢?tftp会在最后传输一个数据段大小是0包。

tftp-server接收到数据包:如果发现是写(WRQ),服务端就发回(ACK:2个字节)+(块编号0:2个字节)的包,接着客户端就发送(DATA:2个字节)+(块编号1:2个字节)+数据段给服务端,服务端发回(ACK:2个字节)+(块编号1:2个字节)。。。依次发送。

错误信息是系统自定义的,格式主要是error(2个字节)+错误码(2个字节)+错误信息(N个字节)

下面是tftp数据包的格式图

下面是C语言解析tftp包的一小段代码:

struct tftphdr {
short th_opcode; /* packet type */
union {
unsigned short tu_block; /* block # */
short tu_code; /* error code */
char tu_stuff[1]; /* request packet stuff */
} __attribute__ ((__packed__)) th_u;
char th_data[1]; /* data or error string */
} __attribute__ ((__packed__));

// 解析udp包,packet_buffer是用rawsocket抓出来的以太网包,
void ParseUDPPacket(unsigned char *packet_buffer)
{
struct ethhdr *eth_header;//以太网头
struct iphdr *ip_header; //ip头
struct udphdr *udp_header; //tcp头
eth_header = (struct ethhdr*)packet_buffer;
ip_header = (struct iphdr*)(packet_buffer + sizeof(struct ethhdr));
udp_header = (struct udphdr*)(packet_buffer + sizeof(struct ethhdr) + ip_header->ihl*4);
unsigned char *data = NULL;
data = (packet_buffer + sizeof(struct ethhdr) + ip_header->ihl*4 + 8);//8代表UDP包头
struct tftphdr *tp = (struct tftphdr *)data; // /usr/include/arpa/tftp.h
tftp_print(data,ntohs(udp_header->len)-8); //ntohs(udp_header->len)-8表示udp数据包长度
}

/*
* Print trivial file transfer program requests
*/
void tftp_print(register const u_char *bp, u_int length)
{
register const struct tftphdr *tp;
register const u_char *p;
register int opcode,i;
static char tstr[] = " [|tftp]";
char buffer[520] = {'\0'};
tp = (const struct tftphdr *)bp;

// printf(" %d", length);
// printf("length of tftp_data = %d\n",length);

/* Print tftp request type */

opcode = EXTRACT_16BITS(&tp->th_opcode);
printf(" %s",tok2str(op2str, "tftp-#%d", opcode));
/* Bail if bogus opcode */

switch (opcode) {

case RRQ:
break;

case WRQ:
break;

case ACK:
break;

case DATA:
break;

case ERROR:
break;

default:
/* We shouldn't get here */
printf("(unknown #%d)", opcode);
break;
}
return;
}

详细的可以查看tcpdump的源码。