我从不生产知识,我只是知识的搬运工 。
目录
1 概述
一个基本TCP客户、服务器程序的套接字函数调用流程如下,后面会详细介绍各个函数;
2 socket
socket函数成功时返回一个小的非负整数值,称为套接字描述符;
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
/*返回:若成功则返回非负描述符,若出错则返回-1*/
第一个参数domain指明协议族(或称为协议域);
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK Appletalk ddp(7)
AF_PACKET Low level packet interface packet(7)
第二个参数type指明套接字类型;
SOCK_STREAM /*字节流套接字*/
SOCK_DGRAM /*数据报套接字*/
SOCK_SEQPACKET /*有序分组套接字*/
SOCK_RAW /*原始套接字*/
第三个参数protocol指明协议类型;
IPPROTO_TCP /*TCP传输协议*/
IPPROTO_UDP /*UDP传输协议*/
IPPROTO_SCTP /*SCTP传输协议*/
需要注意的是,并非所有domain和type的组合都是有效的,以下列出有效的组合:
3 connect
TCP客户端通过调用connect函数来和TCP服务器建立连接;
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
第一个参数是由socket函数返回的套接字描述符,第二个、第三个参数分别是指向套接字地址结构的指针和该结构的大小;其中套接字结构必须含有服务器的IP地址和端口号;
客户端调用connect函数前不必调用bind函数,内核会确定源IP地址,并选择一个临时端口作为源端口;
在每次connect函数调用失败时,都必须要close当前的套接字描述符并重新调用socket函数;
4 bind
bind函数将一个本地协议地址绑定一个套接字;
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*返回:若成功则返回0,若出错则返回-1*/
第一个参数是由socket函数返回的套接字描述符,第二个、第三个参数分别是指向套接字地址结构的指针和该结构的大小;
注意的是,服务器调用bind函数时,可以不指定地址和端口,而让内核自动选择地址和端口;如果指定端口号为0,则内核就在bind函数被调用时选择一个临时端口;如果指定一个通配地址,通配地址由常值INADDR_ANY来指定,一般值为0,则告知内核去选择IP地址;
5 listen
listen函数仅由服务器调用,函数做两件事情:
(1)、当socket函数创建一个套接字时,它被假定为一个主动套接字(客户套接字),listen函数把一个未连接的套接字转换为被动套接字,指示内核应该接受指向该套接字的连接请求;
(2)、本函数的第二个参数规定了内核该为相应套接字排队的最大连接个数;
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
/*返回: 若成功返回0,若失败返回-1*/
本函数通常应该在调用socket、bind函数之后,在accept函数之前被调用;
内核为每个套接字维护两个队列,未完成连接队列和已完成连接队列;listen函数的第二个参数backlog曾被规定为这两个队列总和的最大值,但是它应该指定某个给定套接字上内核为之排队的最大已完成连接数;通常值会设为5;
6 accept
accept函数由TCP服务器调用,用于从已完成连接队列列头返回下一个已完成连接;如果已完成连接队列为空,则进程被投入睡眠(默认套接字为阻塞方式);
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/*返回:若成功返回非负描述符,若失败返回-1*/
第二个、第三个参数用来返回已连接的对端进程(客户端)的协议地址及长度;其中addrlen是值-结果参数,在函数调用前,指定套接字地址结构大小,在函数调用后,返回内核存放该套接字地址结构的确切字节数;
第一个参数sockfd是监听套接字描述符(由socket创建,随后用作bind和listen的第一个参数),而函数成功时的返回值为已连接套接字描述符;
两个套接字描述符的区别是,监听套接字描述符在服务器生命周期中一直存在,而已连接套接字描述符在服务器完成给定客户服务时将关闭;
如果对返回的客户协议地址不感兴趣,可以把第二个、第三个参数都设置为空指针;
7 close
close函数用来关闭套接字,并终止TCP连接;
#include <unistd.h>
int close(int fd);
/*返回:若成功返回0,若失败返回-1*/
8 getscokname和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,若失败返回-1*/
两个函数的最后一个参数addrlen都是值-结果参数,在调用前需要指定sockaddr大小,调用后会返回实际大小;