用于指定通信的协议类型,它的返回值为socket descriptor
函数定义为 int socket(int family,int type,int protocol),在 sys/socket.h中定义。
客户端用来建立与TCP服务器的连接,它的调用将激发TCP的三路握手,即会使当前套接字从CLOSED状态转移到SYN_SENT状态,若成功再转移到ESTABLISHED状态。只有连接建立或者出错才会返回。
connect失败则该套接字不可再用,必须关闭,想要重连接必须再调用socket
对于4.4BSD内核发送SYN,没有响应再等6s发送,无响应等24s,如果总共等了75s仍然没有就返回ETIMEDOUT错误
这是种硬错误。收到RST可能是:没有服务器监听连接的端口;TCP想取消连接;TCP收到一个根本不存在的连接上的分节
这是种软错误
将本地协议地址赋予一个套接字。
本地协议地址:比如 IPv4或IPv6地址与端口的组合
调用bind的端口和地址可以都指定或者都不指定,或者只指定一个。如果端口号不指定,内核会在bind被调用时选择一个临时的端口。
函数定义为 int bind(int sockfd,const struct *myaddr,socklen_t addrlen);第一个参数就是就是socket返回的套接字描述符,第二个参数是指向特定于协议的地址结构的指针,第三个是该地址结构的长度。由于地址结构是个常量,所以如果是内核指定端口,无法返回,所以要获取内核指定的临时端口,必须调用 getsockname
返回协议地址
做两件事
socket创建的套接字默认是用来主动发起请求的,即用来调用connect函数,listen则是将这个套接字变成被动套接字,用来接收请求
backlog的同一个取值根据操作系统不同,实际的数目会有差别
三次握手正常完成的这项会从未完成连接对列移到已完成队列的队尾。当进程调用accept时,已完成队列的头部将返回给进程,如果已完成队列为空,进程将被投入睡眠,睡眠针对的是默认的阻塞模式,直到TCP在该队列中放入一项才唤醒。
当客户SYN到达时,如果队列是满的,TCP会忽略这个包,使得客户端会重传
用于从已完成连接队列队头返回下一个已完成连接。如果accept成功,返回值是有内核自动生成的一个全新的描述符,代表与客户端建立的TCP连接。
一个服务器通常只创建一个监听套接字,他在这个服务的声明周期内一直存在。但是会为每个客户端的连接建立一个以连接套接字,对客户端的服务完成时,就关闭这个连接套接字
首先处于监听状态的服务器监听客户端发来的连接请求
第二步accept返回结果,连接被内核接受,新的套接字(connfd)创建
第三步并发服务器会调用fork,此时listenfd和connfd在父进程和子进程之间共享
最后父进程关闭已连接套接字,子进程关闭监听套接字,由子进程处理与客户端的连接,父进程则继续监听下一个客户端连接请求
父进程中调用fork之前所打开的所有描述符在fork返回之后与子进程共享。
并发服务器的存在是不希望一个服务一个客户端过长时间,而导致整个服务器被单个客户端长期占用,Unix中编写并发服务器最简单的办法就是 fork一个子进程来服务每个客户,一般实现如下:
for(;;){ connfd=Accept(listenfd,..) // fork调用一次会返回两次。在子进程中返回值一次,返回值为0;在调用进程,即父进程,中返回一次,返回值为新建的子进程的进程ID; if((pid=Fork())==0){ Close(listenfd); //子进程不监听,直接关闭 doSomething(connfd); //处理客户端请求 Close(connfd); //处理客户端请求完毕,关闭连接 exit(0); } Close(connfd) //由子进程处理,父进程就可以断开连接 } 复制代码
每个文件或套接字都有一个引用计数。在文件表中维护,它表示的是当前打开着的引用该文件或者套接字的描述符的个数。socket返回后与listenfd关联的文件表项的引用计数值为1,accept返回的connfd也是如此。fork之后,两个文件描述符在父子进程之间共享,因此引用计数均变成2,这样当父进程关闭connfd的时候,只是引用计数从2变成了1,而真正的资源清理和释放只有在变为0才发生。
用来关闭套接字,如果文件的引用计数此时恰好为0,就会发送FIN包,终止TCP连接。
如果想直接终止可以用shutdown