代码改变世界

《Unix网络编程》学习笔记 第一,第二章

2011-08-26 01:13  Aga.J  阅读(1507)  评论(0编辑  收藏  举报

第一章 简介

第一章对Unix下的网络编程做了概要的介绍,使用了获取时间的实例来讲解网络编程中所需要使用到的函数和注意内容,并详细的解析了每个函数的功能及其使用。由于之前已经谈过简单TCP通信的实现,这里便不再赘述。

TCP下的日期/时间 客户端

clip_image002

TCP下的日期/时间 服务器

clip_image003

第二章

传输层:TCP和UDP

一 协议介绍

UDP

不可靠,数据报,

TCP

可靠,字节流,面向连接,全双工,支持ACK、超时重传,需要保证消息独立性

ICMP

网际控制消息协议,处理路由器和主机间的错误和控制信息

IGMP

网络组管理协议

ARP

地址解析协议,将IPv4地址映射到硬件地址

RARP

逆地址解析

BPF

BSD分组过滤器,为进城提供访问数据链路层的接口

二 TCP底层介绍

和UDP这种基于数据报的协议不同,TCP是基于字节流的,而且它是可靠的,它向另一端发送数据时,要求返回一个确认,如果没有收到则会重传或者等待更加长的时间,同时,它还有用户动态估算客户端机到服务器往返所花费的时间RTT的算法。

TCP通过给所发送的数据的每一个字节关联一个序列号来进行排序,架设一个应用进程写了2048个字节到一个TCP socket上,导致TCP发送2个分节(分节会在后面介绍,它是TCP传递给IP的数据单元),第一个分节所含的数据序列号为1-1024,第二个分节所含的数据序列号为1025-2048,如果分节以非顺序到达,那么接收方TCP就会根据其序列号进行重新排序,再将结果数据传递到应用进程内。重复到达的数据也可以根据序列号来判断,并丢弃。

TCP连接建立过程需要经过3次握手:

(1) 服务器准备好接收外来的连接(linux编程:socket bind listen),此时服务器称为被动打开

(2) 客户端通过调用connect来进行“主动打开”,这时客户端发送一个SYN分节,告诉服务器客户在待建立的连接中所要发送的数据的初始序列号

(3) 服务器确认客户的SYN,同时自己发送一个SYN分节,包含服务器将在该连接中发送的数据的初始序列号(还有ACK)

(4) 客户确认服务器的SYN,也发回ACK

clip_image004

其中每个SYN分节可以包含多个TCP选项来配制TCP连接。

TCP连接终止的过程

(1) 进程调用close进行“主动关闭”,TCP发送一个FIN分节

(2) 接收FIN的称为“被动关闭”,发回ACK

(3) 一段时间后,“被动接收方”也调用close来关闭socket,TCP发送FIN

(4) 主动关闭方进行ACK

clip_image005

TCP的状态转换图

clip_image006

clip_image007

上面两张图可以帮助我们更加直观的连接TCP连接,通信,关闭连接的详细过程。

接下来是TCP状态中Time WAIT状态的分析,从上图我们知道,TCP主动关闭的一方会进入TIME WAIT的状态,tcp规定进入该状态后需要等待2MSL的时间(MSL是指IP数据报能在互联网中生存的最长时间,“跳限”也可以限制数据报的生存时间)。那么为什么需要TIME WAIT的状态呢?为什么接收到被动关闭端的FIN后不直接关闭连接呢?

因为

(1) 它可以实现TCP全双工连接的可靠性
假设主动端接到最后FIN后发回的ACK丢失,那么被动端会重新发送FIN,这时主动端必须维护状态信息来运行它重发最终的ACK,如果不维护状态信息,它将响应以RST,而被动端会将该分节解释为一个错误,这样就无法彻底的终止两个方向上的流。

(2) 它允许老的重复分节在网络中消失

当老的重复分节游离后,并且主客都断开了,但是之后双方又建立了一次完全一样的连接,这时不巧老分组又出现,那怎么办,为了避免这种情况,所以等待时间要大于一个分组在网络中能存活的最长时间也就是MSL

TCP输出过程:

clip_image008

每个TCP socket都有一个“发送缓冲区”,可以调节其大小,调用write时,TCP会把应用进程缓冲区的数据拷贝到socket的发送缓冲区内,如果容不下则会挂起进程。所以从wirite返回并不意味着连接那端已经接受到数据,这里仅仅是放到发送缓冲区而已。接下来的发送就要靠TCP来完成了,TCP进行分节后有IP构成数据报,查找路由表确定外出接口,床给数据链路(数据链路有输出队列,如果满了,那么分组被丢弃并上报,TCP会重发,这堆用户是透明的)。

UDP输出

clip_image009

UDP输出中没有所谓的发送缓冲区,但是也由一个值来限定数据报大小。应用进程的数据沿协议栈向下传递,以某种形式拷贝到内核的缓冲区,链路层将数据扔出后就丢弃数据缓存。

第3章 套接口编程简介

(一) 每个协议族都定义了它自己的“套接口地址”结构,以sockaddr_开头,后接每个协议族唯一后缀,例如IPv4的套接口地址结构称为“网际套接口地址结构”,以sockaddr_in命名。数据结构如下

struct in_addr

{

in_addr_t s_addr; //32位IPv4地址,以网络字节序,实际类型uint32_t

};

struct sockaddr_in

{

uint8_t sin_len; //不通用

sa_family_t sin_family;

ln_port_t sin_port; //16位端口,以网络字节序,实际类型uint16_t

struct in_addr sin_addr;

char sin_zero[8]; //未使用

};

【注意:IPv4和端口号都是以“网络字节序”来存储的,要做相应转换】

通用套接字结构

struct sockaddr

{

uint8_t sa_len;

sa_family_t sa_family;

char sa_data[14];

};

【Ipv6的套接字结构类似】

(二) 主机字节序和网络字节序的转换函数

uint16_t htons(..)

uint32_t htonl(..)

uint16_t ntohs(..)

uint32_t ntohl(..)

(三) 多字符字段的操作函数

#include<strings.h>

void bzero(void *dest, size_t bytes);

void bcopy(const void *src,void *dest, size_t nbytes);

void bcmp(const void *ptr,const void *ptr2,size_t nbytes);

#include<string.h>

void *memset(void *dest,int c,size_t len);

void *mencpy(void *dest,const void *src,size_t inbytes);

int memcmp(const void *ptr1,const void *ptr2,size_t nbytes);

(四) 地址转换函数:在ASC||字符串和网络字节序二进制值之间进行转换

常用:

inet_aton, inet_addr, inet_ntoa

适合IPv4和IPv6的新函数

inet_pton, inet_ntop

(五) socket上的读写问题

socket上使用read或者write进行读写时,要注意输入输出的字节数可能比要求的字节数少,这是因为内核中的套接字缓冲区可能已经满了,这时只能再调用一次read或者write来完成读写,我们每次读写成功返回的字节数需要和期望字节数比较,一旦发现不同,则需要再次读取,下面是一个重读函数实现

ssize_t nleft;

ssize_t nread;

char *ptr=vptr;

nleft=n;

while(nleft>0)

{

if( nread=read(fd,ptr,nleft))<0)

{

if(errno=EINTR)

nread=0;

else return (-1);

}

else if(nread==0)

break;

nleft=nleft-nread;

ptr=ptr+nread;

}

return (n-nleft);