转载

UNIX网络编程基础

本博客采用创作共用版权协议, 要求署名、非商业用途和保持一致. 转载本博客文章必须也遵循 署名-非商业用途-保持一致 的创作共用协议.

传输层

  • UDP(User Datagram Protocol), 无连接, 不可靠, 全双工, UDP套接字是一种数据报套接字
  • TCP(Transmission Control Protocol), 客户端服务器建立连接, 全双工, 可靠, 包含动态估算RTT算法, 流量控制, 超时重传
  • SCTP(Stream Control Transmission Protocol), 面向消息, 面向连接, 单个SCTP断点支持多个IP地址( 多宿性 )

TCP三次握手

  1. 客户端主动连接服务器( connect() ), 并发送一个SYN(告诉服务器未来发送数据的初始序列号)
  2. 服务器一直等待连接( accept() ), 收到SYN后, 发出新的SYN+ACK进行确认
  3. 客户端向服务器发出ACK确认, accept返回套接字描述符

TCP四次终止

  1. 客户端主动关闭( close() ), 向服务器端发送FIN表示数据发送完毕
  2. 服务器端收到FIN后, 向客户端发出ACK确认, FIN表明服务器端不需要再接收数据
  3. 一段时间后, 服务器端接收到进程关闭套接字描述符( close() ), 并想客户端发送一个FIN
  4. 客户端收到FIN后, 向服务器发出确认

( 注意: )三阶段到四阶段, 主动关闭的一方进入 TIME_WAIT 状态, 为了可靠的实现TCP全双工连接的终止, 允许老的重复分解在网络中消失(P37)

套接字表面上可以认为由IP和端口号组成, IP用来表示不同的主机(也就是端到端), 端口号用来表示一台主机上不同的进程(不同进程使用不同的端口号)

TCP输出过程:

  • 进程调用write(), 内核从应用进程的缓冲区复制所有数据到套接字的发送缓冲区( SO_SNDBUF )
  • 如果套接字发送缓冲区小于应用发送缓冲区(或者套接字发送缓冲已有数据), 则进程睡眠( 阻塞 ).
  • 直到进程缓冲区所有数据都复制的发送缓冲区.
  • TCP从套接字发送缓冲区提取并发送数据( 数据未确认前, TCP保留数据副本 )

write()返回仅表示可以重新使用应用缓冲区, 并 不标明另一端的TCP接收到数据

对于UDP, 如果进程写一个 大于套接字缓冲区大小的数据报 , 内核将返回 EMSGSIZE错误 , UDP不保留数据副本

write调用成功返回表示 所写数据报已被加入数据链路层的传输队列

套接字

  • bind, connect, sendto, sendmsg是从进程到内核传递套接字
  • accept, recvfrom, recvmsg, getpeername, getsockname是从内核到进程传递套接字

//所有类型的套接字都至少是16字节

struct sockaddr_in {

uint8_t sin_len; /* length of struct */

sa_family_t sin_family; /* Address family */

in_port_t int sin_port; /* Port number */

struct in_addr sin_addr; /* Internet address */

unsigned char sin_zero[ 8 ]; /* Same size as struct sockaddr */

};

struct in_addr {

in_addr_t s_addr; /* 32-bit network byte ordered IPv4 address */

};

//网络编程函数中的struct sockaddr参数

struct sockaddr {

uint8_t sa_len;

sa_family sa_family; /* address family, AF_xxx */

char sa_data[ 14 ]; /* 14 bytes of protocol address */

};

struct sockaddr_in serv; //IPv4套接字结构

( struct sockaddr *)&serv //强制类型转换

  • 内存中存储数据的方法分为 小端字节序大端字节序
  • 网络协议指定 网络字节序 规则各字节传送顺序(使用大端字节序)

ASCII字符串与网络字节序的二进制值进行转换

# include <arpa/inet.h>

/* 下面两个函数仅适用于IPv4 */

//将字符串转换为网络字节序二进制数, 字符串有效返回1, 否则返回0

int inet_aton( const char *cp, struct in_addr *pin);

//将网络字节序二进制转换为点十进制字符换

char *inet_ntoa( struct in_addr in);

/* 适用于IPv4和IPv6 */

int inet_pton( int family, const char * restrict src, void * restrict dst);

const char *inet_ntop( int af, const void * restrict src, char * restrict dst, socklen_t size);

Rio

字节流套接字条用调用read或write输入和输出的字节数可以比请求的数量少, 原因在于内核中用于套接字的缓冲区可能已达到极限, 这一点在 CSAPPRio 章节同样有深入的讲解.

* The Rio package - robust I/O functions

**********************************************************************/

/*

* rio_readn - robustly read n bytes (unbuffered)

*/

/* $begin rio_readn */

ssize_t rio_readn( int fd, void *usrbuf, size_t n) { //等价与UNP中的readn函数

size_t nleft = n;

ssize_t nread;

char *bufp = usrbuf;

while (nleft > 0 ) {

if ((nread = read(fd, bufp, nleft)) < 0 ) {

if (errno == EINTR) /* interrupted by sig handler return */

nread = 0 ; /* and call read() again */

else

return - 1 ; /* errno set by read() */

}

else if (nread == 0 )

break ; /* EOF */

nleft -= nread; //nleft保存剩余需要读取的字节数, nread表示已读字节数

bufp += nread; // bufp始终指向要读取的字符串起始位置

}

return (n - nleft); /* return >= 0 */

}

/* $end rio_readn */

/*

* rio_writen - robustly write n bytes (unbuffered)

*/

/* $begin rio_writen */

ssize_t rio_writen( int fd, void *usrbuf, size_t n) { //等价于UNP中的writen函数

size_t nleft = n;

ssize_t nwritten;

char *bufp = usrbuf;

while (nleft > 0 ) {

if ((nwritten = write(fd, bufp, nleft)) <= 0 ) {

if (errno == EINTR) /* interrupted by sig handler return */

nwritten = 0 ; /* and call write() again */

else

return - 1 ; /* errno set by write() */

}

nleft -= nwritten;

bufp += nwritten;

}

return n;

}

/* $end rio_writen */

/*

* rio_read - This is a wrapper for the Unix read() function that

* transfers min(n, rio_cnt) bytes from an internal buffer to a user

* buffer, where n is the number of bytes requested by the user and

* rio_cnt is the number of unread bytes in the internal buffer. On

* entry, rio_read() refills the internal buffer via a call to

* read() if the internal buffer is empty.

*/

/* $begin rio_read */

static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)

{

int cnt;

while (rp->rio_cnt <= 0 ) { /* refill if buf is empty */

rp->rio_cnt = read(rp->rio_fd, rp->rio_buf,

sizeof (rp->rio_buf)); //调用的是系统read函数,预先读取到内部缓冲区中

if (rp->rio_cnt < 0 ) {

if (errno != EINTR) /* interrupted by sig handler return */

return - 1 ;

} else if (rp->rio_cnt == 0 ) /* EOF */

return 0 ;

else

rp->rio_bufptr = rp->rio_buf; /* reset buffer ptr */

}

/* Copy min(n, rp->rio_cnt) bytes from internal buf to user buf */

cnt = n;

if (rp->rio_cnt < n)

cnt = rp->rio_cnt;

memcpy (usrbuf, rp->rio_bufptr, cnt); //rio_bufptr始终指向字符串初始复制位置,复制cnt个倡导到usrbuf

rp->rio_bufptr += cnt; //复制初始位置指针后移

rp->rio_cnt -= cnt; //缓冲区大小剪掉已读取字符数

return cnt; //返回已读取字符数, 返回值与系统read含义类似

}

/* $end rio_read */

/*

* rio_readinitb - Associate a descriptor with a read buffer and reset buffer*(缓冲区初始化)

*/

/* $begin rio_readinitb */

void rio_readinitb(rio_t *rp, int fd)

{

rp->rio_fd = fd;

rp->rio_cnt = 0 ;

rp->rio_bufptr = rp->rio_buf;

}

/* $end rio_readinitb */

/*

* rio_readnb - Robustly read n bytes (buffered)

*/

/* $begin rio_readnb */

ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n)

{

size_t nleft = n;

ssize_t nread;

char *bufp = usrbuf;

while (nleft > 0 ) {

if ((nread = rio_read(rp, bufp, nleft)) < 0 ) {

if (errno == EINTR) /* interrupted by sig handler return */

nread = 0 ; /* call read() again */

else

return - 1 ; /* errno set by read() */

} else if (nread == 0 )

break ; /* EOF */

nleft -= nread;

bufp += nread;

}

return (n - nleft); /* return >= 0 */

}

/* $end rio_readnb */

/*

* rio_readlineb - robustly read a text line (buffered) 线程安全

*/

/* $begin rio_readlineb */

ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)

{

int n, rc;

char c, *bufp = usrbuf;

for (n = 1 ; n < maxlen; n++) {

if ((rc = rio_read(rp, &c, 1 )) == 1 ) {

*bufp++ = c;

if (c == '/n' )

break ;

} else if (rc == 0 ) {

if (n == 1 )

return 0 ; /* EOF, no data read */

else

break ; /* EOF, some data was read */

} else

return - 1 ; /* error */

}

*bufp = 0 ;

return n;

}

/* $end rio_readlineb */

/**********************************

* Wrappers for robust I/O routines

**********************************/

ssize_t Rio_readn( int fd, void *ptr, size_t nbytes)

{

ssize_t n;

if ((n = rio_readn(fd, ptr, nbytes)) < 0 )

unix_error( "Rio_readn error" );

return n;

}

void Rio_writen( int fd, void *usrbuf, size_t n)

{

if (rio_writen(fd, usrbuf, n) != n)

unix_error( "Rio_writen error" );

}

void Rio_readinitb(rio_t *rp, int fd)

{

rio_readinitb(rp, fd);

}

ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n)

{

ssize_t rc;

if ((rc = rio_readnb(rp, usrbuf, n)) < 0 )

unix_error( "Rio_readnb error" );

return rc;

}

ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)

{

ssize_t rc;

if ((rc = rio_readlineb(rp, usrbuf, maxlen)) < 0 )

unix_error( "Rio_readlineb error" );

return rc;

}

Stevens的UNP中使用static不是线程安全的,在CSapp中rio_readlineb和rio_readnb修改了两个缺陷,都是缓冲区读,并且线程安全, 其中核心为rio_t读缓冲区struct

/* Persistent state for the robust I/O (Rio) package */

/* $begin rio_t */

# define RIO_BUFSIZE 8192

typedef struct {

int rio_fd; /* descriptor for this internal buf */

int rio_cnt; /* unread bytes in internal buf */

char *rio_bufptr; /* next unread byte in internal buf */

char rio_buf[RIO_BUFSIZE]; /* internal buffer */

} rio_t;

/* $end rio_t */

参考

  • UNIX网络编程第三版
  • 深入理解计算机系统(系统I/O章节)
正文到此结束
Loading...