应用程序是怎么把消息给操作系统的?socket就是操作系统提供给应用程序的编程接口、通信接口。socket也叫套接字。支持两种类型TCP和UDP。
套接字是特殊的文件描述符
一个套接字是一种特殊的文件描述符,特殊是因为一般的文件描述符只有一个缓冲区,但套接字有两个缓冲区,一个缓冲区是发送端,一个缓冲区是接收端。因为是文件,因此socket支持的操作其实就是读写IO和打开关闭。
套接字分类
TCP可靠传输,保证数据准确性,因此效率相对没那么高了。UDP保证时效,在规定时间内需要发送多少就发送多少报文,相对快。原始套接字只考虑IP或只考虑MAC地址。
流式套接字实现
大致流程首先服务器会创建初始化socket,与本地IP、端口号进行绑定,对端口进行监听,因为服务器和客户端是一对多的关系,因此需要队列来存放客户端连接。之后调用accept阻塞,如果拿到了客户端连接,就会再次创建一个socket和客户端进行连接,之后就可以和客户端进行数据收发。关闭连接时,关闭的是accept这里创建的socket,一开始创建的socket还在,会继续等待下一个客户端连接。
服务端调用socket()告诉通信方怎么传输,调用bind()告诉通信方往哪里传,调用listen()监听有没有客户端来连接进来,调用accept()从内核获取客户端连接,或者如果没有连接就阻塞等待客户端连接。
流程图
注意图中fd为套接字,服务端中最初创建的socket可以理解为监听套接字,即图中红色fd。acctpe在接收到客户端连接请求后,会创建一个新的socket,这个套接字可以称为已连接套接字也称为通信套接字,即图中绿色的fd。连接套接字可以有多个。
socket 相当于打开文件操作
指明网络层和传输层用什么协议,如果网络层使用IPv4就将domain设置为AF_INET,如果传输层设置为TCP就将type设置为SOCK_STREAM。
int socket(int domain, int type, int protocol);
-
domain
协议域决定了socket的协议类型,常见有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
-
type
常用的socket类型有,SOCK_STREAM、SOCK_DGRAM->UDP、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等
- SOCK_STREAM->TCP
流式套接字,面向连接有保障的传输数据。
- SOCK_DGRAM->UDP
无保障的面向消息的socket。
-
protocol
常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。一般设置为0,与type进行协议默认匹配。
bind函数 绑定地址
服务器的IP和端口提供服务,一般都是固定的,因此需要绑定这个操作。客户端一般都是随机生成的端口号+客户端IP,因此没有bind()这步操作。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
sockfd
socket描述,由上面socket()创建的
-
addr
不同的协议有不同的结构,常用的IPV4协议的结构中存放了端口、IP。
-
addrlen
地址长度
listen函数
listen监听客户端请求。因为服务端和客户端是一对多的关系,因此服务端需要有存客户端的区域——队列。socket会维护2个队列,未完成连接队列(syns queue)/半连接和已完成连接(accept queue)队列。listen函数这里涉及到的是未完成连接队列。backlog的值为已完成连接队列中数量之和。为什么不把未完成连接的队列放进去考虑呢?因为防止syn攻击,只发了SYN没有后续ACK的客户端。
int listen(int sockfd, int backlog);
-
sockfd
要监听的socket描述符。
-
backlog
可以排队的最大连接个数。
结合TCP连接中的三次握手流程,当客户端发送SYN第一次握手后,服务端会在该未完成连接队列中创建一个与SYN包对应的项。
connect函数
客户端通过调用connect与TCP服务器进行连接。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
sockfd
客户端的socket描述符。
-
addr
服务器socket地址。
-
addrlen
socket地址长度。
accept函数
accept接收客户端请求。提取出所监听套接字的等待连接队列中第一个连接请求,创建一个新的套接字,并返回指向该套接字的文件描述符。这里会涉及到已完成连接队列。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd 开始时创建的socket
3次握手完成之后,对应的在未完成队列中的连接就会放到该完成连接队列中。