二、socket基本函数-功能类
1.socket函数
#include <sys/socket.h> int socket (int family, int type, int protocol);
进行socket通信的第一步就是调用socket函数。需要为socket函数指明协议族(family)、类型(type)和协议(protocol)。
这里需要注意的是,type和protocol需要正确组合,有一些组合是无效的。这里举几个正确组合的例子,type=SOCK_STREAM一般和protocol=IPROTO_TCP进行组合,即常用的TCP流方式来传输。或者type=SOCK_DGRAM与protocol=IPROTO_UDP进行组合,即进行UDP方式传输(UDP是基于数据包的传输方式,不会维护流状态DGRAM即datagram)。
socket函数成功执行通常会返回一个非负的描述符,成为套接字描述符sockfd,我们后面要进行的操作的基于这个描述符。如果返回的是负数,-1,则说明创建描述符出了问题。
2.connect函数
#include <sys/socket.h> int connect (int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
第一个参数为socket函数返回的sockfd,第二个参数就是地址结构体了,第三个参数一般是地址结构体的sizeof。如果函数成功返回则返回0,否则会返回一个错误码。
值得注意的是,有三种错误情况:
1)没收到SYN的ACK。发出SYN后,6s内无回应的话,会重发一个SYN,并把超时时间变成24s。若等了了72s还没收到ACK,则该函数返回一个ETIMEDOUT。
2)收到RST。如果对端没有启动监听进程的话,则会返回一个RST过来。本机收到RST后会立即返回ECONNREFUSED。
3)路由不可达。收到路由不可达,内核将先按1)来处理,72s后返回EHOSTUNREACH或者ENETUNREACH。
3.bind函数
#include <sys/socket.h> int bind (int sockfd, struct sockaddr *myaddr, socklen_t addrlen);服务器方通常会调用bind函数来设置端口,有时候也会设置ip地址,比如本机有多个网卡(多个IP),只监听听某个IP的到达数据。TCP客户端一般不绑定端口和IP,这样内核会自动分配一个没用的端口,然后根据出口网卡来设置源IP地址。
常见的服务器bind函数用法
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port);然后将这个地址结构体传给bind函数,这就指定了监听端口。
4.listen函数
listen函数是一个仅由服务器进程调用的非阻塞函数,它会监听客户端发起的TCP连接。#include <sys/socket.h> int listen(int sockfd, int backlog);backlog是套接字排队队列的最大值。
backlog包含两个队列:
1.未完成连接队列,收到客户段发的syn包,,正等待完成3次握手,实际上就是自己发了syn的ack,但是客户端还没回ack。套接字会处于SYN-RCVD状态。
2.已完成连接队列,完成了完整的3次握手。
这里个队列长度只和不超过backlog。
5.accept函数
accept是服务端调用的函数,用于从已完成三次握手的队列中取出下一个已完成的握手的链接。当套接字为默认的阻塞套接字时,如果已完成队列为空,进程会进入休眠。
#include <sys/socket.h> int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);cliaddr和addrlen分别指明了结构体地址和结构长度。这是之前提到的典型的值-结果参数方式。
6.close函数
it is self-explanatory。关闭socket连接。
#include <sys/socket.h> int close (int sockfd);
这个函数会使得sockfd不能够再用于read或者write函数,但是已经在发送队列中的还是会继续发送,直到排队的包发完才会发FIN并进入TCP关闭过程。
如果一定要关闭socket连接则应该使用shutdown函数。
7.getsockname和getpeername函数
说实话,这两个函数不应该属于基本函数,但是随着书的编排,也做一下介绍。
#include <sys/socket.h> int getsockname (int sockfd, struct sockaddr *localaddr, socklen_t *addrlen); int getpeername (int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);getsockname函数用于获取本地的地址结构体。
1)在没有调用bind的TCP客户端,connect成功返回后一个sockfd后,可以调用getsockname来获得内核分配的IP地址和端口号。
2)同样适用于获取当bind的时候指定0端口(交给内核分配)时,内核分配的端口号和IP地址。
3)服务器上,如果使用通配地址调用bind函数,在accept返回后,可以用getsockname来获得内核分配的IP地址。(服务器一般指定端口)
getpeername函数个人认为主要用于多进程并发服务器(fork)。原因如下:
1)首先,fork之前accept能够获得对端地址结构体,accept之后,如果调用exec,由于内存被子进程覆盖了,地址结构体就会丢失。
2)但是sockfd是系统全局共享的,此时exec的时候将sockfd传进去(传的是accept返回的sockfd),就能够通过getpeername获得对端的地址结构体,从而进行下一步操作。