socket编程接口
Linux系统中常用的socket网络编程接口有socket()、bind()、listen()、accept()、connect()、send()、recv()、close(),其中connect()与send()为客户端专用接口,bind()、listen()、accept()及recv()为服务器端专用接口,socket()与close()则由服务器与客户端共用。
下面将对这些接口逐一进行讲解。
① socket()
socket()函数用于创建套接字,也可以说socket()函数用于打开网络通讯端口,该函数类似与文件操作中的open()函数,若调用成功,也返回一个文件描述符,之后应用程序可以采用socket编程接口中的读写函数在网络中收发数据。socket()函数存在于函数库sys/socket.h中,其声明如下:
int socket(int domain, int type, int protocol);
socket()函数中的第一个参数domain用于指定通信域,选择通信时的协议族,其常用设置为AF_INET和AF_UNIX,这些协议族都在头文件sys/socket.h中定义。其中AF_INET针对因特网,使用IPv4格式的IP地址,以此参数建立的socket可与远程的通信端连接并进行通信;AF_UNIX则针对本地进程,以此参数建立的socket可在本地系统进程间进行通信。
第二个参数type用于指定socket的连接类型,其常用取值分别为SOCK_STREAM、SOCK_DGRAM、SOCK_RAW。其中SOCK_STREAM表示套接字使用TCP协议,提供按顺序、可靠、双向、面向连接且基于比特流的通信;SOCK_DGRAM表示套接字使用UDP协议,提供定长、不可靠、无连接且基于数据报的通信;SOCK_RAW表示套接字使用ICMP协议,提供单一的网络访问,一般用于开发人员需自行设置数据报格式或参数时。
第三个参数protocol一般设置为0,表示使用默认协议。
socket()函数若调用失败会返回-1,并设置errno。
② bind()
bind()函数用于服务器端。服务器所监听的网络地址和端口号通常固定不变,客户端程序得知服务器程序的地址和端口号后,可主动向服务器请求连接,因此服务器需调用bind()函数绑定客户端中的进程,以便后续可监听到客户端的请求。
bind()函数的功能为:使服务器端的一个socket文件与网络中的一个进程进行绑定,因为文件描述符可标识socket文件,“主机名+端口号”可标识网络中的唯一进程,因此bind()函数实际上是将服务器端的socket文件与网络中的进程地址进程绑定。
bind()函数存在于函数库sys/socket.h中,其声明如下:
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
bind()函数中的参数sockfd指代socket文件的文件描述符,一般由socket()函数返回;参数addr指代需要与服务器进行通信进程的地址,其本质为struct sockaddr结构体类型的指针,struct sockaddr结构体的类型定义如下:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
该结构体中的成员sa_data[]便表示进程地址。
bind()函数中的第三个参数addrlen表示参数addr的长度,实质上addr参数可接受多种协议的结构体,而这些结构体的长度各不相同,因此需使用参数addrlen额外指定结构体长度。如亦可使用以下语句,定义一个struct sockaddr_in类型的结构体:
struct sockaddr_in servaddr; //结构体定义
bzero(&servaddr, sizeof(servaddr)); //结构体清零
servaddr.sin_family = AF_INET; //设置地址类型为AF_INET
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //设置网络地址为INADDR_ANY
servaddr.sin_port = htons(85); //设置端口号为85
bind()若调用成功,则返回0;否则返回-1,并设置errno。
③ listen()
listen()函数仍用于服务器端,从字面上看,其功能为使已绑定的socket监听对应客户端进程状态,但实际上,该函数用于设置服务器同时可建立的连接的数量。listen()函数存在于函数库sys/socket.h中,其声明如下:
int listen(int sockfd, int backlog);
listen()函数中的参数sockfd表示socket文件描述符;参数backlog用于设置请求队列的最大长度。典型的服务器可同时服务于多个客户端,当有客户端发起连接请求时,服务器调用的accept()函数将返回,并接受这个连接;若发起连接请求的客户端过多,服务器来不及处理,尚未建立连接的客户端就会处于等待连接状态。listen()函数中的参数backlog便用于限制建立的连接数量,若已有backlog个客户端连接到服务器,服务器就会忽略之后接收到的连接请求。
listen()函数若调用成功则返回0,表示监听成功;否则返回-1,并设置errno。
④ accept()
accept()函数在listen()函数之后使用,其功能为阻塞等待客户端的连接请求。accept()函数存在于函数库sys/socket.h中,其声明如下:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
当传输层使用TCP协议时,服务器与客户端在创建连接前,会先经过“三次握手”机制测试连接,“三次握手”完成后,服务器调用accept()函数处理连接请求,此时若还没有客户端的请求到达,便阻塞等待调用accept()函数的进程,直到接收到客户端发来的请求,且服务器中已创建的连接数未达到backlog,accept()函数才会返回,并传出客户端的地址。
accecp()函数中的参数sockfd表示socket文件描述符。参数addr是一个传出参数,表示客户端的地址,该参数也可设置为NULL,表示不关心客户端的地址。参数addrlen是一个传入传出参数,传入时为函数调用者提供的缓冲区addr的长度,传出时为客户端地址结构体的实际长度。
⑤ connect()
connect()函数用于客户端,该函数的功能为向服务器发起连接请求。connect()函数存在于函数库sys/socket.h中,其声明如下:
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
connect()函数的参数与bind()函数中参数的形式一致,区别在于bind()中的参数为客户端进程地址,而connect的参数为服务器端地址。
connect()函数调用成功则返回0,否则返回-1,并设置errno。
⑥ send()
send()函数用于向处于连接状态的套接字中发送数据,该函数存在于函数库sys/socket.h中,其声明如下:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
send()函数中的参数sockfd表示接收端的socket文件描述符;参数buf为指向要发送数据的缓冲区指针;参数len表示缓冲区buf中数据的长度;参数flags表示调用的执行方式(阻塞/非阻塞),当flags设置为0时,可使用之前学习的write()函数替代send()函数。
除以上两个函数外,Linux系统中还提供了sendto()函数和sendsg()函数,这两个函数不但能发送数据给已连接的套接字,还可向未连接的套接字发送数据。sendto()和sendmsg()函数都存在于函数库sys/socket.h中,它们的函数声明分别如下:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
sendto()函数中的前4个参数与send()函数的参数相同,之后的参数dest_addr和addrlen分别用于设置接收数据进程的地址和地址的长度;sendmsg()函数中的第二个参数msg为struct msghdr类型的结构体指针,该参数用于传入目标进程的地址、地址的长度等信息。
若sendto()函数和sendmsg()函数向已连接的套接字中发送信息,则忽略参数dest_addr、addrlen和msg结构体中用于传递地址的成员,此时若参数dest_addr和addrlen不为NULL可能会返回错误EISCONN或0。
send()函数、sendto()函数及sendmsg()函数若调用成功都返回0,否则返回-1,并设置errno。
需要注意的是,以上函数调用成功并不表示接收端套接字一定会接收到发送的数据。
⑦ recv()
recv()函数用于从已连接的套接字中接收信息,该函数存在于函数库sys/socket.h中,其声明如下:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
该函数的参数列表与send()函数的参数列表形式相同,代表含义也基本对应,只是其参数sockfd表示发送信息的socket文件描述符。
此外,read()函数、recvfrom()函数和recvmsg()函数也可用于接收信息,read()函数在文件管理中已有讲述,另两个函数的功能与sendto()、sendmsg()相对,此处不再详细讲解。
函数recv()、recvfrom()、recvmsg()若调用成功则返回读到的字节数,否则返回-1,并设置errno。
⑧ close()
close()函数用于释放系统分配给套接字的资源,该函数即文件操作中常用于关闭文件的函数,存在于函数库unistd.h中,其声明如下:
int close(int fd);
close()函数中的参数fd为文件描述符,当其用于scoket编程中时,需传入socket文件描述符。该函数调用成功则返回0,否则返回-1,并设置errno。