深入学习TCP套接口
基本TCP套接口编( 网络I/O)
虽然都知道套接口可以完成两个主机之间的网络通信,但也就只是知道,有很多已经写好的代码,我们直接copy一些就可以用了,但是!!你理解他为何这样用么?TCP/UDP又到底是啥?TCP/UDP又是怎么和socket联系在一起的?tcp三次握手又是如何产生的???
今天我们就先来谈谈TCP套接口 好好学习吧 好嘛? 少看zhibo
======================================正经分界线,现在开始严肃!!进入学霸角色
老生常谈tcp client 建立tcp sockect 通信步骤分别是: socket->connet->write->read->close (划重点client在调用connect前不必非得bind!!!!!因为如果需要的话内核会确定一个源IP地址,并选择一个临时端口作为源端口)
竟然刚开始就调用了socket函数怎们就下来看看它!整明白
socket 函数
#include <socket.h>
int socket(int family,int type,int protocal) //socket在返回成功时返回一个最小的非负整数(后面你会看到只要是返回描述符都是返回当前最小的未用的描述符),失败时返回 -1,因为这是套接口返回的所以我们叫他“套接口表述符” 简称“套接字”
为了执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议(你是要建立tcp连接还是udp连接呀?你是要使用ipv6还是ipv4呀?你得自己先搞清楚你要建立啥样的连接吧!这些就是通过这三个参数指定的)
family AF_INET IPV4协议 AF_INTE6 IPV6协议 AF_LOCAL Unix域协议 AF_ROUTE 路由套接口协议 AF_KEY **套接口
type SOCK_STREAM 字节流套接口 SOCK_DGRAM 数据包套接口 SOCK_SEQPACKET 有序分组套接口 SOCK_RAW 原始套接口
protocal IPPROTO_TCP TCP传输协议 IPPROTO_UDP UDP传输协议 IPPROTO_SCP SCTP传输协议 (注意该参数如果设置为0就默认选择系统缺省值)
family 和 type 对应的protocal默认如****并不是所有的组合都有意义,空白部分组合无效)
AF_INET AF_INET6 AF_LOCAL AF_ROUTE AF_KEY
TCP|SCTP | TCP|SCTP | Yes | ||
UDP | UDP | Yes | ||
SCTP | SCTP | Yes | ||
IPV4 | IPV6 | Yes | Yes |
SOCK_STREAM
SOCK_DGRAM
SOCK_SEQPACKET
SOCK_RAW
AF_xxx 与 PF_xxx
有的时候你看code会发现上面的family使用了PF_INET这和上面指定的不同呀!这啥意思?搞得脑子特别胡乱!那么让我现在告诉你
AF_xxx前缀表示地址族,PF_xxx前缀表示协议族。在很久很久以前有这样一个想法:单个协议族可以支持多个地址族,PF_用来创建套接口,AF_用来创建套接口地址,但实际上支持多个地址族的协议族从来就未实现过(纯属扯犊子呢),而且<sys/socket.h> 里给定协议定义的PF_值总是等于AF_值,所以后面我们会都用AF_常值,虽然我们会遇到使用PF_值的code。
connect函数
int connect(int sockfd, const struct sockafddr *servaddr, socklen_t addrlen);
sockfd 就是socket()返回的套接字,servaddr是一个指向套接口地址结构的指针(必须包含服务器的IP地址和端口号),如果是TCP套接口,调用connect函数并激发TCP三路握手过程!!!而且在建立成功或出错时才返回!
错误返回有以下几种情况:
1.TCP客户端没有收到syn响应,则返回ETIMEDOUT,可以理解为client已经进行超时重传了但是还没有等到server 的 ack,就会这个鸭子!
2.TCP客户端收到的响应是RST(表示复位),则表示该服务器在我们指定的端口上没有进程在等待与之连接(例如服务器进程没有运行)。这是一种硬错误,客户端一旦收到RST就马上返回ECONNREFUSED错误。(产生RST的三个条件:目的地为某端口的syn到达,但是端口上没有要监听的服务;TCP想取消一个已有的连接;TCP连接到一个根本不存在连接的分节上)
3.客户发出的SYN在中间路由上引发了"destination unreachable”,客户主机保存改消息,继续发syn就像第一种情况一样(75秒没有回),则把保存的消息作为EHOSTUNREACH或ENETUNREACH错误返回给进程。
注意:connect()函数会导致TCP状态从CLOSE状态转换到SYN_SENT状态,若建立成功再转移到ESTABLISHED状态,若失败就需要关闭该套接口。
bind函数
bind函数把一个本地协议地址赋予一个套接口。对于网际协议,协议地址是32位ipv4地址或128位ipv6地址与16位TCP或UDP端口的组合.
int bind(int sockfd,const struct sockaddr *myaddr, socklen_t addrlen); //成功返回0 失败返回-1
在服务器测我们可以使用bind也可以不使用bind,如果使用bind我们也可以指定端口或不指定端口,对于没有指定的部分内核会选择一个临时的端口,如果没有指定ip那么内核等到套接口已连接或在套接口发出数据的时候才分配一个地址。
INADDR_ANY通配地址,一般为0。
使用方法:
struct sockaddr_in servaddr;
servaddr.sin_addr.s_addr = htol(INADDR_ANY);
bind函数常见的错误就是EADDRINUSE(Address already in use)
listen函数
int listen(int sockfd, int backlog); //成功返回0 出错返回 -1
仅TCP服务器可以使用,他做了两件事
1.socket函数创建一个套接口时,他被设置成一个主动套接口,也就是说她是一个将调用connect的客户套接口。listen函数把一个未连接的套接口转换成一个被动套接口,指示内核应该接受指向该套接口的连接请求。listen导致套接口从CLOSE状态转换到LISTEN状态。
2.第二个参数规定了内核为套接口排队的最大已完成连接数;
为了更好地理解backlog 我们必须认识到内核为每一个监听套接口维护着两个队列:
(1)未完成连接队列:那些处于SYN_RCVD状态的套接口
(2)已完成连接队列:那些处于ESTABLISHED状态的套接口
accept函数
int accept(int sockfd, struct sockaddr *cliaddr,socklen_t *addrlen); //成功返回0 失败返回-1
针对该函数的返回值我们叫他是已连接的套接口(内核为每一个由服务器进程接受的客户连接创建了一个已连接套接口),第一个参数称作监听套接口(该程序生命周期内一直存在)。
cliaddr 和 addrlen 用来返回对端(客户)的协议地址,addrlen是值结果参数,调用前,我们将*addrlen 所引用的整数值设置为cliaddr所指向的套接口的地址长度;返回时,即为由内核存在的该套接口地址结构的确切字节数。
该函数有三个返回值
(1)既可能是新套接口描述字,也可能是出错指示整数
(2)客户进程的协议地址
(3)客户地址的大小
close函数
int close(int sockfd); //成功返回0 失败返回 -1
使用close以后的套接口不能做read 和 write 的第一个参数,然而TCP尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是TCP连终止序列。