TCP概念

来源:互联网 发布:ce怎么修改游戏数据 编辑:程序博客网 时间:2024/05/21 16:59

TCP:Transmission Control Protocol

TCP 在一个client和server间建立一个连接,在此连接上交换数据,然后终断这个连接。

TCP提供可靠性,每发送一份数据到对方,都要等待对方一个回应,如果回应没有到达,将重传数据并等待更长的一个时间,当试过多次后,TCP才放弃,然后返回一个错误给发送方,所有这些时间从4分钟到10分钟(根据实现)。

TCP不保证对方能接收到数据,它只是尽可能的发送数据,如果有错误(放弃发送或连接中断)就通知发送方。

TCP通过算法动态计算client与server之间的RTT(round-trip time),以便确定等待回应的时间。TCP总是不停的计算给定连接的RTT,应为网络环境经常变化。

TCP给要发送的连续数据包分配一个序列号(1    1024,1025    2048),这样接收方可以根据这个序列号重新组织数据,也可以丢弃重复的数据。

TCP提供溢出控制。任何时候,双方都要知道对方能接收多少数据,这就是所谓的 advertised window 。发送方发送的数据不能超出接收方的 receive buffer。如果接收方的 receive buffer 己满,它必须等程序从receive buffer中读取数据后才开始接收数据。

TCP的连接是双全工的。程序可以在任一时间进行发送和接收数据。

三次握手建立连接

  1. server准备好接收请求。通过调用:socket, bind, listen。这个叫做 passive open
  2. client 调用 connect 执行一个active open。这一过程client发送一个SYN(synchronize)数据包,告诉server将要发送数据的初始序列号。一般,这个SYN不包含数据,只包含IP头,TCP头,和一些TCP 属性。
  3. server对client的SYN发送一个ACK(acknowledge)。同时发送一个SYN,告诉client将要发送数据的初始序列号。这两个内容在一个数据包中发送。
  4. client对server的SYN发送一个ACK。

以上过程最少需要3个数据包。所以叫3次握手。

client-----------SYN J----------------------->server

client<----------ACK J + 1, SYN K-------server

client--------------------------ACK K + 1-->server

client的初始序列号为J,server的初始序列号为K。ACK中的序列号为发送ACK的结点期望的下一个序列号。一个SYN产后一个字节的序列号,所以ACK中的序列号为初始序列号+1。同样,一个FIN的ACK中的序列号为FIN的序列号+1。

TCP Options

  1. MSS(maximum segment size)。可接收的最大数据包尺寸。
  2. Window scale。最大的window是65535,因为TCP头对应的位为16位。对高速网络要TCP头左移0-14位,最大变为1G,此属性设定要左移的位数。两个连接结点必须都支持这一属性才能使用。
  3. Timestamp。高速连接避免各种情况引起的数据冲突。

TCP连接终止

  1. 程序调用close,称为active close。此结点会发送FIN数据包,表示终止数据发送。
  2. 接收到FIN的一端执行passive close。此结点会发送一个ACK。同时给相应程序发送一个end-of-file。
  3. 一段时间后,接收end-of-file的程序调用close关闭socket,这将发送一个FIN给对方。
  4. active close一端发送一个ACK。

有时第1个FIN会和数据一起发送,2,3也可能一起发送。所以只能说一般用4个数据包终止连接。

client-----------FIN M----------------------->server

client<----------ACK M + 1---------------server

client<--------------------------FINK N----server

client--------------------------ACK N + 1-->server

一个FIN产后一个字节的序列号,这个和SYN相同。

在2,3当中,有可能有数据从passive close到active close。这一现象称为half-close。

当程序结束时,所有的socket都会自动close。这时都会终止连接。

TCP State Transition Diagram

TIME_WAIT State

执行active close的结点最终进入TIME_WAIT,这一状态会持续2MSL(maximum segment lifetime)。这所以是active close的结点进入TIME_WAIT,是因为最后的ACK是由它发出的。

根据实现,MSL的时间为1-4分钟。在IP头中包含一个8位的hop limit。我们假设一个包含最大hop limit(255)的IP数据包在网络中的存在时间不超过MSL。

一个数据包因为网络问题很长时间没有到达目的地,这时发送方因为time out重发这一数据。后来最初的数据终于到达目的地,但是己经是重复的数据了,这种情况下这个最初的数据称为lost duplicate或wandering duplicate。

TIME_WAIT处理两种情况:

  1. 使TCP双全工连接能可靠的结束。如:最后的ACK丢失了,server重发FIN,client必须要保持状态发送对此FIN的ACK。
  2. 充许duplicate数据包在网络中消失。如:两个相同的IP地址对断开一会又重新连接,后面的这个连接叫前一个的incarnation。TCP必须阻止前一次连接中duplicates,这就要在TIME_WAIT时不能建立incarnation。因为TIME_WAIT为2MSL,这就保证一个数据包在一个方向上在1个MSL内丢失,另一个MSL保证对它的响应丢失。
Port Numbers

端口号为16位。client的端口号可以为临时的,只要保证主机上唯一就好。

端口号划为三个范围:

  1. well-known ports:0-1023,由IANA(Internet Assigned Numbers Authority)分配。
  2. registered ports:1024-49151,没被IANA控制,但是注册到IANA并由它公示出来。
  3. dynamic or private ports:49152-65535,临时的。

有几点说明:

  1. Unix 1024以下的端口号为保留端口号,有此范围端口号的server要有root权限。
  2. 有一些client(非servers)要用到保留端口号,如rlogin和rsh客户端。这些程序调用rresvport建立socket并分配一个从513-1023之间的没使用的端口号。

Socket Pair

对TCP,socket pair指four-tuple:本地的IP地址和端口号,远端的IP地址和端口号。在网络中一个socket pair唯一标识一个TCP连接。对SCTP,一个association包含本地的一组IP地址和端口号,远端的一组IP地址和端口号。

标识一个结点的两个值,IP地址和端口号,叫做socket。

If the host on which the server is running is multihomed, the server can specify that it wants only to accept incoming connections that arrive destined to one specific local interface. This is a one-or-any choice for the server. The server cannot specify a list of multiple addresses.

Buffer Sizes and Limitations

  1. IPv4的最大数据包大小为65535字节,包含IPv4头,因为总长度域为16位。
  2. IPv6的最大数据包大小为65575字节,包含40字节的IPv6头,因为数据长度域为16位。IPv6有一个jumbo payload option,可以将数据长度域扩充为32位,但这个数据只有在数据链路的MTU(maximum transmission unit)超过65535时有效。
  3. 许多网络的MTU由硬件决定,如Ethernet的MTU为1500字节。IPv4的最小MTU为68字节,IPv6为1280字节。
  4. 一条路径的最小MTU称为path MTU。1500字节的Ethernet MTU经常就是path MTU。路径的两个方向path MTU可能不同。
  5. 一个数据包的大小超过了link MTU,则分片发生。IPv4在主机和路由器上都可以分片,IPv6只在主机上分片。
  6. 如果IPv4中的DF(don't fragment)位被设置,数据包不能分片,如果在一个路由器上的MTU小于数据包大小,报错。
  7. IPv4和IPv6定义了minimum reassembly buffer size。IPv4为576字节,IPv6为1500字节。
  8. TCP有一个MSS(maximum segment size),决定了TCP peer中每一个分片的最大尺寸。MSS的目标就是避免分片。MSS经常设为MTU减去IP和TCP的头。MSS在TCP MSS option中有16位,最大为65535。这对IPv4是很好的,因为在IPv4中的TCP最大数据包尺寸为65495(65535减去20字节的IPv4头和20字节的TCP头)。IPv6如果没有设置jumbo payload option,大小为65515(65535减去20字节的TCP头)。IPv6如果设置了jumbo payload option,MSS的值如果为65535,此时认为MSS的值为无穷大,此种情况最大的分片为MTU。
  9. SCTP分片的点是所有peer address的最小MTU。

Socket Address Structures

IPv4 Socket Address Structure

struct in_addr {    in_addr_t s_addr;        /* 32-bit IPv4 address, network byte ordered */};struct sockaddr_in {    uint8_t sin_len;         /* length of structure (16) */    sa_family_t sin_family;  /* AF_INET */    in_port_t sin_port;      /* 16-bit TCP or UDP port number, network byte ordered */    struct in_addr sin_addr; /* 32-bit IPv4 address, network byte ordered */    char sin_zero[8];        /* unused */};
sin_len 一般在routing socket 时才用,其它情况可忽略。

此结构只是给出了一个地址,为连接提供了一些数据。

Generic Socket Address Structure

struct sockaddr {    uint8_t sa_len;    sa_family_t sa_family; /* address family: AF_xxx value */    char sa_data[14];      /* protocol-specific address */};
IPv6 Socket Address Structure
struct in6_addr {    uint8_t s6_addr[16];   /* 128-bit IPv6 address, network byte ordered */};#define SIN6_LEN           /* required for compile-time tests */struct sockaddr_in6 {    uint8_t sin6_len;          /* length of this struct (28) */    sa_family_t sin6_family;   /* AF_INET6 */    in_port_t sin6_port;       /* transport layer port#,  network byte ordered */    uint32_t sin6_flowinfo;    /* flow information, undefined */    struct in6_addr sin6_addr; /* IPv6 address, network byte ordered */    uint32_t sin6_scope_id;    /* set of interfaces for a scope */};
IPv6 family y为AF_INET6,IPv4 family为AF_INET

成员排列按64位对齐

sin6_flowinfo其中low-order 20位为flow label,high-order 12位保留。

The storage socket address structure

struct sockaddr_storage {    uint8_t ss_len;        /* length of this struct (implementation dependent) */    sa_family_t ss_family; /* address family: AF_xxx value */                           /* implementation-dependent elements to provide:                            * a) alignment sufficient to fulfill the alignment requirements of                            *    all socket address types that the system supports.                            * b) enough storage to hold any type of socket address that the                            *    system supports.                            */};

Byte Ordering Functions

2个字节组成的16位整数,在内存中有两种存储方式:little-endian和big-endian

网络字节序为big-endian

#include <netinet/in.h>// return: value in network byte orderuint16_t htons(uint16_t host16bitvalue) ;uint32_t htonl(uint32_t host32bitvalue) ;// return: value in host byte orderuint16_t ntohs(uint16_t net16bitvalue) ;uint32_t ntohl(uint32_t net32bitvalue) ;
Byte Manipulation Functions
#include <strings.h>void bzero(void *dest, size_t nbytes);void bcopy(const void *src, void *dest, size_t nbytes);int bcmp(const void *ptr1, const void *ptr2, size_t nbytes); // Returns: 0 if equal, nonzero if unequalvoid *memset(void *dest, int c, size_t len);void *memcpy(void *dest, const void *src, size_t nbytes);int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);// Returns: 0 if equal, <0 or >0 if unequal
inet_aton, inet_addr, and inet_ntoa Functions
#include <arpa/inet.h>int inet_aton(const char *strptr, struct in_addr *addrptr);  // Returns: 1 if string was valid, 0 on errorin_addr_t inet_addr(const char *strptr);  // Returns: 32-bit binary network byte ordered IPv4 address; INADDR_NONE if errorchar *inet_ntoa(struct in_addr inaddr);  // Returns: pointer to dotted-decimal string
以上函数用于点分式地址和32位网络字节序地址转换

inet_pton and inet_ntop Functions

#include <arpa/inet.h>int inet_pton(int family, const char *strptr, void *addrptr);  // Returns: 1 if OK, 0 if input not a valid presentation format, -1 on errorconst char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);  // Returns: pointer to result if OK, NULL on error#define INET_ADDRSTRLEN 16    /* for IPv4 dotted-decimal */#define INET6_ADDRSTRLEN 46   /* for IPv6 hex string */



Concurrent Servers

对于并发server,对每一个新的连接,server都会建立一个child去处理。

Output

application----------------------------------------------->TCP--------------------------------------------------->IP--------------------------------------------->datalink

application buffer( any size )--------------->socket send buffer | MSS-sized seqments------>MTU-sized IPv4/IPv6 packets-------->

通过SO_SNDBUF option 可以设置TCP的send buffer,当程序调用write,内核将数据拷贝到send buffer中,如果send buffer满了,socket将睡眠,这就是所谓的blocking socket。只有所有数据都拷贝到了send buffer,write才返回,告诉程序可以继续使用send buffer了,但它不会告诉程序对方是否收到数据或收到了什么数据。

TCP按照规将send buffer的数据发送出去。peer TCP必须确认数据,并且在没有收到确认前必须存在数据的一个copy。

TCP发送数据到IP,每份数据最大为MSS。发送前加上TCP的头。如果没有MSS option,则最大为536字节。IP加上IP头,查找路由表,发送数据到datalink。IP可能分片,但MSS的目标就是防止分片。每个datalink都有一个传输队列,如果己满,一个错误产生,TCP注意到这个错误,等待一段时间重发数据。程序不会得知这一切。

socket Function

#include <sys/socket.h>int socket (int family, int type, int protocol);  // Returns: non-negative descriptor if OK, -1 on error
family :

AF_INET       : IPv4

AF_INET6     : IPv6

AF_LOCAL    : Unix domain

AF_ROUTE   : Routing sockets

AF_KEY        : Key socket

type:

SOCK_STREAM          : stream socket

SOCK_DGRAM            : datagram socket

SOCK_SEQPACKET   : sequenced packet socket

SOCK_RAW                 : raw socket

protocol

IPPROTO_TCP     : TCP

IPPROTO_UDP     : UDP

IPPROTO_SCTP   : SCTP

 AF_INETAF_INET6AF_LOCALAF_ROUTEAF_KEYSOCK_STREAMTCP | SCTPTCP | SCTPYes  SOCK_DGRAMUDPUDPYes  SOCK_SEQPACKETSCTPSCTPYes  SOCK_RAWIPv4IPv6 YesYesconnect Function

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);    //  Returns: 0 if OK, -1 on error

cliend在调用connect前不用调用bind

connect 完成TCP连接的三次握手,只有连接建立或发生错误时才返回:

  1. client发送SYN后没收到响应,返回ETIMEDOUT。如:当调用connect后,一个SYN发送,6秒后没收到,24秒后没收到,最后75秒没收到,返回错误。
  2. serve对SYN的响应是一个RST(reset),这说明server没有进程提供此服务,如:server进程没启动。返回 ECONNREFUSED。一个RST有三种情况会发生,除了刚才的还有一种是TCP终止一个存在的连接,还有一种是TCP接收到一个不存在的连接的分片。
  3. client从路由器收到一个ICMP destination unreachable。client保存这个错误但还是发送SYN,如果一段时间还是没响应,返回EHOSTUNREACH或ENETUNREACH。

每次connect出错,必须调用close ,然后重新调用socket。

bind Function

#include <sys/socket.h>
int bind (int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);  // Returns: 0 if OK,-1 on error

将一个本地地址分配给socket,地址包含一个IP地址和一个端口号。

  1. server 开始时要绑定一个well-known端口号。如果不调用bind,将使用随机端口号。
  2. 一个进程可绑定一个指定的IP到一个socket。一般client不绑定IP地址到socket。如果server不绑定IP,内核使用client的SYN的目标地址的IP做为serve的源地址。

IP addressportresultWildcard0kernel chooses IP address and portWildcardnonzerokernel chooses IP address and process specifies portLocal IP0process specifies IP address kernel chooses portLocal IPnonzeroprocess specifies IP address and port当bind被调用,如果 port 为0,将选一个随机端口。如果IP为wildcard,只有当TCP连接或UDP发送数据时对会选择本地IP地址。

对IPv4,INADDR_ANY,它的值为0

struct sockaddr_in servaddr;
servaddr.sin_addr.s_addr = htonl (INADDR_ANY); /* wildcard */

对IPv6

struct sockaddr_in6 serv;
serv.sin6_addr = in6addr_any; /* wildcard */

listen Function

#include <sys/socket.h>
#int listen (int sockfd, int backlog);  // Returns: 0 if OK, -1 on error

对一个listening socket,有两个列表,incompleted connection queue和completed connection queue。一个是等待三次握手,状态为SYN_RCVD。一个是连接建立,状态为ESTABLISHED。

  1. backlog指两个队列的所有项的合值。
  2. Berkeley-derived的实现为backlog*1.5
  3. 不要设置backlog的值为0
  4. 连接建立后,数据如果在serve调用accept之前到达,将缓冲到 socket's receive buffer中。

accept Function

#include <sys/socket.h>
int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); // Returns: non-negative descriptor if OK, -1 on error

被TCP server调用,从completed connection queue头部返回下一个连接,如果queue为空,block。

accept成功返回一个新的connected socket,它由内核建立,代表与client的一个连接。如果完成对client的服务,此socket关闭。

一个server时刻保持一个listen socket,对每一个请求连接的client,新建一个connected socket。

返回3个值,一个是connected socket,一个是client的地址,一个是地址的长度。如果对client地址没兴趣,两个指针可设为空。

fork and exec Functions

#include <unistd.h>
pid_t fork(void); // Returns: 0 in child, process ID of child in parent, -1 on error

调用一次,返回两次。一次返回到主进程,返回值为子进程的ID,一次返回到子进程,返回值为0。返回值可以得知是在主进程还是在子进程。

fork在子进程返回0,而不是父进程的ID,是因为进程只能有一个父进程,可通过getppid.得到。但进程可以有多个子进程。

所有打开的descriptors在fork返回后在父子进程都会共享。如:子进程通过connected socket进行读写,父进程关闭connected socket。

有两个典型应用:

  1. 一个进程copy一份自己,使得一个copy进行一个一个操作,同时另一个进行另一个任务。
  2. 一个进程要运行另一个程序。先copy一份自己,然后调用exec用另一个程序代替自己。

总共有6个exec,它们的区别在于:

  1. 程序是否由文件名确定。
  2. 给新程序的参数是一个一个列出还是通过一个数组
  3. 主进程的上下文环境是传递给新程序,还是创建一个新的上下文。

#include <unistd.h>
int execl (const char *pathname, const char *arg0, ... /* (char *) 0 */ );
int execv (const char *pathname, char *const argv[]);
int execle (const char *pathname, const char *arg0, .../* (char *) 0, char *const envp[] */ );
int execve (const char *pathname, char *const argv[], char *const envp[]);
int execlp (const char *filename, const char *arg0, ... /* (char *) 0 */ );
int execvp (const char *filename, char *const argv[]);
// All six return: -1 on error, no return on success

Concurrent Servers

pid_t pid;int listenfd, connfd;listenfd = Socket( ... );/* fill in sockaddr_in{} with server's well-known port */Bind(listenfd, ... );Listen(listenfd, LISTENQ);for ( ; ; ) {    connfd = Accept (listenfd, ... ); /* probably blocks */    if( (pid = Fork()) == 0)     {        Close(listenfd); /* child closes listening socket */        doit(connfd); /* process the request */        Close(connfd); /* done with this client */        exit(0); /* child terminates */    }    Close(connfd); /* parent closes connected socket */}
before call to accept returns

client | connect ----------------------------connection request----------------->listenfd | server

after return from accept

client | connect <----------------------------connection----------------->connfd - listenfd | server
after fork returns

client | connect <----------------------------connection----------------->connfd - listenfd | server

                                                 ----------connection------------------>connfd - listenfd | serve-child

after parent and child close appropriate sockets

client | connect                                                                                listenfd | server

                                                 ----------connection------------------>connfd | serve-child

close Function

#include <unistd.h>
int close (int sockfd);  //  Returns: 0 if OK, -1 on error

调用此函数的默认操作为:将在缓冲中的数据发完,终止连接。

可以设置SO_LINGER改变默认操作。

getsockname and getpeername Functions

#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
Both return: 0 if OK, -1 on error

返回socket的本地和远端地址。

  1. 没调用bind,调用connect返回后,getsockname本地的IP地址和端口号
  2. 调用bind并指定端口号为0,getsockname返回本地被内核分配的端口号
  3. getsockname可以得到socket的address family
  4. 在TCP server,调用bind并指定IP地址为wildcard,一但边接建立(accept成功),server调用getsockname可得到内核分配的IP地址。此时socket的descriptor必须是connected socket
  5. 当server在子进和中运行,得到client地址的方法只有调用getpeername。

POSIX Signal Handling

signal又称为software interrupts。可以通过以下方式发送:

  1. 从一个进程到另一个进程
  2. 从内核到进程

一个进程terminates,内核发送SIGCHLD给它的父进程。每个signal都有一个disposition,可通过sigaction设置。

  1. 可定义一个句柄来响应signal,称为catching signal。其中SIGKILL和SIGSTOP不能被catch。函数原型为void handler (int signo);
  2. SIGIO, SIGPOLL, SIGURG这几个要特殊处理,其它只要通过sigaction设置就可以了
  3. 设置disposition为SIG_IGN可以忽略Signal。SIGKILL和SIGSTOP不能被忽略
  4. 设置disposition为SIG_DFL,按默认方式处理。一般为终止进程或忽略signal

POSIX Signal Semantics

  1. Once a signal handler is installed, it remains installed.
  2. 当signal函数执行时,此signal blocked。所有通过mask传递给sigaction的signal一起block
  3. 当一个signal block时,多次提交此signal,只当作提交了一次
  4. 可通过sigprocmask设置一组signal block or unblock

wait and waitpid Functions

#include <sys/wait.h>
pid_t wait (int *statloc);
pid_t waitpid (pid_t pid, int *statloc, int options);
Both return: process ID if OK, 0 or 1 on error

返回两个值:终止的子进程的ID,子进程终止的状态(正常,被信号杀死,被job control 停止)

如果有子进程在运行并且没有子进程结束,将block,直到有一个子进程结束。

waitpid,参数为-1,等待第一个进程结束 。WNOHANG option,不block。

I/O multiplexing

  1. 当client handle理多个descriptors(normally interactive input and a network socket)I/O multiplexing应该使用
  2. client handle 多个socket,不常见
  3. server 同时handle listening socket and its connected sockets
  4. server 同时handle TCP and UDP
  5. server 同时多个服务和多个协议

I/O Models

blocking I/O
nonblocking I/O
I/O multiplexing (select and poll)
signal driven I/O (SIGIO)
asynchronous I/O (the POSIX aio_functions)

对输入一般有两个情况:

  1. Waiting for the data to be ready
  2. Copying the data from the kernel to the process

select Function

#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
Returns: positive count of ready descriptors, 0 on timeout, 1 on error

  1. Wait forever Return only when one of the specified descriptors is ready for I/O. For this, we specify the timeout argument as a null pointer
  2. Wait up to a fixed amount of time Return when one of the specified descriptors is ready for I/O, but do not wait beyond the number of seconds and microseconds specified in the timeval structure pointed to by the timeout argument
  3. Do not wait at all Return immediately after checking the descriptors. This is called polling. To specify this, the timeout argument must point to a timeval structure and
    the timer value (the number of seconds and microseconds specified by the structure) must be 0


0 0
原创粉丝点击