目录
socket 函数
为了执行网络 I/O ,第一件事就是调用 socket 函数,指定期望的通信协议类型。
#include <sys/socket.h>
/* Create a new socket of type TYPE in domain DOMAIN, using
protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically.
Returns a file descriptor for the new socket, or -1 for errors. */
int socket(int domain, int type, int protocol);
domain 常用值:
AF_UNIX, AF_LOCAL Local communication
AF_INET IPv4 Internet protocols
AF_INET6 IPv6 Internet protocols
type 常用值:
SOCK_STREAM 字节流套接字
SOCK_DGRAM 数据报套接字
SOCK_SEQPACKET 有序分组套接字
SOCK_RAW 原始套接字
protocol 常用值:
IPPROTO_CP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
返回值:成功返回非负描述符,失败返回 -1。
connect 函数
建立连接
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd 是由socket函数返回的值,addr 是指向一个套接字地址结构的指针,addrlen 是该结构的大小。
返回值:成功返回 0,失败返回 -1。
注意:connect 函数会导致当前套接字从 CLOSED 状态(该套接字自由 socket 函数创建以来一直所处的状态)转移到 SYN_SENT 状态,若成功则再转移到 ESTABLISHED 状态。若 connect 失败则该套接字不再可用,必须关闭,我们不能对这样的套接字再次调用 connect 函数。在每次 connect 失败后,都必须 close 当前的套接字描述符,并重新调用 socket 函数。
bind 函数
把一个本地协议地址赋予一个套接字。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd 是由socket函数返回的值,addr 是指向一个套接字地址结构的指针,addrlen 是该结构的大小。
返回值:成功返回 0,失败返回 -1。
注意:如果一个 TCP 客户或服务器未曾调用 bind 绑定一个端口,当调用 connect 或 listen 时,内核就要为相应的套接字选择一个临时端口。一般来说,TCP 客户常由内核来选择临时端口,而 TCP 服务器来说却极为罕见。下面是一个例外:
通配地址指的是 IP 为 0 的地址,其通常为人所知的名字是 INADDR_ANY。
/* Address to accept any incoming messages. */
#define INADDR_ANY ((in_addr_t) 0x00000000)
extern const struct in6_addr in6addr_any; /* :: */
当由内核来选择端口号时,我们只能通过 getsockname 函数来返回协议地址来知晓。
listen函数
当创建一个套接字时,它被假设为一个主动套接字(客户套接字)。该函数将它转换成一个被动套接字,指示内核应该接受指向该套接字的连接请求。调用该函数会导致套接字从 CLOSED 状态转换成 LISTEN 状态。其第二个参数规定了内核应该为相应套接字排队的最大连接个数。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd 是由socket函数返回的值,backlog 规定了内核应该为相应套接字排队的最大连接个数。
返回值:成功返回 0,失败返回 -1。
注意:内核为任何一个给定的监听套接字维护两个队列
当来自客户的 SYN 到达时,TCP 在未完成连接队列中创建一个新项,然后响应以三路握手的第二个分节:服务器的 SYN 响应,其中捎带对客户 SYN 的 ACK。这一项一直保留在未完成连接队列中,直到三路握手的第三个分节(客户对服务器 SYN 的 ACK)到达或者该项超时为止。如果三路握手正常完成,该项就从未完成连接队列移到已完成连接队列的队尾。当进程调用 accept 时,已完成连接队列中的队头项将返回给进程,或者如果已完成队列为空,那么进程将被投入睡眠,直到 TCP 在该队列中放入一项才唤醒它。
accept 函数
accept 函数由 TCP 服务器调用,用于从已完成连接队列头返回下一个已完成连接。如果已完成队列为空,那么进程投入睡眠(假定套接字为默认的阻塞方式)。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd 为服务器的监听套接字描述符,addr 是指向一个套接字地址结构的指针,addrlen 是值-结果参数。如果 addr 为 NULL, 则addrlen 也应该为NULL。
返回值:成功返回非负描述符,同时 addr 被填充为对端 scoket ,addrlen 被改为对端地址的长度,失败返回 -1。
fork、exec 函数
fork 函数用来派生新的进程。exec 函数用来执行存放在硬盘上的可执行程序文件。
#include <unistd.h>
pid_t fork(void);
extern char **environ;
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
fork 调用一次返回两次。在父进程中返回新派生进程的进程 ID,在子进程中返回值为0。一般根据这个来区分父、子进程。父进程中调用 fork 之前打开的所有描述符在 fork 之后由子进程分享。
exec 函数包括以上6个函数。区别在于:(1)待执行的程序文件是文件名还是由路径指定;(2)新程序的参数是一一列出还是由一个指针数组来引用;(3)把调用进程的环境传递给新程序还是给新程序指定新的环境。
exec 返回值:成功不返回,出错返回 -1。这 6 个函数的关系:
只有 execve 是内核中的系统调用,可以使用 “man 2 execve” 查看,其余的都是给予它的库函数。
注意:要以 NULL 结束可变参数。如下:
int ret = execl("./exe1","exe1",myarg,NULL);
注意:进程在调用 exec 之前打开着的描述符通常跨 exec 继续保持打开。但我们可以使用 fcntl 设置 FD_CLOEXEC 描述符标志禁止掉,这样在 exec 执行的程序里,此描述符被关闭,不能再使用它。如下
int fd = open("test.txt",O_RDONLY);
int val = fcntl(fd,F_GETFD);
val |= FD_CLOEXEC;
fcntl(fd,F_SETFD,val);
close 函数
关闭套接字,并终止 TCP 连接。
#include <unistd.h>
int close(int fd);
返回值:成功返回 0,失败返回 -1。
注意:这个 close 函数只是减少了 fd 的引用计数,并不引发 TCP 的四分组连接终止序列。也就是说该函数不会引发连接终止操作,如果我们想发送 FIN 的话,可以使用 shutdown 函数。为什么这么做的原因是:当多个进程共享这个 fd 的时候,某个进程的 close 不会导致别的进程不能使用该 fd。
getsockname、getpeername 函数
这两个函数返回与某个套接字关联的本地协议地址,或者返回与某个套接字关联的外地协议地址。
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
返回值:成功返回 0,这两个函数装填由 localaddr 或 peeraddr 指针所指的套接字地址结构;失败返回 -1。
需要这两个函数的理由:
(1) 在没有调用 bind 的 TCP 客户上,connect 成功后,getsockname 用于返回由内核赋予该连接的本地 IP 和本地端口号。
(2) 在一个服务器通过调用 accept 函数可以知道对端 socket 详情,但当服务器开了某个进程另外执行某种操作时,那么该进程能获得客户身份的唯一途径便是使用 getpeername 函数。