第4章 基本TCP套接字编程

目录

socket 函数

connect 函数

bind 函数

listen函数

accept 函数

fork、exec 函数

close 函数

getsockname、getpeername 函数


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 函数。

猜你喜欢

转载自blog.csdn.net/lc250123/article/details/81223144