Socket编程流程梳理

Socket编程流程

服务端:socket—>bind—>listen—>accept—>send/recv—>closesocket

客户端:socket—>bind(可选)—>connect—>send/recv---->closesocket

创建Socket

socket是通信端点的抽象,使用socket描述符来标识,类似文件描述符,通过调用socket函数创建。

#include <sys/socket.h>
int socket (int family, int type, int protocol);
返回值:成功,返回socket描述符,失败,返回-1

family用于指明协议族,取值范围如下所示:

取值含义
AF_INETIPv4协议
AF_INET6IPv6协议
AF_UNIXUNIX域协议,可用于进程间通讯
AF_UPSPEC未指定域
AF_ROUTE路由套接字,内核路由表接口
AF_KEY密钥套接字,内核密钥表接口

type用于确定套接字的类型,取值范围如下所示:

取值含义
SOCK_DGRAM数据报套接字,提供固定长度、无连接、不可靠的报文传递,可用于UDP
SOCK_RAW原始套接字,可用于IP协议编程
SOCK_SEQPACKET有序分组套接字,固定长度、有序的、可靠的、面向连接的报文传递,可用于SCTP
SOCK_STREAM字节流套接字,有序的、可靠的、双向的、面向连接的字节流,可用于TCP

protocol用于确定协议类型,取值范围如下所示:

取值含义
0选择默认协议,通常使用
IPPROTO_IPipv4
IPPROTO_IPV6ipv6
IPPROTO_ICMPICMP
IPPROTO_RAW原始IP数据包
IPPROTO_TCPTCP
IPPROTO_UDPUDP

SOCK_STREAM和SOCK_SEQPACKET区别:前者基于流,无法区分报文的界限,可能需要多次函数调用才能获取一个完整数据报文,后者单次调用就能获得完整报文。

SOCK_RAW作用:提供数据报接口,用于直接访问IP层,应用程序需要自己构造协议头部。

关闭套接字

调用close函数关闭套接字,其本质是将套接字的引用计数减1,当引用为0,将套接字标记成关闭状态,不可调用read或write函数,发送队列上的等待的数据将发往对端,随后进入TCP四次挥手流程。

#include <unistd.h>
int close(int sockfd)
返回值:成功,返回0,出错,返回-1

使用close时,只有最后一个进程close,才会释放socket。shutdown可以立即禁用socket,并且可以指定禁用方式。

#include <sys/socket.h>
int shutdown(int sockfd, int how);
返回值:成功,返回0,失败,返回-1

how表示以什么方式禁用,取值范围:

  • SHUT_RD:关闭读端,无法读数据
  • SHUT_WR:关闭写端,无法发送数据
  • SHUT_RDWR:不能读也不能发送数据

套接字绑定地址

服务端套接字需要先调用bind函数绑定地址和端口后,才能调用listen函数,将套接字转换为监听套接字,进入监听状态。客户端调用connect之前可选择性调用bind,如果不调用,由系统决定使用哪个源IP和port。bind函数的定义如下所示:

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
- 返回值:成功,返回0,失败,返回-1
- sockfd:本地有效socket描述符
- addr:通用socket地址指针
- len:socket地址长度

在给socket绑定sockaddr时,可选择绑定IP、端口中的0个或多个,产生的结果如下所示:

IP地址端口结果
INADDR_ANY0内核决定IP和端口
INADDR_ANY非0内核决定IP,进程决定端口
特定IP地址0进程决定IP,内核决定端口
特定IP地址非0进程决定IP和端口

bind函数存在以下限制:地址中的端口必须大于1024,一个sockfd只能绑定一个地址

socket和地址绑定成功之后,可以调用getsockname获取socket绑定的地址信息,如果和服务端建立了连接,可以通过getpeername获取对端的地址信息。

#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t * restrict alenp);
- 返回值:成功,0,失败,-1
- sockfd:socket文件描述符
- addr:通用套接字地址
- alenp:通用套接字地址长度
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);
- 返回值:成功,0,失败,-1
- sockfd:socket文件描述符
- addr:通用套接字地址
- alenp:通用套接字地址长度

服务端进入监听

仅有服务端调用,服务端调用listen函数进入监听状态,此时客户端可以调用connect函数与服务端建立TCP链接。socke创建的套接字默认是主动套接字,即客户端套接字,用于调用connect发起连接,listen将未连接的套接字,转换成被动套接字,指示内核应接收指向该套接字的连接请求。

内核为被动套接字维护两个队列:未完成连接队列和已完成连接队列。未完成连接队列维护处于半连接状态的连接,服务端收到一个SYN包时,在未完成连接队列里创建一项,当该连接状态转为Establish时,移到已完成连接队列尾部。accept函数从已完成连接队列头部返回一个连接。

#include <sys/socket.h>
int listen(int sockfd, int backlog);
- 返回值:成功,0,失败,-1
- sockfd:服务端socket
- backlog:最大的等待连接队列数目
  1. backlog不会超过SOMAXCONN,一旦队列满了,就会拒绝多余的请求
  2. 调用了listen之后,才能调用accept获得连接并建立连接
  3. listen不会阻塞,listen之后,客户端就可以通过connect建立三次握手,accept只是从三次握手成功的队列中出一个
  4. linten的套接字转换成监听套接字,内核才会接收连接该套接字的外部连接。accept返回套接字称为已连接套接字

客户端发起连接请求

客户端调用connect函数,向服务端发起请求。

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
- 返回值:成功,0,失败,-1
- sockfd:本地的socket描述符
- addr:对侧的地址,如果本侧socket未绑定地址,则绑定默认地址
- len:addr长度

重连机制:如果连接失败,有的系统支持使用同一个socket描述符重新调用connect,有的操作系统则要求重新创建socket,因此为了移植性,建议使用后者

阻塞connect:如果套接字描述符处于非阻塞模式,在连接不能马上建立时,connect返回-1,并将errno设置为EINPROGRESS,调用poll、select或epoll判断描述符何时可写,如果可写,则连接完成

UDP调用connect:UDP等无连接的传输,也可以调用connect,将报文的目的地址设置为connect指定的地址

获得连接

服务端调用accept,从已完成队列中,返回下一个Establish状态的连接。

#include <sys/socket.h>int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);- 返回值:成功,返回已连接套接字,失败,返回-1- sockfd:监听套接字- addr:客户端的地址存放的缓存,不关注的话可以设置为NULL- len:accept会更新len,指向实际客户端地址结构的长度,不关注的话可以设置为NULL

返回值是一个服务端的套接字描述符,和原始的sockfd有相同的地址族、协议和type,服务端使用该套接字与客户端通讯,而源sockfd继续保持监听状态用于接收新的连接,这也是一个服务端可以被多个客户端连接的原因。

如果sockfd处于阻塞模式,如果已完成队列为空,那么accept阻塞;如果sockfd处于非阻塞模式,返回-1,errno设置为EAGAIN或EWOULDBLOCK

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值