套接字 Socket 基础理论理解
一、何为套接字
- 套接字是指通过软件来实现作为插口或者插槽,一端插在客户端,一端插在服务端。
- Socket 编程进行的时端到端的通信,往往意识不到中间经过多少局域网,多少路由器,因此,能设置的参数也只能时端到端的协议之上网络层和传输层。
- 在网络层:Socket 函数需要指定是 IPV4 还是 IPV6,分别对应设置为 AF_INET 和 AF_INET6。
- 在传输层:需要指定是 TCP 还是 UDP 。TCP 协议是基于数据流的,所以设置为 SOCK_STERAM,而 UDP 是基于数据报的,所以设置为 SOCK_DGRAM。
二、基于 TCP 协议的 Socket 程序函数调用过程
-
TCP 的服务端要监听一个端口,一般是先调用 bind 函数,给这个 Socket 赋予一个 IP 地址和端口。
-
当服务端有了 IP 地址和端口号,就可以调用 listen 函数进行监听。客户端就可以发起连接了。
-
在内核中,每个Socket 维护两个队列。
- 已经建立连接队列,完成三次握手完毕,处于established 状态。
- 未完成建立连接的队列,没有三次握手,处于 syn_rcvd 的状态。
-
服务端调用 accept 函数,拿出一个已经完成的连接进行处理,如果还没有完成就要等着。
-
在服务端等待的时候,客户端可以通过 connect 函数发起连接。先在参数中指明要连接的 IP 地址和端口号,然后发起三次握手。内核会给客户端分配一个临时的端口。一旦握手成功,服务端的 accep 就会返回另一个 Socket 。
-
监听的 Socket 和真正用来传输数据的 Socket 是不同的两个,一个是叫做监听Socket,一个叫做已连接 Socket。
-
建立连接成功后,双方开始通过 read 和 write 函数来读写数据。
-
具体连接如下图所示:
二、基于 UDP 协议的 Socket 程序函数调用过程
- 因为 UDP 没有连接,所以不需要三次握手,也不需要listen 和connect。同时 UDP因为没有维护连接状态,所以,也不需要每对连接建立一组 Socket ,只需要一个 Socket 就能够和多个客户端通信。
- UDP 也需要 IP 地址和端口号,因此也需要 bind 。每次通信时候,调用sendto 和recvfrom 就可以传入 IP地址和端口号。
- 具体连接如下图所示:
三、服务器如何接更多的项目?
方式一:将项目外包给其他公司(多进程方式)
- 相当于你是一个代理,一直监听请求,一旦建立一个连接,就会有一个已连接的Socket ,这时候就可以创建一个子进程,然后将基于连接 Socket 的交互交给这个新的子进程来做。
- 在Linux下,创建进程用 FORK 函数。两个进程复制完成时候一模一样,通过返回值为 0 则判断为子进程,返回其他的整数,则判断为父进程。
- FORK 的子进程和父进程完全一样,所以判断子进程是否完成了通信退出的方法是,父进程通过子进程的 ID 查看是否完成而选择是否退出。
方式二:将项目转包给独立的项目组(多线程方式)
- 在 Linux 下,通过pthread_create 创建一个线程,也是调用 do_fork 。不同的是,虽然新的 task 列表会创建一项,但是很多资源还是共享的。子不是多了一个引用而已。
注:基于进程或者线程模型的通信方式,存在 C10K 问题,所谓 C10K 问题就是 1 台机器要维护 1 万个连接,就要创建 1 万个进程或者线程。显然这样是很不科学且实用的。
方式三:一个项目组支持多个项目(IO多路复用,一个线程维护多个 Socket)
- 一个项目组可以看多个项目,通多项目进度墙来查看项目的进度。一旦项目有进展,就派人去盯一下。
- 由于 Socket 是文件描述符,因而某个线程盯的 Socket ,都放在一个文件描述集合 fd_set 中,这就是项目墙。然后调用 select 函数来监听文件描述符集合是否有变化。
方式四:以恶搞项目组支撑多个项目(IO多路复用,从“派人盯着”到“有事通知”)
- 由于方式三是通过轮询的方式来盯着这个项目,所以还是不够好。因此,采用 epoll 函数通过注册 callback 函数的方式,来当文件描述符发送变化的时候就主动通知。