在进行套接字编程之前必须熟悉其地址结构,有关套接字的地址结构可参考文章《套接字编程简介》。基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述符。客户端随后调用connect 函数,服务器端则调用 bind、listen 和accept 函数。套接字通常使用标准的close 函数关闭,但是也可以使用 shutdown 函数关闭套接字。下面针对套接字编程实现过程中所调用的函数进程分析。以下是基于 TCP 套接字编程的流程图:
套接字是通信端点的抽象,实现端对端之间的通信。与应用程序要使用文件描述符访问文件一样,访问套接字需要套接字描述符。任何套接字编程都必须调用socket 函数获得套接字描述符,这样才能对套接字进行操作。以下是该函数的描述:
/* 套接字 */ /* * 函数功能:创建套接字描述符; * 返回值:若成功则返回套接字非负描述符,若出错返回-1; * 函数原型: */ #include <sys/socket.h> int socket(int family, int type, int protocol); /* * 说明: * socket类似与open对普通文件操作一样,都是返回描述符,后续的操作都是基于该描述符; * family 表示套接字的通信域,不同的取值决定了socket的地址类型,其一般取值如下: * (1)AF_INET IPv4因特网域 * (2)AF_INET6 IPv6因特网域 * (3)AF_UNIX Unix域 * (4)AF_ROUTE 路由套接字 * (5)AF_KEY 密钥套接字 * (6)AF_UNSPEC 未指定 * * type确定socket的类型,常用类型如下: * (1)SOCK_STREAM 有序、可靠、双向的面向连接字节流套接字 * (2)SOCK_DGRAM 长度固定的、无连接的不可靠数据报套接字 * (3)SOCK_RAW 原始套接字 * (4)SOCK_SEQPACKET 长度固定、有序、可靠的面向连接的有序分组套接字 * * protocol指定协议,常用取值如下: * (1)0 选择type类型对应的默认协议 * (2)IPPROTO_TCP TCP传输协议 * (3)IPPROTO_UDP UDP传输协议 * (4)IPPROTO_SCTP SCTP传输协议 * (5)IPPROTO_TIPC TIPC传输协议 * */
在处理面向连接的网络服务时,例如 TCP ,交换数据之前必须在请求的进程套接字和提供服务的进程套接字之间建立连接。TCP 客户端可以调用函数connect 来建立与 TCP 服务器端的一个连接。该函数的描述如下:
/* * 函数功能:建立连接,即客户端使用该函数来建立与服务器的连接; * 返回值:若成功则返回0,出错则返回-1; * 函数原型: */ #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); /* * 说明: * sockfd是系统调用的套接字描述符,即由socket函数返回的套接字描述符; * servaddr是目的套接字的地址,该套接字地址结构必须包含目的IP地址和目的端口号,即想与之通信的服务器地址; * addrlen是目的套接字地址的大小; * * 如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址,即内核会确定源IP地址,并选择一个临时端口号作为源端口号; */TCP 客户端在调用函数 connect 前不必非得调用 bind 函数,因为内核会确定源 IP 地址,并选择一个临时端口作为源端口号。若 TCP 套接字调用connect 函数将建立 TCP 连接(执行三次握手),而且仅在连接建立成功或出错时才返回,其中出错返回可能有以下几种情况:
调用函数
socket 创建套接字描述符时,该套接字描述符是存储在它的协议族空间中,没有具体的地址,要使它与一个地址相关联,可以调用函数
bind 使其与地址绑定。客户端的套接字关联的地址一般可由系统默认分配,因此不需要指定具体的地址。若要为服务器端套接字绑定地址,可以通过调用函数
bind 将套接字绑定到一个地址。下面是该函数的描述:
/* 套接字的基本操作 */ /* * 函数功能:将协议地址绑定到一个套接字;其中协议地址包含IP地址和端口号; * 返回值:若成功则返回0,若出错则返回-1; * 函数原型: */ #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); /* * 说明: * sockfd 为套接字描述符; * addr是一个指向特定协议地址结构的指针; * addrlen是地址结构的长度; */
在地址使用方面有下面一些限制:
在编写服务器程序时需要使用监听函数 listen 。服务器进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。listen 函数描述如下:
/* * 函数功能:接收连接请求; * 函数原型: */ #include <sys/socket.h> int listen(int sockfd, int backlog);//若成功则返回0,若出错则返回-1; /* * sockfd是套接字描述符; * backlog是该进程所要入队请求的最大请求数量; */
listen 函数一般应该在调用socket 和bind 这两个函数之后,并在调用 accept 函数之前调用。 内核为任何一个给定监听套接字维护两个队列:
accept 函数由 TCP 服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示 TCP 三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。该函数描述如下:
/* 函数功能:从已完成连接队列队头返回下一个已完成连接;若已完成连接队列为空,则进程进入睡眠; * 函数原型: */ int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);//返回值:若成功返回套接字描述符,出错返回-1; /* * 说明: * 参数 cliaddr 和 addrlen 用来返回已连接的对端(客户端)的协议地址; * * 该函数返回套接字描述符,该描述符连接到调用connect函数的客户端; * 这个新的套接字描述符和原始的套接字描述符sockfd具有相同的套接字类型和地址族,而传给accept函数的套接字描述符sockfd没有关联到这个链接, * 而是继续保持可用状态并接受其他连接请求; * 若不关心客户端协议地址,可将cliaddr和addrlen参数设置为NULL,否则,在调用accept之前,应将参数cliaddr设为足够大的缓冲区来存放地址, * 并且将addrlen设为指向代表这个缓冲区大小的整数指针; * accept函数返回时,会在缓冲区填充客户端的地址并更新addrlen所指向的整数为该地址的实际大小; * * 若没有连接请求等待处理,accept会阻塞直到一个请求到来; */
/* 函数功能:创建子进程; * 返回值: * (1)在子进程中,返回0; * (2)在父进程中,返回新创建子进程的进程ID; * (3)若出错,则范回-1; * 函数原型: */ #include <unistd.h> pid_t fork(void); /* 说明: * 该函数调用一次若成功则返回两个值: * 在调用进程(即父进程)中,返回新创建进程(即子进程)的进程ID; * 在子进程返回值是0; * 因此,可以根据返回值判断进程是子进程还是父进程; */ /* exec 序列函数 */ /* * 函数功能:把当前进程替换为一个新的进程,新进程与原进程ID相同; * 返回值:若出错则返回-1,若成功则不返回; * 函数原型: */ #include <unistd.h> int execl(const char *pathname, const char *arg, ...); int execv(const char *pathnam, char *const argv[]); int execle(const char *pathname, const char *arg, ... , char *const envp[]); int execve(const char *pathnam, char *const argv[], char *const envp[]); int execlp(const char *filename, const char *arg, ...); int execvp(const char *filename, char *const argv[]); /* 6 个函数的区别如下: * (1)待执行的程序文件是 文件名 还是由 路径名 指定; * (2)新程序的参数是 一一列出 还是由一个 指针数组 来引用; * (3)把调用进程的环境传递给新程序 还是 给新程序指定新的环境; */
表 1 exec 序列函数的总结 前4位 统一为:exec 第5位 l:参数传递为逐个列举方式 execl、execle、execlp v:参数传递为构造指针数组方式 execv、execve、execvp 第6位 e:可传递新进程环境变量 execle、execve p:可执行文件查找方式为文件名 execlp、execvp
当要求一个服务器同时为多个客户服务时,需要并发服务器。TCP 并发服务器,它们为每个待处理的客户端连接调用 fork 函数派生一个子进程。当一个连接建立时,accept 返回,服务器接着调用 fork 函数,然后由子进程服务客户端,父进程则等待另一个连接,此时,父进程必须关闭已连接套接字。
当要关闭套接字时,可使用 close 和 shutdown 函数,其描述如下:
/* 函数功能:关闭套接字,若是在 TCP 协议中,并终止 TCP 连接; * 返回值:若成功则返回0,若出错则返回-1; * 函数原型: */ #include <unistd.h> int close(int sockfd); /* * 函数功能:关闭套接字上的输入或输出; * 返回值:若成功则返回0,若出错返回-1; * 函数原型: */ #include <sys/socket.h> int shutdown(int sockfd, int how); /* * 说明: * sockfd表示待操作的套接字描述符; * how表示具体操作,取值如下: * (1)SHUT_RD 关闭读端,即不能接收数据 * (2)SHUT_WR 关闭写端,即不能发送数据 * (3)SHUT_RDWR 关闭读、写端,即不能发送和接收数据 * */
为了获取已绑定到套接字的地址,我们可以调用函数
getsockname 来实现:
/* * 函数功能:获取已绑定到一个套接字的地址; * 返回值:若成功则返回0,若出错则返回-1; * 函数原型: */ #include <sys/socket.h> int getsockname(int sockfd, struct sockaddr *addr, socklen_t *alenp); /* * 说明: * 调用该函数之前,设置alenp为一个指向整数的指针,该整数指定缓冲区sockaddr的大小; * 返回时,该整数会被设置成返回地址的大小,如果该地址和提供的缓冲区长度不匹配,则将其截断而不报错; */ /* * 函数功能:获取套接字对方连接的地址; * 返回值:若成功则返回0,若出错则返回-1; * 函数原型: */ #include <sys/socket.h> int getpeername(int sockfd, struct sockaddr *addr, socklen_t *alenp); /* * 说明: * 该函数除了返回对方的地址之外,其他功能和getsockname一样; */
《Unix 网络编程》
原文地址:http://blog.csdn.net/chenhanzhun/article/details/41847945