套接字Socket
- Socket编程是基于TCP和UDP协议的。在通信之前,客户端和服务端都要建立一个Socket。
- 在建立Socket时,能够设置的参数是网络层和传输层范围内的。在网络层,Socket函数需要指定是IPv4还是IPv6,还要指定是TCP还是UDP。
- 基于TCP协议的Socket程序函数调用过程:
A.当服务端要监听一个端口之前,一般是先调用bind函数,给这个Socket赋予一个IP地址和端口。(内核要通过TCP头里的端口,来找到相应的应用程序,把包给它。一台机器会有多个网卡,也就会有多个IP地址,网络包需要确定要发送的网卡。)
B.当服务端有了IP地址和端口号,就可以调用listen函数进行监听,进入listen状态,此时客户端就可以发起连接了。(在内核中,为每个Socket维护两个队列:一个是已经建立了连接的队列,此时三次握手已经完毕;一个是还没有完全建立连接的队列,此时三次握手还没完成。)
C.接下来,服务端调用accept函数,对已经完成的连接进行处理。如果连接没有完成,服务端就要等着。
D.在服务端等待时,客户端通过connect函数发起连接。先在参数中指明要连接的IP地址和端口号,然后开始发起三次握手。内核会给客户端分配一个临时的端口,一旦握手成功,服务端的accept就会返回另一个Socket。(监听的Socket和真正用来传数据的Socket不同,一个叫作监听Socket,一个叫作已连接Socket。)
E.连接建立成功之后,双方开始通过read和write函数来读写数据。
- Socket在Linux中是以文件的形式存在的,读写是通过文件描述符进行的。在内核中,每一个进程都有一个数据结构task_struct,里面指向一个文件描述符数组,来列出这个进程打开的文件的文件描述符。文件描述符是一个整数,是这个数组的下标,这个数组中的内容是一个指针,指向内核中打开的文件的列表。既然是一个文件,就会有一个inode,只不过Socket对应的inode不像真正的文件系统一样,保存在硬盘上,而是保存在内存中,在这个inode中,指向了Socket在内核中的Socket结构,在这个结构里,主要是两个队列,一个是发送队列,一个是接收队列,这两个队列里面保存了一些缓存。
- 基于UDP协议的Socket程序函数调用过程:
UDP是没有连接的,所以不需要三次握手,也就不需要调用listen和connect,但是UDP的交互仍然需要IP和端口号,因而也需要bind函数。UDP是没有维护连接状态的,因而不需要每对连接建立一组Socket,而是只要有一个Socket,就能够和多个客户端通信,也正是因为没有连接状态,每次通信时,都可以传入IP地址和端口。
- 能够支撑大量连接的高并发的服务端需要多进程、多线程、epoll机制等。