Unix网络编程卷1第4章 - 基本TCP套接口编程

来源:互联网 发布:网络架构师英语怎么说 编辑:程序博客网 时间:2024/05/01 10:07

本章主要讲几个常用的TCP套接口编程函数。

1. socket 函数

#include <sys/socket.h>
int socket(int family, int type, int protocol)
功能: 创建用于通信的套接字,并指定期望的通信协议类型。
返回: 成功 -> 非负描述字,出错 -> -1
family: 通信协议类型,AF_INET(IPv4协议),AF_INET6(IPv6协议),AF_LOCAL(Unix域协议), AF_ROUTE(路由套接口),AF_KEY(密钥套接口)。
type: 套接口的类型,SOCK_STREAM(字节流套接字), SOCK_DGRAM(数据报套接字),SOCK_RAW(原始套接字)。

protocol: 一般设为0,除非用在原始套接口上。IPPROTO_TCP:tcp传输协议 IPPROTO_UDP:UDP传输协议
IPPROTO_SCTP:stcp传输协议
socket函数成功时返回一个很小的非负整数值,称为套接口描述字socket descriptor,简称套接字sockfd。
2.connect 函数

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)
功能: 创建一个与TCP服务器的连接。
返回: 成功 -> 0, 出错 -> -1
sockfd: 由socket函数返回的套接口描述字。
servaddr: 指向套接口地址结构的指针,含有服务器的IP地址和端口号。
addrlen: 套接口地址机构的长度。
客户在调用函数connet前不必非得调用bind函数,因为如果需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口。如果是tcp套接口,调用connect函数将激发tcp的三路握手过程,而且仅在连接建立成功或出错时才返回。

其中出错情况包括:
1. TCP客户没有收到SYN分节的响应,返回ETIMEDOUT。提示信息:connect error: Connection timed out.
2. 服务器主机在我们指定的端口上没有进程在等待与之连接,称为硬错(hard error)。客户会接口到SYN的响应RST,返回ECONNREFUSED。提示信息:connect error: Connection refused。
3. 客户发出的SYN在中间的路由器上引发了一个目的地不可到达的ICMP错误,称为软错(soft error),返回EHOSTUNREACH或ENETUNREACH。提示信息:connect error: No route to host。
客户调用函数connect之前,不必非得调用函数bind,因为有必要的话,内核会选择源IP地址和一个临时的端口。
如果函数connect调用失败,则套接口不可再用,必须关闭,重新调用socket,然后才能调用函数connect。

3.bind 函数

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen)
功能: 给套接口分配一个本地协议地址。
返回: 成功 -> 0, 出错 -> -1。
sockfd: 目标套接口描述字。
myaddr: 指向特定于协议的地址机构指针, 该结构中有IP地址和端口信息,也可以两者都不指定。
addrlen: myaddr指向的地址结构的长度。
如果TCP服务器不把IP地址捆绑到接口上,内核就把客户所发SYN所在分组的目的IP地址作为服务器的源IP地址。
若指定端口号为0,调用函数bind时,内核选择一个临时端口。
若指定通配IP地址,则直到套接口已连接(TCP)或数据报已在套接口上发出(UDP),内核才选择一个本地IP地址。
对于IPv4,通配符地址由常值INADDR_ANY指定:
#define INADDR_ANY ((unsigned long int) 0x00000000)       [netinet/in.h]
对于IPv6,通配符地址由一下方式指定:
struct sockaddr_in6 serv;
serv.sin6_addr = in6addr_any;          [netinet/in.h]
系统分配变量in6addr_any并将其初始化为常量 IN6ADDR_ANY_INIT。
#define IN6ADDR_ANY_INIT {{{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }}}   [netinet/in.h]
函数bind返回的一个常见错误是EADDRINUSE(地址已使用)。

4.listen 函数

#include <sys/socket.h>
int listen(int sockfd, int backlog)
功能: 将未连接的主动套接口转换成被动套接口。
返回: 成功 -> 0, 出错 -> -1
sockfd: 目标套接口描述字。
backlog: 规定内核为此套接口排队的最大连接个数。backlog为已完成连接队列(三次握手完成,处于ESTABLISHED状态)和 未完成连接队列(三次握手中,处于SYN_RCVD状态)之和。
当进程调用函数accept时,已完成连接队列中的队头条目返回给进程,但当队列为空时,进程将睡眠,知道有条目放入已完成队列才唤醒它。

5.accept 函数

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)
功能:从已完成连接队列头返回下一个已完成连接。若已完成连接队列为空,则进程睡眠。
返回:成功 -> 已连接套接口描述字connected socket,代表与客户的TCP连接, 出错 -> -1.
sockfd: 监听套接口描述字listening socket。
cliaddr: 连接对方进程(客户)的协议地址。
addrlen: 值-结果参数,调用前,此整数值置为由cliaddr所指的套接口地址结构的长度,返回时,此整数值即为由内核存在此套接口地址结构内的准确字节数。
给定的服务器常常是只生成一个监听套接口且一直存在,知道该服务器关闭。内核为每个被接受的客户连接创建一个已连接套接(完成TCP三次握手)口,当服务器完成某客户的服务时,关闭已连接套接口。
如果对返回的客户协议地址不感兴趣,可将指针cliaddr, addrlen均置为NULL.

6.fork 和 exec 函数

#include <unistd.h>
pid_t fork(void)
功能: Unix中派生新进程的唯一方法。
返回: 成功 -> 在子进程中为0,在父进程中为子进程的ID, 出错 -> -1。
子进程可以调用函数getppid获取父进程的ID;然而父进程想跟踪所有子进程的ID,他必须记住fork的返回值。
父进程中调用fork之前打开的所有描述字在函数fork返回之后都是共享的。并发服务器: 父进程调用accept,然后调用fork,这样,已连接套接口就在父进程与子进程间共享,一般地,接下来就是子进程读,写已连接套接口,而父进程则关闭已连接套接口。

#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 *argv0, ... /* (char *) 0 */);
int execvp(const char *filename, char *const argv[]);
所有六个函数: 成功 -> 无返回, 出错 -> -1。
以文件形式存储在磁盘上的可执行程序被Unix执行的唯一方法是:由一个现有进程调用六个exec函数中的一个。exec用新程序代替当前进程映像,且此新程序一般都是从main函数开始执行。一般将调用exec的进程称为调用进程calling process,而将新执行的程序称为新程序new program。
六个exec函数间的区别:
1. 被执行的程序是由文件名(filename)还是路径名指定(pathname);
2. 新程序的参数是一一列出还是由一个指针数组来索引;
3. 调用进程的环境传递给新进程还是指定新环境。
注意的地方:
1. 必须使用空指针来表示可变数量参数的终止或argv数组的结束(因为数组没有指定大小);
2. filename,将根据现行的PATH环境变量转换为pathname。但若filename参数中含有斜扛'/',将不再使用PATH变量。
3. envp必须以一个空指针结束。
一般来讲,调用exec之前在进程中打开的描述字在跨exec过程中保持打开。[描述字也可用函数fcntl设置FD_CLOEXEC描述字标志来关闭]

7.并发服务器

文件或套接口的访问记数: 该访问计数在文件表项中维护,它表示当前指向该文件或套接口的打开的描述字的个数。描述字只在访问计数值达到 0 时才正真关闭。
因此,并发服务器调用fork后,父进程关闭已连接套接口,子进程关闭监听套接口。

8.close 函数

#include <sys/socket.h>
int close(int sockfd)

close一个tcp套接口的缺省行为是把套接口标记成已关闭,然后立即返回到调用进程。该套接口描述字不能再由调用进程使用,也就是说它不能再作为read或write的第一个参数。然而tcp将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的tcp连接终止序列。
  如果父进程对每个由accept返回的已连接套接口都不调用close,那么并发服务器中将会发生什么。首先,父进程最终将耗尽可用描述字,因为任何进程在任何时刻可拥有的打开着的描述字数通常是有限制的。不过更重要的是,没有一个客户连接会被终止。当子进程关闭已连接套接口时,它的引用计数值将由2递减为1且保持为1,因为父进程永不关闭任何已连接套接口。这将妨碍tcp连接终止序列的发生,导致连接一直打开着。

9.getsockname 和 getpeername 函数

#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen *addrlen);
功能: 返回与套接口关联的本地协议地址(getsockname),或返回与套接口关联的远程协议地址(getpeername)。
返回: OK -> 0,  出错 -> -1。

原创粉丝点击