网络协议梳理(四)(socket、想要实现高并发,可以通过以下四种方式)

基于TCP协议的socket程序函数调用过程

socket需要指定协议是IPv4还是IPv6,分别设置AF_INET和AF_INET6。设置指定TCP协议还是UDP协议。如果是TCP,则参数要设置成SOCK_STREAM,UDP的则要设置成SOCK_DGRAM。

 

 

对于TCP:

 

服务端监听一个端口,调用bind()函数绑定服务端的IP地址和端口号。之后进入监听状态等待客户端的连接以及已连接的客户端发送数据。

在内核中,为每个socket维护两个队列:

  • 一个是已经建立连接的队列,这些连接已经完成了三次握手,处于established状态;
  • 另一个队列是没有完全建立连接的队列,这些连接三次握手还没完成,处于syn_rcvd状态。

客户端通过connect函数发起连接,connect函数中要配置好客户端的IP地址以及端口号,然后发起三次握手。当有新的请求时,服务端调用accept函数来处理请求。内核会为客户端分配一个临时端口,一旦握手完成,服务端的accept会返回另一个socket——即监听的socket和连接的socket的俩socket。

连接建立成功之后,双方通过read函数和write函数进行读写数据,就像往文件中读写一样。

socket在linux中以文件形式存在,其对应一个文件描述符,每个进程都有数据结构task_struct里面指向一个文件描述符数组,文件描述符是一个整数,是这个数组的下标。数组中的内容是一个指针,指针指向内核中所打开的文件列表

既然是文件,就会有一个inode,socket对应的inode存放在内存中,而不像其他文件存放在硬盘上。这个inode中,指向了socket在内核中的socket结构。在这个结构里,有两个队列,

  • 一个是发送队列
  • 一个是接受队列

这两个队列中保存的是一个缓存sk_buff。这个缓存里面能够看到完整的包的结构。

整个数据结构图:

 

对于UDP:

UDP不是面向连接,因此没有三次握手,也不需要调用listen和connect函数。UDP不用维护连接状态,因此不需要没对连接都建立一组socket,而是只需要一个socket,就可以和多个客户端进行通信。也是因为没有连接状态,因此每次通信时,都是调用sendtorecvfrom函数,都可以传入IP地址和端口号。

 

 

 

服务器如何连接更多的项目?

 

最大连接数的计算

系统会用一个四元组来标识一个TCP连接。

{本机 IP, 本机端口, 对端 IP, 对端端口}

                                                  最大 TCP 连接数 = 客户端 IP 数×客户端端口数

对于IPv4,客户端的IP数最多为2的32次幂,客户端的端口上=数最多为2的16次方,也就是服务端单击最大TCP连接数,约为2的

48次幂。

服务器最大并发TCP连接数不能达到理论上限,因为:

  • 文件描述符的限制。socket都是文件,索引首先通过ulimit配置文件描述符的个数
  • 另一个限制是内存,每个TCP链接都要占用一定的内存,操作系统是有限的

 

 

想要实现高并发,可以通过以下四种方式:

 

1、多进程

 

每来一个新的连球,就将基于已连接socket的交互交给这个新的子进程来自做。

linux下,通过fork创建子进程。内核会复制文件描述符的列表复制内存空间,还会复制一条记录当前执行到了哪一行程序的进程。通过fork的返回值可以判断当前进程是父进程(pid!=0)还是子进程(pid=0)。

因为复制了文件描述符表,而文件描述符都是指向整个内核统一的打开的文件列表的,因为父进程刚才因为accept创建的已连接socket也是一个文件描述符,同样也会被子进程获得。

接下来,子进程可以通过这个已经连接的socket和客户端进行通信了。通信完毕之后,可以退出进程。父进程可以通过子进程的进程号查看子进程是否退出。

 

 

2、多线程

 

linux下通过pthread_create创建一个线程,也是调用do_fork。不同的是,虽然新的线程在task列表会新创建一项,但是很多资源,如文件描述符列表、进程空间,还是共享的,只不过多了一个引用而已

新的线程也可以通过已连接socket处理请求,从而达到并发处理的目的。

 

 

3、IO多路复用,一个线程维护多个socket——select

 

由于socket的文件描述符,将文件描述符都放在文件描述符集合fd_set中,然后调用select函数来监听文件描述符集合是否有变化。一旦有变化,就会依次查看每个文件描述符。那些发生变化的文件描述符在fd_set对应的位都设为1,表示socket可读或者可写,从而可以进行读写操作。然后调用select盯着下一轮的变化。

select的问题就是,每次都是通过轮询的方式查看发生变化的文件描述符,时间复杂度比较高,因此常常用FD_SETSIZE限制。

 

 

4、IO多路复用——epoll

 

不需要通过轮询的方式来查看变化。发生变化的文件描述符会主动通知。epoll在内核中的实现不是通过轮询的方式,而是通过注册callback函数的方式,当某个文件描述符发生变化,就会主动通知

如图,进程打开了socket m,n,x等多个文件描述符,现在通过epoll来监听这些socket都有事件发生。其中,epoll_create创建一个epoll对象,也就是一个文件,也对应一个文件描述符,该文件描述符也对应着打开文件列中的一项。在这项里有一个红黑树,在红黑树里,保存这个epoll监听的所有socket。

当epoll_ctl添加一个socket的时候,其实是加入这个红黑树,同时红黑树里面的节点指向一个结构,将这个结构挂在被监听的socket的事件列表中。当一个socket带来一个事件的时候,可以从这个列表中得到epoll对象,并调用call back通知他。

这种通知方式使得监听的socket数据增加的时候,效率不会大幅度降低,能够同时监听的socket的数目非常多。上限就为系统定义的、进程打开的最大文焕描述符个数。

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值