我们现在所使用的网络TCP/IP 其实就是大牛们几十年前发明的东西,经过几十年的发展,虽然出现了很多的其它协议,但是底层的东西却基本稳定。现在的B/S ,C/S 等的网络体系都是运行在这样一个网络体系之上的。
socket 函数
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domin ,int type, int protocol);
这是执行网络IO 的第一步,指定期望的通信类型。
@domin 协议类型
@type 套接字类型
@protocol 协议类型常值
这里有一个TCP/IP 服务器的基本流程。
这个函数成功后返回一个小的非负整数值,我们称之为套接字描述符,简称sockfd,这里sockfd 仅仅指定了协议族和套接字模型,并没有指定本地协议或远程协议地址。
connect 函数
#include<sys/types.h>
#include<sys/socket.h>
int connect (int sockfd, const struct sockaddr * addr,socklen_t addrlen);
@sockfd 套接字描述符
@sockaddr 套接字结构体指针
@addrlen 该结构体大小
客户在调用这个函数之前不必非调用bind 需要的话,内核会提供给它一个确定的IP地址,并且选择一个临时的端口。
如果是TCP 连接,传说中的三路握手就再这个函数上,它返回有以下的几种情况:
1.若TCP客户没有收到SYN分节的响应返回ETIMEDOUT 错误,比如说BSD 调用这个函数后发送一个SYN 若无响应则等待6S后再次发送,下一次等24S在总共75秒的等待后,仍然无响应返回本错误,但是有些使用的则是超时机制。
2.若对用户的SYN 响应的是RST,则表示该服务器的在我们指定的端口上没有连接的进程,就是服务的程序。这就是个硬伤,所以一但接收到这个标记就会立刻返回ECONNREFUSED错误。
发生RST 的条件有以下的三种情况:
@目的地某端口的SYN 到达,然而该端口上并没有相应的服务程序。
@TCP 想取消一个已有的链接。
@TCP 接收到一个根本不存在的的链接上的分节。
3.若客户发出的SYN 在中间的某个路由器上引发一个destination unreachable (目的不可达)ICMP错误,则认为是一个软错误。客户主机内核保存这个消息,然后尝试集序发送SYN ,当然在一定的等待秒数之后,内核就会把自己保存的错误信息发送给相应的进程。当然也可能出现以下的错误原因:
#按照本地路由表根本没有到达远程系统的路径;
#connect 调用根本不等待就返回;
下面是一些问题的说法:
@当我们向根本不存在的IP发送请求的时候,它将永远收不到ARP 响应,当超时后就会得到该错误了。(本地子网并不存在的IP)
@当我们尝试链接一个没有运行的主机,直接就会得到链接失败的错误。
@我们指定一个因特网不可到达的IP的时候,就会发现ICMP 错误
connect 函数将当前套接字从CLOSED 状态转移到SYN_SENT 状态正常情况下在 转移到ESTABLISHED状态。
bind 函数
#include<sys/socket.h>
int bind(int socket ,const struct sockaddr *address, socklen_t address_len);
这个函数把本协议地址赋予一个套接字,对与网际协议,协议地址是32位的IPv4地址或128位的IPv6地址与16位的TCP或UDP 端口号组合。
@sockfd 套接字
@是一个指向特定于协议的地址结构指针
@该结构地址长度
对与TCP 来说,调用这个函数可以指定一个端口号,或者指定一个IP地址,也可以全部指定或者全部都不指定。
对与客户的来说让内核随意选定端口是正常的所以它可以不调用绑定函数,但是对与服务器来说就是及其罕见的,因为端口是服务器标识的一部分。
进程可以把IP和端口绑定在这个套接字上,这样这个服务器进程就会只接受向这个IP端口来的信息,正好符和我们的一贯做法。但是TCP客户一般并不需要这样做,内核会根据所用的外出网络接口来选择源IP地址。而外出端口再则是根据向服务器的地址路径来决定的。
当端口设置为0的时候,内核会在这个函数被调用时选择一个临时端口。我们也回发现这个函数的第二个参数是const 的,也就是说如果内核自己选定了参数我们就不能直接获取这个地址和端口号,但是我们可以使用 getsockname( ) 函数来获取这些信息。
listen 函数
#include<sys/types.h>
#include<sys/socket.h>
int listen (int sockfd, int backlog);
这个函数只做两件事情:
socket创建一个套接字,然后假设在这个套接字是一个主动的套接字,他是一个将调用connect 发起链接的客户套接字。listen 把一个未链接的套接字转换成一个被动套接字,指示内核应该接受指向该套接字的链接请求。
第二个参数指定了内核规定应该为相应套接字排队的最大链接数。
本函数应该在调用socket 和 bind 这两个函数之后,并在调用accept 函数之前调用。
其中第二个参数是两个队列的长度总和,未完成链接队列,已完成链接队列。
未完成链接队列:
每个这样的SYN对应其中一样,正在等待完成相应的TCP三路握手过程,这些套接字处在SYN_RCVD状态。
已完成链接队列:
每个已完成TCP三路握手过程的客户对应其中的一项,这些套接字是 ESTABLISHED状态。
网上找来一个图。还是UNP书上的。
accept 函数
#include<sys/socket.h>
int accept(int sockfd ,struct sockaddr * cliaddr, socklen_t *addrlen)
这个函数由TCP服务器调用,用于从已完成链接队列队头返回下一个已完成链接,如果已完成链接队列为空,那么进程被投入睡眠。
这个函数成功调用后返回一个由内核自动生成的一个全新的描述符,代表与所返回客户的TCP链接。本函数最多返回三个值一个可能的新的套接字描述符,也可能是出错的整数,客户进程的协议地址,以及该指针的大小。如果对用户地址不感兴趣,我们就可以把参数设置为空指针。