hello,各位51CTO的小伙伴大家好啊,不出意外的话我又来水文了!
今天我们聊的话题是Socket,请注意这里我并不是要向大家演示如何用某种编程语言来实现Socket编程,而是从网络的角度来探究Socket内部的秘密,以此帮助我们更好的掌握Socket!
概述
首先简单介绍一下什么Socket?Socket即套接字,就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。
贴近生活一点,大伙平时都上网吧!上网时肯定不是大脑直接接触网络吧!(虽然未来的某一天说不定真的是这样),而是通过某某应用来访问网络中的数据。目前在Internet中最广泛的网络应用编程接口就是Socket API。无论客户进程还是服务器进程,都需要创建Socket来与底层交互,一般的网络应用进程可以创建三种类型的Socket:
- 数据报类型套接字(SOCK_DGRAM):面向传输层UDP接口
- 流式套接字(SOCK_STREAM):面向传输层TCP接口
- 原始套接字(SOCK_RAW):面向网络层协议,如IP、ICMP等
Socket API
Socket为开发人员提供了C/S架构网络应用的相关接口能力,具体表现为一组Socket API函数。网络应用通过调用不同的Socket API函数,实现创建套接字、发送数据、接受数据、等功能。常用的Socket API函数如下:
- 创建套接字:int socket(int family,int type,int protocol),family为协议族,type为套接字类型SOCK_DGRAM/STREAM/RAW,protocol为协议IPPROTO_TCP/UDP。函数调用成功,返回非负整数;失败,返回-1。
- 关闭套接字:int close(int sockfd),一个描述符为sockfd的套接字,函数调用成功,返回0;失败,返回-1。
- 绑定套接字本地断点:int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);myaddr为结构sockaddr_in指针类型的本地端点地址(IP+port);addrlen为端点地址长度。函数调用成功,返回0;失败,返回-1。
- 监听套接字:int listen(int sockfd,int backlog);backlog为连接请求队列大小;函数调用成功,返回0;失败,返回-1。
- 建立连接:int connect(int sockfd,const struct sockaddr *servaddr,socklen_t addrlen);servaddr为服务端地址;函数调用成功,返回0;失败,返回-1。
- 接受新套接字:int accept(int sockfd,const struct sockaddr *cliaddr,socklen_t addrlen);函数调用成功,返回0;失败,返回-1。
- 发送数据:ssize_t send(int sockfd,const void *buff,size_t nbytes,int flags);buff指向待发送数据的缓存指针;nbytes为数据长度;flags为控制比特,通常取0;支持TCP/UDP;函数调用成功,返回发送的字节数;失败,返回-1。
- 发送数据:ssize_t sendto(int sockfd,const void *buff,size_t nbytes,int flags,const struct sockaddr *to,socklen_t addrlen);本函数用于UDP服务器端套接字或未调用connect()函数的UDP客户端套接字;函数调用成功,返回发送的字节数;失败,返回-1。
- 接受数据:ssize_t recv(int sockfd,void *buff,size_t nbytes,int flags);支持TCP/UDP;函数调用成功,返回接收到的字节数;失败,返回-1。
- 接受数据:ssize_t recvfrom(int sockfd,const void *buff,size_t nbytes,int flags,struct sockaddr *from,socklen_t addrlen);用于从UDP服务器端套接字或未调用connect()函数的UDP客户端套接字接受数据。函数调用成功,返回接收到的字节数;失败,返回-1。
- 设置套接字选项:int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t addrlent);level为选项级,如IPPROTO_IP为IPv4选项;optname为选项名,如IP_TTL为IP数据报存活时间选项;optval为存储选项值。函数调用成功,返回0;失败,返回-1。
- 读取套接字选项:int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t addrlent);optval用于存放选项读取值。函数调用成功,返回0;失败,返回-1。
交互图
当然看完以上的常用函数,可能大伙还是不太清楚其交换过程,索性我们就以交互图的形式来给大家展示一下。首先我们看下基于TCP客户与服务器的典型Socket API函数调用过程。整体交互过程大概是,服务器端调用socket()函数创建一个套接字;然后调用bind()函数绑定本地端点地址;再调用listen()函数将套接字ms置为监听模式;随后再调用accept()函数通过主套接字ms接收客户连接请求,并阻塞服务器进程,直到有客户连接请求过来,则返回连接套接字ss,若客户端有数据交互,则对应调用recv()函数接收,通过send()函数回复;客户端这边则是首先通过socket()函数创建一个本地套接字cs,然后通过connect()函数请求与服务器建立TCP连接,连接成功后即进入数据交互环节,即通过send()函数将客户端数据发送至服务器端,通过recv()函数用来接收服务器端的数据。交互完成则调用close()函数断开连接。相应的服务端也会断开本次的套接字释放相应的资源。
看完TCP的,我们再来看看UDP的交互图。由于UDP是无连接、不可靠的数据报传输服务,所以它的流程图比TCP要简单很多。首先是UDP服务器端通过socket()函数创建套接字ums,随后调用bind()函数进行本地端点绑定,这两步完成后即可等待客户端数据到来;UDP客户端通过socket()函数创建本地套接字,若要和UDP服务器交互,则只需调用sendto()函数将数据发送到服务器,需要接受服务器端的返回数据的话则只需调用recvfrom()函数即可。服务端通过recvfrom()函数来接收UDP客户端发来的数据,再通过sendto()函数返回结果数据。双方交互完毕即可调用close()函数关闭双方的套接字。
好啦,至此就是本次我想分享的关于Socket的全部内容啦!谢谢各位!