详解基本TCP套接字函数
以下讲解基本TCP套接字函数。
1、socket 函数
指定期望的通信协议类型。
指定期望的通信协议类型。
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
- int socket(int domain, int type, int protocol);
- 返回:若成功则为非负描述符,出错则为-1。
参数说明:
domain: 指明协议族,也称为协议域,是一个常值。
AF_INET IPv4 协议
AF_INET6 IPv6 协议
AF_LOCAL/AF_UNIX Unix协议域
AF_ROUTE 路由套接字
AF_KEY 密匙套接字
type: 指明套接字的类型。
SOCK_STREAM 字节流套接字
SOCK_DGRAM 数据报套接字
SOCK_SEQPACKET 有序分组套接字
SOCK_RAW 原始套接字
protocol: 指明协议类型。一般为0,以选择给定的domain和type组合的系统默认值。
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
函数描述:
socket 函数在成功时返回一个小的非负整数值,与文件描述符类似,我们称它为套接字
描述符,简称 sockfd。为了得到这个套接字描述符,我们只是指定了协议族(IPv4、IPv6
或Unix)和套接字类型(字节流、数据报或原始套接字)。我们并没有指定本地跟远程的
协议地址。
2、connect 函数
TCP 客户用 connect 函数来与 TCP 服务器建立连接。
- #include <sys/socket.h>
- int connect( int sockfd, const struct sockaddr *servaddr, socklen_t addrlen );
- 返回:若成功则为0,出错则为-1。
参数说明:
sockfd: 由 socket 函数返回的套接字描述符。
servaddr、addrlen:指向一个套接字地址结构的指针和该结构的大小。套接字地址结构
必须含有服务器的IP地址和端口号。
函数描述:
客户在调用 connect 函数前并不一定得调用 bind 函数,如果需要的话,内核会确定源
IP地址,并选择一个临时端口作为源端口。所以在客户进程中的套接字一般只需指明客户
所要连接的服务器的IP跟端口号。
如果是 TCP 套接字,调用 connect 函数将激发 TCP 的三路握手。而且仅在连接成功或
出错时才返回。其中出错的情况有如下几种:
1-> TCP 客户没有收到 SYN 分节的响应。
2-> TCP 服务器对客户的 SYN 分节的响应是 RST 。
3-> 客户发出的 SYN 分节在某个路由器器上发生了错误。
若 connect 调用失败则该套接字不再可用,必须关闭,我们不能对这样的套接字再次执行
connect 函数。
3、bind 函数
将一个本地协议地址赋予一个套接字。对于网际网协议,协议地址是32位的IPv4地址和128
位的IPv6地址与16位的TCP或UDP端口号的组合。bind 函数主要用于服务器端,用来指定本地
主机的哪个网络接口(IP,可以是INADDR_ANY,表示本地主机的任一网络接口)可以接受客户
端的请求,和指定端口号(即开启的等待客户来连接的进程)。
- #include <sys/socket.h>
- int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
- 返回:若成功则为0,出错则为-1。
参数说明:
sockfd: socket 函数返回的套接字描述符。
myaddr、addrlen:指向一个套接字地址结构的指针和该结构的大小。
函数描述:
对于 TCP ,调用 bind 函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还
可以两者都不指定。
服务器在启动时捆绑它们众所周知的端口号(如何捆绑?)。如果一个TCP客户或服务器未曾调用
bind捆绑一个端口,当调用 connect 或 listen 时,内核就要为相应的套接字选择一个临时端口。
让内核来选择临时端口对于TCP客户来说是正常的,除非应用需要一个预留端口。然而对于TCP服务
器来说却极为罕见,因为服务器是通过它们的众所周知的端口号来被大家认识的。
进程可以把一个特定的IP捆绑到它的套接字上,不过这个IP地址必须属于其所在主机的网络接口之
一(对于TCP服务器)。对于TCP客户,这就为在该套接字上发送的IP数据报指派了源IP地址(服务器
源地址)。对于TCP服务器,这就限定该套接字只接收那些目的地为这个IP地址的客户连接。TCP套接
字通常不把IP地址捆绑到它的套接字上。当连接套接字时,内核将根据所用外出网络接口来选择源IP
地址,而所用外出端口则取决于到达服务器所需的路径。如果TCP服务器没有把IP地址捆绑到它的套接
字上,内核就会把发送的SYN的目的IP地址作为服务器的源IP地址(即服务器IP等于INADDR_ANY的情
sockfd: socket 函数返回的套接字描述符。
myaddr、addrlen:指向一个套接字地址结构的指针和该结构的大小。
函数描述:
对于 TCP ,调用 bind 函数可以指定一个端口号,或指定一个IP地址,也可以两者都指定,还
可以两者都不指定。
服务器在启动时捆绑它们众所周知的端口号(如何捆绑?)。如果一个TCP客户或服务器未曾调用
bind捆绑一个端口,当调用 connect 或 listen 时,内核就要为相应的套接字选择一个临时端口。
让内核来选择临时端口对于TCP客户来说是正常的,除非应用需要一个预留端口。然而对于TCP服务
器来说却极为罕见,因为服务器是通过它们的众所周知的端口号来被大家认识的。
进程可以把一个特定的IP捆绑到它的套接字上,不过这个IP地址必须属于其所在主机的网络接口之
一(对于TCP服务器)。对于TCP客户,这就为在该套接字上发送的IP数据报指派了源IP地址(服务器
源地址)。对于TCP服务器,这就限定该套接字只接收那些目的地为这个IP地址的客户连接。TCP套接
字通常不把IP地址捆绑到它的套接字上。当连接套接字时,内核将根据所用外出网络接口来选择源IP
地址,而所用外出端口则取决于到达服务器所需的路径。如果TCP服务器没有把IP地址捆绑到它的套接
字上,内核就会把发送的SYN的目的IP地址作为服务器的源IP地址(即服务器IP等于INADDR_ANY的情
况)。
实际上客户的源IP地址就是服务器的目的地址,服务器的源IP地址就是客户的目的地址,说到底也就只
存在两个IP地址:客户IP跟服务器IP。
实际上客户的源IP地址就是服务器的目的地址,服务器的源IP地址就是客户的目的地址,说到底也就只
存在两个IP地址:客户IP跟服务器IP。
4、listen 函数
- #include <sys/socket.h>
- int listen(int sockfd, int backlog);
- 返回:若成功则为0,出错则为-1。
函数描述:
listen 函数仅由 TCP 服务器调用,它做两件事情。
(1)把一个未连接的套接字(主动)转换成一个被动套接字,指示内核应该接受指向该套接字的连接请
listen 函数仅由 TCP 服务器调用,它做两件事情。
(1)把一个未连接的套接字(主动)转换成一个被动套接字,指示内核应该接受指向该套接字的连接请
求。
(2)backlog 参数规定了内核应该为相应套接字排队的最大连接数。其中内核始终为监听套接字维护两个
队列。
(1)未完成连接队列,每个SYN分节对于其中一项:
已由某个客户发出并到达服务器,而服务器正在等待待完成的TCP三路握手过程。这些套接字处
(2)backlog 参数规定了内核应该为相应套接字排队的最大连接数。其中内核始终为监听套接字维护两个
队列。
(1)未完成连接队列,每个SYN分节对于其中一项:
已由某个客户发出并到达服务器,而服务器正在等待待完成的TCP三路握手过程。这些套接字处
于SYN_RCVD状态。
(2)已完成连接队列
每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态。
backlog 就是这两个队列和的最大值。
在三路握手完成之后,但在服务器调用 accept 之前到达的数据应由 TCP 服务器排队,最大数据量为
(2)已完成连接队列
每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态。
backlog 就是这两个队列和的最大值。
在三路握手完成之后,但在服务器调用 accept 之前到达的数据应由 TCP 服务器排队,最大数据量为
相应已连接套接字的接收缓冲区的大小。
5、accept 函数
accept 函数由 TCP 服务器调用,用于从已完成连接队列头返回下一个已完成连接。如果已完成队列为
accept 函数由 TCP 服务器调用,用于从已完成连接队列头返回下一个已完成连接。如果已完成队列为
空,那么进程被投入睡眠(假设套接字为默认的阻塞方式)。
- #include <sys/socket.h>
- int accept(int sockfd ,struct sockaddr *cliaddr, socklen_t *addrlen);
- 返回:若成功则为非负已连接描述符和对端的IP和端口号,出错则为-1。
参数说明:
cliaddr、addrlen 用来返回已连接的对端进程(客户)的协议地址 。调用前,我们将由 *addrlen 所
引用的整数值置为由cliaddr所指的套接字地址结构的长度,返回时,该整数值即为内核存放在该套接字
地址机构内的确切字节数。
函数描述:
如果 accept 调用成功,那么其返回值是由内核自动生成的一个全新描述符,代表着与所返回客户的TCP
连接。在讨论 accept 函数时,我们称它的第一个参数为监听套接字描述符(由 socket 创建,随后用
cliaddr、addrlen 用来返回已连接的对端进程(客户)的协议地址 。调用前,我们将由 *addrlen 所
引用的整数值置为由cliaddr所指的套接字地址结构的长度,返回时,该整数值即为内核存放在该套接字
地址机构内的确切字节数。
函数描述:
如果 accept 调用成功,那么其返回值是由内核自动生成的一个全新描述符,代表着与所返回客户的TCP
连接。在讨论 accept 函数时,我们称它的第一个参数为监听套接字描述符(由 socket 创建,随后用
作bind 和 listen 的第一个参数的描述符),称它的返回值为已连接套接字描述符。区分这两个套接字
非常重要。一个服务器通常仅仅创建一个监听套接字,它在服务器的生命期内一直存在。内核为每个服
务器进程接受的客户连接创建一个已连接套接字(也就是说对于它的TCP三路握手过程已经完成)。当服
务器完成对某个连接客户的服务时,相应的已连接套接字就要被关闭。
6、fork 函数
是 Unix 中派生新进程的唯一方法。
6、fork 函数
是 Unix 中派生新进程的唯一方法。
- #include<unistd.h>
- pid_t fork(void);
- 返回:在子进程中为0,在父进程中为子进程ID,出错则为-1.
函数描述:
fork 调用一次却返回两次。它在调用进程(父进程)中返回一次,返回值是新派生进程(子进程)
的进程ID号;在子进程又返回一次,返回值为0。因此,返回值本身告知当前进程是子进程还是父
进程。
fork 在子进程返回0而不是父进程的进程ID的原因在于:任何子进程只有一个父进程,而且子进程
总是可以通过调用 getppid 取得父进程的进程ID。相反,父进程可以有许多子进程。而且无法通过
函数调用来获取子进程的进程ID。如果父进程想要跟踪所有子进程的进程ID,那么它必须记录每次
fork 调用的返回值。当前进程可以通过 getpid 系统调用来获得自己的进程ID。
父进程中调用 fork 之前打开的所有描述符在 fork 返回之后由子进程共享,数据段会得到一份拷贝
而不是共享。
fork 调用的两个典型用法:
(1)一个进程创建一个自身的副本,这样每个副本都可以在另一个副本执行其他任务的同时处理各自的
操作。这是网络服务器的典型用法。
(2)一个进程想要执行另一个程序。既然创建新进程的唯一方法是调用 fork ,该进程于是首先调用
fork创建一个自身的副本,然后其中一个副本调用 exec 把自身替换成新的程序。这是诸如 shell 之
类程序的典型用法。
6、exec 函数族
一个 exec 函数可以把当前进程替换成一个新进程,新进程由 path 或 file 参数指定。
6、exec 函数族
一个 exec 函数可以把当前进程替换成一个新进程,新进程由 path 或 file 参数指定。
- #include <unistd.h>
- extern char **environ;
- int execl(const char *path, const char *arg, ...);
- int execlp(const char *file, const char *arg, ...);
- int execle(const char *path, const char *arg,
- ..., char * const envp[]);
- int execv(const char *path, char *const argv[]);
- int execvp(const char *file, char *const argv[]);
- int execve(const char *path, char *const argv[], char *const envp[]);
- 返回:成功则不返回,出错则为-1。
函数描述:
这 6 个 exec 函数之间的区别在于:(a)待执行的程序的文件名是由文件名(file)指定还是由路径名
(path)指定;(b)新程序的参数是一一列出还是由一个指针数组来引用;(c)把调用进程的环境传递给新
程序还是给新程序指定新的环境。这些函数只在出错时才返回调用者。否则,控制权将传递给新程序的
起始点。通常就是 main 函数。一般来说 execve 是内核中的系统调用,其他 5 个都是调用 execve
的库函数。
7、getsockname 和 getpeername 函数
7、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);
- 返回:成功则不返回,出错则为-1。
这两个函数返回与某个套接字关联的本地协议地址(getsockname),或者返回与某个套接字关联的外地协
议地址(getpeername)。