Unix网络编程-3,4,5,6

套接字地址结构

  • 每一个协议族都有自己的套接字地址结构,这些地址结构以 sockaddr_ 开头,后跟协议族特定的后缀,如 TCP/IPV4 协议族对应的套接字地址结构为 sockaddr_in.

    • sockaddr_in,TCP/IPV4对应的套接字地址结构,内部以网络字节序存放着IP地址与端口号.具体结构如下:

struct sockaddr_in{
    sa_family_t sin_family;
    in_port_t sin_port; /* 端口号 */
    struct in_addr sin_addr; /* IP 地址 */
};
struct in_addr{
    in_addr_t s_addr;
};

  • 通用地址结构,其名为 sockaddr;其存在的唯一意义就是将指向特定于协议的套接字地址结构的指针进行强制类型转换.如下:

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);/* bind() 函数的原型中使用的是 struct sockaddr 类型,所以再将 TCP 套接字绑定到一个地址上时需要进行强制类型转换, */
struct sockaddr_in local_addr;
/* 填充 local_addr */
bind(sockfd,(struct sockaddr*)&local_addr,sizeof(local_addr));

MSB,LSB

  • MSB,most significant bit,最高有效位;LSB,least significant bit,最低有效位;对于整数 123456,可以认为最高有效位是 1,最低有效位是 6.以下函数用于将整数在主机字节序以及网络字节序之间进行转换.

  • 主机字节序,网络字节序转换接口如下:

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

inet_pton(),inet_ntop()

  • const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);将二进制形式(网络字节序)的IP地址(包括IPV4,IPV6)转换为字符串表示形式(对于IPV4来说,字符串表示形式即点分十进制).

    • af 若为 AF_INET,则表明 src 的实际类型为 struct in_addr*,即IPV4地址;若为 AF_INET6,则表明为IPV6地址.

    • src 指向着二进制形式的IP地址.

    • dst,size 缓冲区的起始地址以及缓冲区的字节长度.当 af 为 AF_INET 时,size 的值至少应该大于或等于 INET_ADDRSTRLEN.

    • 返回值 若为 0,则表明 af 参数的值不是合法值或者缓冲区长度过短.

  • int inet_pton(int af, const char *src, void *dst);将字符串表示的IP地址(包括IPV4,IPV6)转换为二进制形式(网络字节序).

    • af 若为 AF_INET,则 dst 的实际类型是 struct in_addr* 类型,即 IPV4 地址.

    • src 指向着字符串表示的 IP 地址

    • dst 转换后的二进制形式的IP地址将存放在 dst 指向的缓冲区中.

    • 返回值

      • 若为-1,则表明 af 参数的值不合法.

      • 若为0,则表明 src 指向的字符串格式不正确.

      • 若为1,则表明结构正确.

TCP套接字,基本编程

  • 介绍了几个基本接口,利用这些接口可以完成基本的TCP套接字编程;

socket()

  • int socket(int domain, int type, int protocol);创建一个套接字,套接字是协议的接口,在利用某协议进行通信之前,必须创建该协议对应的套接字.

    • domain,type,protocol 确定套接字所对应的协议,如

      • domain=AF_INET,type=SOCK_STREAM,protocol=IPPROTO_TCP,则表明套接字关联的协议是 TCP 协议

      • domain=AF_INET,type=SOCK_DGRAM,protocol=IPPROTO_UDP,则表明套接字关联的协议是 UDP 协议

bind()

  • int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);在指定的端口下面创建一个TCP/UDP端点,并将该端点与套接字 sockfd 关联起来,至此当从 sockfd 读取时,相当于读取关联端点的接受缓冲区,当写入 sockfd 时,相当于写入关联端口的发送缓冲区.

    • addr 将根据 addr 中保存的 IP 地址与端口号为初始化端点的本地IP地址,本地端口号.其中

      • sin_addr,若为 INETADDR_ANY,则表示通配地址;否则必须是主机网络接口之一.

      • sin_port,若为0,则内核随机选择一个未用的端口号.

  • 以 INETADDR_ 开头的地址使用主机字节序进行保存,如:

struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(3333);
local_addr.sin_addr.s_addr = htonl(INETADDR_ANY);/* 将主机字节序转换为网络字节序 */

connnect()

  • int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);首先若 sockfd 尚未关联过 TCP 端点(即未调用 bind() ),则为该 sockfd 关联一个 TDP 端点(此时端点的本地 IP 地址为 addr->sin_addr 的出口地址,本地端口号为随机),然后向指定的IP地址:端口号发起三次握手,直至三次握手失败或者 sockfd 进入 ESTABLISHED 状态后才会返回.

    • 若三次握手失败,则套接字 sockfd 不再可使用,此时应该调用 close(sockfd) 来关闭套接字,并释放底层资源.

listen(),accept()

TCP 连接模型

  1. 调用如下接口,开始监听,此时 TCP 端点如图:

    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in dst_addr;
    bind(sockfd,makeSockaddr(&dst_addr,"*",8080),sizeof(dst_addr));
    listen(sockfd,7);

  2. 当TCP收到一个SYN数据包时,会新建一个TCP端点,如下图,然后在该TCP端点上执行三次握手,待三次握手完成后,将该端点加入已完成连接队列.

    Unix网络编程-3,4,5,6

listen()

  • int listen(int sockfd, int backlog);为已完成队列分配内存空间,使其可以存放 backlog 个端点;并且让 sockfd 关联的端点进入 LISTEN 状态,开始监听.

accept()

  • int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);若已完成链接队列为空,则会主动放弃CPU,睡眠一段时间再重试;若已完成链接队列不为空,则从已完成链接队列中移除一个端点,并且将端点的远端IP地址与远端端口号复制到 addr 指向的缓冲区中,然后为该端点创建一个套接字描述符(即该套接字描述符与端点关联),并返回该描述符.

    • addr 输出参数,若不为0,则将(被移除的)端点的远程IP地址,远端端口号复制到 addr 指向的缓冲区中.

    • addrlen 输入/输出参数,作为输入参数,addrlen 是 addr 指向缓冲区的字节长度;作为输出参数,内核将占用的实际字节长度存放在 *addrlen 中.

    • 返回值 一个新的文件描述符.

shutdown()

  • int shutdown(int sockfd, int how);禁止套接字描述符 sockfd 可读,或可写的能力.即此后 sockfd 将不可读,或者不可写.

    • how 可以取 SHUT_RD,SHUT_WR,SHUT_RDWR

      • SHUT_RD 此时套接字描述符 sockfd 不再可读,下一次调用 read() 将返回 0.

      • SHUT_WR 此时套接字描述符 sockfd 不再可写,下一次调用 write() 将触发信号 SIGPIPE.在 TCP 中,此时会发送 FIN 报文到另一端.

      • SHUT_RDWR 即 sockfd 不再可读,也不再可写,

close()

  • int close(fd);将 fd 标记为已关闭,然后立即返回.标记为已关闭,意味着当 fd 关联的 TCP 端点中发送缓冲区的数据发送完毕后,执行四次挥手,并且释放底层资源.

    • fd,也算是文件描述符,所以'标记为已关闭'这项动作将在引用计数为0时才会实施,参见'打开的文件'

RST 与 FIN

  • TCP连接是一个双向连接,发送 FIN 仅是终止一端到另一端的单向连接,而发送 RST 则是终止双向连接.如:

Unix网络编程-3,4,5,6

getpeername(),getsockname()

  • TCP端点的四元组,本地IP地址:本地端口 远程IP地址,远程端口.

  • int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 返回套接字 sockfd 关联的TCP端点的远程IP地址,远程端口.

  • int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 返回套接字 sockfd 关联的TCP端点的本地IP地址,本地端口.

Unix上的IO模型

  • Unix上具有5种IO模型:阻塞式IO,非阻塞式IO,IO复用,信号驱动式IO,异步IO;在 Unix 中,一次读取操作可以分为2步,如下,

    1. 等待数据准备好(在读取套接字时,这一步意味着从网络中等待数据,将数据复制到内核空间)

    2. 将数据从内核空间复制到进程空间.

  • 根据读取操作来理解5种IO模型:

    • 阻塞式IO,当数据尚未准备好时,此时将挂起进程直至数据准备完毕.

    • 非阻塞式IO,当数据尚未准备好时,出错返回,而不是挂起进程.

    • IO复用,由一个特殊的函数来监控数据是否准备完成,该函数会挂起进程直至数据准备完毕.

    • 信号驱动式IO,由内核为我们监视数据是否准备完毕,即内核会在数据准备完毕后发送 SIGIO 信号通知进程.

    • 异步IO,内核会为我们监视数据是否准备完毕,并且在数据准备完毕后自动将数据由内核空间拷贝到进程空间,在拷贝完成后以某种机制通知我们.

  • 同步IO/异步IO

    • 同步IO操作,该操作会阻塞请求进程,直至IO操作完成.

    • 异步IO操作,该操作不会阻塞请求进程,即当一个进程请求异步IO操作时,该异步IO操作并不会阻塞进程

    • 在 Unix 五种IO模型中,前四种都属于同步IO操作,因为他们都需要将数据从内核空间拷贝到进程空间,而这一步将阻塞进程,第五种IO模型为异步IO操作.

IO复用

  • IO复用,即提供一个特殊的函数,该函数可以监视多个文件描述符;并挂起进程,直至文件描述符上指定的条件满足.该特殊函数可以是 select(),poll(),

select()

  • int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);该函数会挂起当前进程(线程),直至 readfd,writefds,exceptfds 中至少一个文件描述符达到指定的条件,或者超时.

    • nfds 为 readfds,writefds,exceptfds 中最大文件描述符的值+1;即内核会检测 readfds,writefds,exceptfds 集合中位于 [0,nfds) 范围内的文件描述符.

    • readfds 对于该集合内的文件描述符来说,满足条件意味着:

      • 对该文件描述符调用 read() 不再阻塞(包括 read() 返回-1,0,>0的情况),此时文件描述符上可能有准备好的数据,或者出错,或者到达了文件结束符.

      • 该文件描述符是一个监听套接字,并且已完成连接队列不为0,即对该监听套接字调用 accept() 不会阻塞.

    • writefds 对于该集合内的文件描述符来说,满足条件意味着:

      • 对该文件描述符调用 write() 不再阻塞,此时包括 write() 出错返回,正常返回,或者触发了信号 SIGPIPE.

      • 该文件描述符关联的 TCP 端点已完成三次握手,此时包括三次握手成功,或者三次握手失败

    • exceptfds 对于该集合内的文件描述符来说,满足条件意味着:

      • 该文件描述符关联的 TCP 端点收到了带外数据,

    • timeout 指定了最大超时时间,该参数有三种情况:

      • 若 timeout==0,则 select() 会一直阻塞下去,直至至少一个文件描述符满足条件.

      • 若 timeout->tv_sec == 0&&timeout->tv_usec==0,此时 select() 检查 readfds,writefds,exceptfds 内的文件描述符后立即返回,不会阻塞.

      • 其他情况,此时 select() 会挂起进程,直至超时或者至少一个文件描述符满足条件.

    • 返回值 若为 -1,则表示出现了某种错误;否则为满足条件的文件描述符个数(若 readfds,writefds 中都可以一个文件描述符 fd,且该文件描述符满足了可读可写,则此时 select() 返回 2),此时 readfds,writefds,exceptfds 中仍存在的文件描述符表明它们满足了相应的条件,未满足条件的描述符将被移除.

  • fd_set 文件描述符集合,存放着多个文件描述符,常见的操作如下:

  • void FD_CLR(int fd, fd_set *set);从文件描述符集合中移除文件描述符 fd.

  • int FD_ISSET(int fd, fd_set *set);若返回值非0,则表示在文件描述符集合 set 中存在 fd.

  • void FD_SET(int fd, fd_set *set);将文件描述符 fd 加入到集合 set 中.

  • void FD_ZERO(fd_set *set);清空文件描述副集合 set.

poll()

  • 不介绍 poll().


 





















































































你可能感兴趣的:(Unix网络编程-3,4,5,6)