UNPv1第四章:基本TCP套接口编程

来源:互联网 发布:知乎的发展 编辑:程序博客网 时间:2024/05/16 11:05

1 socket函数

为了执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型

#include <sys/socket.h>int socket (int family, int type, int protocol);//返回:若成功则为非负描述符,若出错则为-1

其中family指明协议族,type参数指明套接字类型,protocol参数应该设为某个(见下图)协议类型常值,或者设为0,以选择所给定family和type组合的系统默认值
socket函数中family和type参数的组合
这里写图片描述

2 connect函数

TCP客户用connect函数来建立与TCP服务器的连接

#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);                //返回:若成功则为0,若出错则为-1

sockfd是socket函数返回的套接字描述符,剩下的2个参数分别是一个指向套接字地址结构的指针和该结构的大小。connect函数将激发TCP的三次握手过程,而且仅在连接建立成功或出错时才返回,其中出错有如下几种情况:
1).若TCP客户没有收到SYN包的响应,则返回ETIMEDOUT错误。如调用该函数时,内核发送一个SYN,若无响应则等待6s后再发一个,若仍无响应,则等待24s再发一个,若总共等了75s后仍未收到响应消息则返回该错误(因内核而异)。
2).若响应时RST,表明该服务器主机在我们指定的端口上没有进程等待,客户收到RST包后马上返回ECONNREFUSED错误。
3).若客户发出的SYN在中间的路由器上引发了一个“destination unreachable”的ICMP错误,则按第一种情形继续发送SYN,若在规定的时间内没有收到回应,则将ICMP错误作为EHOSTUNREACH或ENETUNREACH错误返回。
函数connect导致从CLOSED状态(socket之后一直是这状态)转到SYN_SENT状态,若成功再转到ESTABLISHED状态。

3 bind函数

bind函数把一个本地协议地址赋予一个套接字。对于网际协议,协议地址是一个ip地址和一个端口号

#include <sys/socket.h>int bind (int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);//返回,成功为0,出错为-1

参数sockfd是socket函数返回的套接字描述符,myaddr是一个指向特定于协议的地址结构的指针,第三个参数是该地址结构的长度,对于TCP,调用bind函数可以指定一个端口,或者指定一个地址,也可以两者都指定,还可以都不指定。

4 listen函数

函数listen 仅被TCP服务器调用,它做两件事件:
1).当函数socket创建一个套接口时,它被假设为一个主动套接口,也就是说,它是一个将调用connect发起连接的客户套接口,函数listen将未连接的套接口转换成被动套接口,指示内核应接受指向此套接口的连接请求,
2).函数的第二个函数规定了内核为此套接口排队的最大连接个数

#include <sys/socket.h>int listen (int sockfd, int backlog);//返回,成功为0,出错-1 

要理解backlog参数,我们要知道内核为任何一个给定的监听套接字维护2个队列:
1).未完成连接队列。客户和服务器之间的tcp三次握手并未完成。
2).已完成连接队列。tcp的三次握手已经完成,处于ESTABLISHED状态。
这里写图片描述
关于两个队列的处理:
1.listen函数的backlog参数曾被规定为两个队列总和的最大值
2.源自Berkeley的实现给backlog增设了一个模糊因子,把它乘以1.5得到未处理队列最大长度
3.不要把backlog定义为0,因为不同的实现对此有不同的解释
4.在三路握手正常完成的前提下(也就是说没有丢失分节,从而没有重传),未完成连接队列的任何一项在其中的存留时间就是一个RTT,而RTT的值取决于特定的客户与服务器
当一个客户SYN到达时,若这些队列是满的,TCP就忽略该分节,也就是不发送RST
5.在三路握手完成后,但在服务器调用accept之前到达的数据应由服务器TCP排列,最大数据量为相应已连接套接字的接受缓冲区大小

5 accept函数

accept函数由TCP服务器调用,用于从已完成连接队列列头返回下一个已完成连接,如果已完成连接队列为空,进程将被投入睡眠

#include <sys/socket.h>int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);//返回:若成功为非负描述符,出错为-1

参数cliaddr和addrlen返回已连接的客户的协议地址,如果对客户的协议地址不感兴趣,可以置为空,参数addrlen是值-结果参数,在函数调用的时候是传入的套接字地址结构的大小,函数返回时它的值是内核存放在该套接字地址结构中的确切字节数。
如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与返回客户的TCP连接,一般我们称accept函数第一个参数为监听套接字描述符(由socket创建,随后用作bind和listen的第一个参数的描述符),称它的返回值为已连接套接字描述符
accept 函数最多返回三个值:一个既可能是新的套接字描述符也可能是出错指示的整数、客户进程的协议地址(由cliaddr指针所指)、以及该地址的大小(由addrlen指针所指)。

6 fork和exec函数

fork函数(包括有些系统可能提供的它的各种变体)是Unix中派生新进程的唯一方法。

#include <unistd.h>pid_t fork(void);//返回:在子进程中为0,在父进程中为子进程的ID,若出错为-1

理解fork的最难之处在于调用一次,它却返回两次,返回值本身告知当前进程是子进程还是父进程。
fork 在子进程中返回0,在父进程中返回子进程的ID号的原因在于:一个子进程只有一个父进程,而且在子进程中可以通过调用getppid获取父进程ID。但是父进程可以有多个子进程,并且在父进程中没有办法获取子进程的ID,如果父进程想跟踪子进程,那么它必须在fork返回后保存子进程的ID。
fork函数的2个典型用法:
(1)一个进程创建一个自身的副本,每个副本执行各自的操作。
(2)一个进程想要执行另外一个程序,那么它先调用fork函数创建一个自身的副本,然后调用exec函数把自身替换成新的程序。
下面exec函数之间的区别在于
a.待执行的程序是由文件名还是由路径名指定
b.新程序的参数是一一列出还是由一个指针数组来引用
c.把调用进程的环境传递给新程序还是给新城粗指定新的环境

#include <unistd.h>int execl (const char *pathname, const char *arg0, ... /* (char *) 0 */ );int execv (const char *pathname, char *const argv[]);int execle (const char *pathname, const char *arg0, .../* (char *) 0, char *const envp[] */ );int execve (const char *pathname, char *const argv[], char *const envp[]);int execlp (const char *filename, const char *arg0, ... /* (char *) 0 */ );int execvp (const char *filename, char *const argv[]);

另外进程在调用exec之前打开着的描述符通常跨exec继续保持打开。

7 close函数

通常的Unix close函数也用来关闭套接字,并终止TCP连接

#include <unistd.h>int close (int sockfd);         //返回:若成功为0,出错为-1

close一个TCP套接字的默认行为是把该套接字设置成已关闭,然后立即返回到调用进程,在并发服务器中,fork一个子进程会复制父进程在fork之前创建的所有描述符,复制完成后相应描述符的引用计数会增加1,调用close 会使描述符的引用计数减1,一旦描述符的引用计数为0,内核就会关闭该套接字。调用close后套接字的描述符引用计数仍然大于0的话,就不会引发TCP的终止序列。如果想在一个TCP连接上发送FIN 可以调用shutdown函数。

8 getsockname和getpeername函数

getsockname函数返回与某个套接字关联的本地协议地址,getpeername函数返回某个套接字关联的外地协议地址

#include <sys/socket.h>int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);//返回值:成功返回0,出错返回-1.// socklen_t *addrlen是值-结果参数

需要这两个函数,有如下的理由:
1).在一个没有调用bind的TCP客户上,connect成功返回后,getsockname用于返回内核赋予该连接的本地IP地址和本地端口号。
2).在以端口号0调用后,getsockname用于返回内核赋予的本地端口号
3).一旦连接建立,获取客户身份便可以调用getpeername。
4).在一个以通配IP地址调用bind的TCP服务器上,与某个客户的连接一旦建立(accept成功返回),getsockname就可以用于返回由内核赋予该连接的本地IP地址,在这样的调用中,套接字描述符参数必须是已连接套接字的描述符,而不是监听套接字的描述符
5).当一个服务器是由调用过accept的某个进程通过调用exec执行程序时,它能够获取客户身份的唯一途径便是调用getpeername

0 0