socket

socket可以用于TCP/UDP连接
对于TCP的socket:
1、服务端调用socket(AF_INET,SOCKET_STREAM,0)即TCP连接IPV4
AF_UNIX是本机 AF_INET6是IPV6
SOCKET_DGRAM是UDP,SOCKET_DGRAM是原始套接字
2、调用bind函数,绑定IP和端口号,由端口号找应用程序,由IP找机器
3、调用listen函数监听(可以通过netstat命令来查看对应端口号有没有被监听)
Linux:netstat -t(TCP)/-u(UDP)/-i(端口)
4、进入监听状态后,调用accept函数,从内核获取客户端连接,如果没有则阻塞等待连接
客户端怎么发起连接?
客户端创建完socket,调用connect函数发起连接

文件描述符即fd,每个进程都有一个数据结构task_struct,结构体中有一个指向文件描述符数组的成员指针,数组的下标就是文件描述符,数组的内容是一个指针,指向内核中所有打开文件的列表
每个文件都有一个inode,socket文件的inode指向了内核中socket结构,在这个结构体中有两个队列分别是发送和接收队列,队列中保存的为struck sk_buff用链表串起来
sk_buff可以表示各个层的数据包,在应用层数据包为data,TCP层位segment,IP层位packet,在数据链路层位frame,就一个数据通过指针来给不同层

多进程模型
服务器的主进程负责监听客户连接,一旦与客户端连接,accept函数就返回一个已连接socket,通过fork函数创建一个子进程,fork会将父进程所有相关东西复制一份,包括文件描述符、内存地址空间、程序计数器、执行的代码
但是返回值不同,返回为0是子进程,子进程会复制父进程的文件描述符,可以直接使用已连接的socket与客户端通信

当子进程退出时,内核中还保留该进程的一些信息,如果没回收好,就会变成僵尸进程,两种方式回收子进程:wait()和waitpid()函数

多线程模型:
当服务器与客户端TCP连接后,通过pthread_create()函数创建线程,然后将已连接socket文件描述符传递给线程函数
用线程池来避免线程频繁创建和销毁

IO多路复用:
进程可以通过一个系统调用函数从内核中获取多个事件
select:
将已连接的socket放到一个文件描述符集合,调用select函数将文件描述集合拷贝到内核里,让内核检查是否有网络事件产生,通过遍历文件描述符集合,当有事件标记socket为可读/可写,然后再把整个文件描述符拷贝回用户态,用户态还需要再通过遍历方法找到可读/科协的socket
这种select需要进行2次遍历文件描述符集合,一次内核态,一次用户态,还有2次拷贝文件描述符集合,从用户空间传入内核空间,由内核修改后再传出用户空间
select用固定长度bitsmap来表示文件描述符集合,所支持的文件描述符个数是有限的,Linux中由内核FD_SETSIZE限制,默认最大为1024,只能监听0-1023
poll不再用bitsmap来存储所关注的文件描述符,用动态数组来替换,以链表形式组织,突破了select个数限制,但还受到系统文件描述符限制
select和poll没太大区别,都是用线性结构存储进程关注的socket集合,因此需要遍历文件描述符集合来找到可读/可写的socket,时间复杂度为O(n),也需要在用户态和内核态之间拷贝文件描述符集合
epoll:
有两方面解决了select/poll问题:
1、epoll在内核使用红黑树来跟踪进程所有待检测的文件描述字,将socket通过epoll_ctl()函数加入至内核中红黑树,红黑树对于线性结构快速增删改,且直接在内核中维护,只需要传入一个待检测的socket减少内核和用户空间大量数据拷贝和内存分配
2、epoll使用事件驱动内核里维护了一个链表来记录就绪事件,当某个socket事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中,当用户调用epoll_wait()函数,只会返回有事件发生的文件描述符个数,不需要像select/poll那样轮询

epoll有两种事件触发模式:边缘触发(ET)和水平触发(LT)
边缘触发:当被监控的socket描述符上有可读事件发生,服务端只会从epoll_wait中苏醒一次,即进程没有调用read函数从内核读取数据,也只有一次,因此需要一次性将内核缓存区数据读完
水平触发:当被监控的socket上有可读事件发生,服务器端不断从epoll_wait中苏醒,直到内核缓存区数据被read函数读完才结束

posted @ 2024-08-12 10:31  我是小白别骂我  阅读(67)  评论(0)    收藏  举报