C++项目TinyWebServer学习记录(上)

基础知识
RAII
全称  Resource Acquisition is Initialization 资源获取即初始化
  • 在构造函数中申请分配资源,在析构函数中释放资源。在RAII的指导下,我们需要使用类来管理资源,将资源和对象生命周期绑定
  • RAII的核心思想是将资源或者状态与对象的生命周期绑定,通过C++构造函数和析构函数的语言机制,实现资源和状态的安全管理,智能指针就是RAII最好的例子
信号量机制
  • 信号量是一种特殊的变量,只能取自然数,只支持两种操作:P、V操作,假设有信号量SV
    • P,如果SV大于0,则减一,如果SV等于0,则挂起执行
    • V,如果有其他进行因为等待SV而挂起,则唤醒SV;如果没有,则将SV加一
  • 最常用的是二进制信号量,主要操作:
  • sem_init函数用于初始化一个未命名的信号量
  • sem_destory函数用于销毁信号量
  • sem_wait函数将以原子操作方式将信号量减一,信号量为0时,sem_wait阻塞
  • sem_post函数以原子操作方式将信号量加一,信号量大于0时,唤醒调用sem_post的线程
    以上成功返回0,失败返回errno
互斥量
也称互斥锁,保护关键代码段,确保独占式访问,进入关键代码段时,获得互斥锁并加锁,离开关键代码段时,唤醒等待该互斥锁的线程
  • pthread_mutex_init函数用于初始化互斥锁
  • pthread_mutex_destory函数用于销毁互斥锁
  • pthread_mutex_lock函数以原子操作方式给互斥锁加锁
  • pthread_mutex_unlock函数以原子操作方式给互斥锁解锁
以上成功返回0,失败返回errno
条件变量,提供了一种线程间的通知机制,当某个共享数据达到某个值时,唤醒等待这个共享数据的线程
  • pthread_cond_init函数用于初始化条件变量
  • pthread_cond_destory函数销毁条件变量
  • pthread_cond_broadcast函数以广播的方式唤醒 所有等待目标条件变量的线程
  • pthread_cond_wait函数用于等待目标条件变量.该函数调用时需要传入  mutex参数(加锁的互斥锁) ,函数执行时,先把调用线程放入条件变量的请求队列,然后将互斥锁mutex解锁,当函数成功返回为0时,互斥锁会再次被锁上.  也就是说函数内部会有一次解锁和加锁操作
五种IO模型:
  • 阻塞IO:调用者调用了某个函数,等待这个函数返回,期间什么也不做,不停的去检查这个函数有没有返回,必须等这个函数返回才能进行下一步动作。也就是在函数返回之前,调用者一直处于等待数据状态,什么也不做知道函数返回值
    • 术语描述 :在应用调用recvfrom读取数据时,其系统调用直到数据包到达且被复制到应用缓冲区中或者发送错误时才返回,在此期间一直会等待,进程从调用到返回这段时间内都是被阻塞的称为阻塞IO;
  • 非阻塞IO:非阻塞等待,每隔一段时间就去检测IO事件是否就绪。没有就绪就可以做其他事。非阻塞I/O执行系统调用总是立即返回,不管时间是否已经发生,若时间没有发生,则返回-1,此时可以根据errno区分这两种情况,对于accept,recv和send,事件未发生时,errno通常被设置成eagain
    • 术语 :非阻塞IO是在应用调用recv读取数据时,如果该缓冲区没有数据的话,就会直接返回一个EWOULDBLOCK错误,不会让应用一直等待中。在没有数据的时候会即刻返回错误标识,那也意味着如果应用要读取数据就需要不断的调用recv请求,直到读取到它数据要的数据为止。
  • 信号驱动IO:linux用套接口进行信号驱动IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO时间就绪,进程收到SIGIO信号。然后处理IO事件。
  • IO复用:linux用select/poll函数实现IO复用模型,这两个函数也会使进程阻塞,但是和阻塞IO所不同的是这两个函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检测。知道有数据可读或可写时,才真正调用IO操作函数, select函数有一个参数是文件描述符集合,对这些文件描述符进行循环监听,当某个文件描述符就绪时,就对这个文件描述符进行处理。
  • 异步IO:linux中,可以调用aio_read函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方式,然后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序。相当于雇人来完成任务
    • 术语描述:  应用告知内核启动某个操作,并让内核在整个操作完成之后,通知应用,这种模型与信号驱动模型的主要区别在于,信号驱动IO只是由内核通知我们合适可以开始下一个IO操作,而异步IO模型是由内核通知我们操作什么时候完成。
阻塞程度:阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO,效率是由低到高的
高效事件处理模式
服务器程序通常需要处理三类事件: I/O 事件、信号及定时事件 。有两种高效的事件处理模式: Reactor   Proactor 同步 I/O  模型通常用于实现  Reactor  模式, 异步 I/O  模型通常用于实现  Proactor  模式。
Reactor反应堆,事件驱动机制
  • 主线程只负责监听文件描述符上是否有事件发生,如果有的话就立即将该事件通知工作线程(即逻辑单元),除此之外,主线程不做任何其他实质性的工作
  • 读写数据,接收新的连接,以及处理客户请求(业务逻辑)均在工作线程中完成
  • 使用同步IO模型(以epoll_wait为例)实现的Reactor模式的工作流程
    1. 主线程往epoll内核事件表中 注册 socket上的读就绪事件。(监听socket与连接socket成功建立连接后, 以下socket都指的是连接socket )
    2. 主线程调用epoll_wait 等待 socket上有数据可读。
    3. 当socket上有数据可读时,epoll_wait 通知 主线程。主线程则将socket可读事件 放入 请求队列。
    4. 睡眠在请求队列上的某个工作线程被 唤醒 ,它从连接socket读取数据,并 处理 客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件。
    5. 主线程调用epoll_wait 等待 scoket可写。
    6. 当socket可写时,epoll_wait 通知 主线程。主线程将socket可写事件 放入 请求队列。
    7. 睡眠在请求队列上的某个工作线程被 唤醒 ,它往socket上 写入 服务器处理客户请求的结果
总结:主线程仅负责监听socket是否有发生事件,然后就通知工作线程读取,处理数据,如为写事件,再在epoll内核事件表上注册socket的可写事件,然后再由某个工作线程接管,处理,执行回答,读事件同理
Reactor模式中,没必要区分所谓的“读工作线程”和“写工作线程”
Proactor模式
  • Proactor模式将所有IO操作都交给主线程和内核来处理,工作线程仅仅负责业务逻辑
  • 因此,Proactor模式更符合服务器基本框架图中的描述
  • 使用异步IO模型(以aio_read 和aio_write为例)
    1. 主线程调用aio_read函数向内核 注册socket上的读完成事件 ,并告诉内核用户 读缓冲区的位置 ,以及读操作完成时 如何通知 应用程序
    2. 主线程 继续处理其它逻辑
    3. 当socket上的数据被 读入用户缓冲区后 ,内核用户 向应用程序发送一个信号 ,以 通知应用程序数据已经可用
    4. 应用程序 预先定义好的信号处理函数选择一个工作线程来处理客户请求 。工作线程处理完客户请求之后,调用aio_write函数向内核 注册socket上的写完成事件 ,并告诉内核用户 写缓冲区的位置 ,以及写操完成时 如何通知 应用程序(仍以信号为例)
  • 总结:内核用户与主线程进行IO操作,由信号通知主线程唤醒一个工作线程进行处理数据(业务逻辑),业务处理完,再交给内核用户与主线程进行IO操作(服务器应答)
半同步/半反应堆线程池(以Proactor模式为例)
半同步/半反应堆线程池是一种线程池的实现方式,它结合了同步和异步处理的特点,以优化多线程环境下的任务处理效率。
在这种线程池模型中,主线程和工作线程之间通过一个共享的工作队列进行同步。主线程负责监听和接收连接请求,当有新的任务到来时,它会将任务对象添加到工作队列中。工作线程则睡眠在工作队列上,等待任务的到来。一旦有任务被添加到队列中,会唤醒正在等待的工作线程。然而,并不是所有的工作线程都会被唤醒,通常只有一个线程会获得新任务的“接管权”,从队列中取出任务并执行,而其他线程则继续睡眠等待。
这种机制避免了大量的线程创建和销毁开销,同时也减少了线程上下文切换的成本。线程池中的线程在完成任务后不会被销毁,而是被放回线程池中等待下一次任务。这样可以更有效地利用系统资源,提高处理大量并发任务的能力。
此外,半同步/半反应堆线程池还通过解耦主线程和工作线程来提高系统的可扩展性和稳定性。由于主线程和工作线程之间通过工作队列进行通信,它们之间没有直接的耦合关系。主线程只需要将任务添加到队列中,而无需关心具体由哪个工作线程来处理。这使得系统能够更容易地处理更多的客户端请求,提高并发性能。
处理IO多路复用的机制:

1. select的工作原理

select的工作原理主要基于轮询机制。在调用select时,会传入一个文件描述符集合,这些文件描述符代表了程序感兴趣的I/O通道。select会阻塞进程,直到有文件描述符就绪(可读、可写或有异常)或者超时。一旦有文件描述符就绪,select会返回,并告诉进程哪些文件描述符已经就绪。然后,进程需要遍历返回的文件描述符集合,找到并处理就绪的文件描述符。

2. poll的工作原理

poll的工作原理与select类似,也是通过轮询文件描述符集合来检查是否有文件描述符就绪。但是,poll在结构上更为灵活,它不限制单个进程所能打开的最大文件描述符数量。在调用poll时,同样会传入一个文件描述符数组和一个超时时间。内核会遍历这个数组,检查每个文件描述符对应的事件是否就绪。如果有就绪的事件,内核会将其放入一个事件集合中,并返回给进程。进程同样需要遍历事件集合来处理就绪的文件描述符。

3. epoll的工作原理

epoll是Linux特有的I/O多路复用机制,它在处理大量文件描述符时具有更高的效率。epoll的工作原理主要基于事件驱动。
首先,通过epoll_ctl函数,进程可以将关心的文件描述符添加到内核的一个事件表中。然后,通过epoll_wait函数,进程可以等待事件表中的文件描述符就绪。与selectpoll不同,epoll不需要每次调用都重复传入文件描述符集合,因为文件描述符已经保存在内核的事件表中。当事件表中的某个文件描述符就绪时,内核会通知epoll_wait,并返回就绪的文件描述符集合。进程可以直接处理这些就绪的文件描述符,无需遍历整个文件描述符集合。
此外,epoll还支持两种触发模式:水平触发(LT)和边缘触发(ET)。水平触发模式下,只要文件描述符就绪,就会一直通知进程;而边缘触发模式下,只在文件描述符状态发生变化时通知进程一次。这使得epoll在处理大量并发连接时具有更高的性能和效率。
select
poll
          epoll
操作方式
遍历
遍历
回调
底层实现
数组
链表
红黑树
IO效率
每次调用都进行线性遍历,时间复杂度为O(n)
每次调用都进行线性遍历,时间复杂度为O(n)
事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1)
最大连接数
1024(x86)或2048(x64)
无上限
无上限
fd拷贝
每次调用select,都需要把fd集合从用户态拷贝到内核态
每次调用poll,都需要把fd集合从用户态拷贝到内核态
调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝
作者:似水牛年
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
epoll主要函数:#include<sys/epoll.h>
  • int epoll_create(int size)
    • 创建一个指示epoll内核时间表的文件描述符fd,该描述符将用作其他epoll系统调用的第一个参数,size没作用
  • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
    • 用于操作内核事件表监控的文件描述符上的事件:注册、修改、删除
    • epfd:为epoll_create的句柄
    • op:表示动作,用三个宏来表示:
    • EPOLL_CTL_ADD (注册新的fd到epfd),
    • EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
    • EPOLL_CTL_DEL (从epfd删除一个fd);
  • event:告诉内核需要监听的事件
event是epoll_event结构体指针类型,表示内核所监听的事件,具体定义如下:
struct  epoll_event {
__uint32_t  events;  /* Epoll events */
epoll_data_t  data;  /* User data variable */
};
  • events描述事件类型,其中epoll事件类型有以下几种
    • EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
    • EPOLLOUT:表示对应的文件描述符可以写
    • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
    • EPOLLERR:表示对应的文件描述符发生错误
    • EPOLLHUP:表示对应的文件描述符被挂断;
    • EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的
    • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
  • int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout)
    • maxevents:告知这个events的大小,该值不能大于创建epoll_create()时的size
    • timeout:超时时间
      • -1:阻塞
      • 0:立刻返回。非阻塞
      • >0:指定毫秒
    • 返回值:成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1
  • LT水平触发模式(select和poll只能工作在此模式下)
    • epoll_wait检测到文件描述符有事件发生,则将其通知给应用程序,应用程序可以不立即处理该事件。
    • 当下一次调用epoll_wait时,epoll_wait还会再次向应用程序报告此事件,直至被处理
  • ET边缘触发模式
    • epoll_wait检测到文件描述符有事件发生,则将其通知给应用程序,应用程序必须立即处理该事件
    • 必须要一次性将数据读取完,使用非阻塞I/O,读取到出现eagain
  • EPOLLONESHOT
    • 一个线程读取某个socket上的数据后开始处理数据,在处理过程中该socket上又有新数据可读,此时另一个线程被唤醒读取,此时出现两个线程处理同一个socket
    • 我们期望的是一个socket连接在任一时刻都只被一个线程处理,通过epoll_ctl对该文件描述符注册epolloneshot事件,一个线程处理socket时,其他线程将无法处理, 当该线程处理完后,需要通过epoll_ctl重置epolloneshot事件
HTTP报文分为请求报文和响应报文,都有固定格式。浏览器向服务器发送的为请求报文,服务器处理后返回个浏览器的为响应报文
请求报文
  • 请求报文分为 请求行(request line)、请求头部(header)、空行和请求数据四个部分
  • 请求报文又分为两种
    • get和post
    • GET请求主要用于获取资源,即进行查询操作。而POST请求则主要用于传输实体对象,通常用于添加、修改和删除等操作。尽管在实际开发中,POST请求有时也用于删除操作,但按照规范,删除操作应使用DELETE请求。
    • get为明文传输,post将参数放在请求体中,post更加安全
    
响应报文
四个部分: 状态行、消息报头、空行和响应正文
HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8
空行
<html>
      <head></head>
      <body>
            <!--body goes here-->
      </body>
</html>
有限状态机(FSM)
一种抽象的理论模型,它能够把有限个变量描述的状态变化过程,以可构造可验证的方式呈现出来。从软件工程的角度来说,主要是为了封装逻辑
STATE_MACHINE(){
    State cur_State = type_A;
    while(cur_State != type_C){
        Package _pack = getNewPackage();
        switch(){
            case type_A:
                process_pkg_state_A(_pack);
                cur_State = type_B;
                break;
            case type_B:
                process_pkg_state_B(_pack);
                cur_State = type_C;
                break;
        }
    }
}
有限状态机一种逻辑单元内部的一种高效编程方法,在服务器编程中,服务器可以根据不同状态或者消息类型进行相应的处理逻辑,使得程序逻辑清晰易懂。
主状态机:
  • CHECK_STATE_REQUESTLINE,解析请求行
  • CHECK_STATE_HEADER,解析请求头
  • CHECK_STATE_CONTENT,解析消息体,仅用于解析POST请求
从状态机:
  • LINE_OK,完整读取一行
  • LINE_BAD,报文语法有误
  • LINE_OPEN,读取的行不完整
HTTP_CODE含义
表示HTTP请求的处理结果,在头文件中初始化了八种情形,在报文解析时只涉及到四种。
  • NO_REQUEST
    • 请求不完整,需要继续读取请求报文数据
  • GET_REQUEST
    • 获得了完整的HTTP请求
  • BAD_REQUEST
    • HTTP请求报文有语法错误
  • INTERNAL_ERROR
    • 服务器内部错误,该结果在主状态机逻辑switch的default下,一般不会触发
上面是服务器解析请求报文部分,接下来是发送响应报文
基础API:
  • stat函数用于取得指定文件的文件属性,并将文件属性存储在结构体stat里,这仅对用到的成员进行介绍
    • //获取文件属性,存储在statbuf中
    • int  stat ( const  char  *pathname, struct stat *statbuf);
    • struct  stat 
    • {
    • mode_t st_mode; /* 文件类型和权限 */
    • off_t st_size; /* 文件大小,字节数*/
    • };
  • mmap函数用于将一个文件或其他对象映射到内存,提高文件的访问速度
    • void mmap ( void * start, size_t  length, int  prot, int  flags, int  fd, off_t  offset);
    • int  munmap ( void * start, size_t  length);
  • start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址
  • length:映射区的长度
  • prot:期望的内存保护标志,不能与文件的打开模式冲突
    • PROT_READ 表示页内容可以被读取
  • flags:指定映射对象的类型,映射选项和映射页是否可以共享
    • MAP_PRIVATE 建立一个写入时拷贝的私有映射,内存区域的写入不会影响到原文件
  • fd:有效的文件描述符,一般是由open()函数返回
  • off_toffset:被映射对象内容的起点
  • iovec定义了一个向量元素,通常,这个结构用作一个多元素的数组
        struct iovec{
            void *iov_base;//指向数据的地址
            size_t iov_len;//表示数据的长度
}
  • writev 函数用于在一次函数调用中写多个非连续缓冲区,有时也将这该函数称为聚集写
  • writev  是一个 Unix 系统调用,它允许应用程序一次性写入多个不连续的数据缓冲区到文件描述符(通常是套接字)中

#include<sys/uio.h>
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
  • filedes表示文件描述符
  • iov为前述io向量机制结构体iovec
  • iovcnt为结构体的个数
若成功则返回已写的字节数,若出错则返回-1。 writev 以顺序 iov[0] iov[1] iov[iovcnt-1] 从缓冲区中聚集输出数据。 writev 返回输出的字节总数,通常,它应等于所有缓冲区长度之和。
特别注意:  循环调用writev时,需要重新处理iovec中的指针和长度,该函数不会对这两个成员做任何处理。writev的返回值为已写的字节数,但这个返回值“实用性”并不高,因为参数传入的是iovec数组,计量单位是iovcnt,而不是字节数,我们仍然需要通过遍历iovec来计算新的基址,另外写入数据的“结束点”可能位于一个iovec的中间某个位置,因此需要调整临界iovec的iov_base和iov_len。
HTTP_CODE含义
表示HTTP请求的处理结果,在头文件中初始化了八种情形,在报文解析与响应中只用到了七种。
  • NO_REQUEST
    • 请求不完整,需要继续读取请求报文数据
    • 跳转主线程继续监测读事件
  • GET_REQUEST
    • 获得了完整的HTTP请求
    • 调用do_request完成请求资源映射
  • NO_RESOURCE
    • 请求资源不存在
    • 跳转process_write完成响应报文
  • BAD_REQUEST
    • HTTP请求报文有语法错误或请求资源为目录
    • 跳转process_write完成响应报文
  • FORBIDDEN_REQUEST
    • 请求资源禁止访问,没有读取权限
    • 跳转process_write完成响应报文
  • FILE_REQUEST
    • 请求资源可以正常访问
    • 跳转process_write完成响应报文
  • INTERNAL_ERROR
    • 服务器内部错误,该结果在主状态机逻辑switch的default下,一般不会触发
定时器
非活跃,是指客户端(这里是浏览器)与服务器建立连接后,长时间步交换数据,一直占用服务端的文件描述符,导致连接资源浪费。
定时事件,指固定一段时间之后触发某段代码,由该段代码处理一个事件,如从内核事件表删除事件,并关闭文件描述符,释放连接资源
定时器,是指利用结构体或其他形式,将多种定时事件进行封装起来。具体只涉及一种定时事件,即定期检测非活跃连接,这里将该定时事件与资源封装为一个结构体定时器
定时器容量,指某种容器类数据结构,将上述多个定时器组合起来,便于对定时事件统一管理。具体的,项目中使用升序链表将所有定时器串联组织起来
本项目中,服务器主循环为每一个连接创建一个定时器,并对每个连接进行定时。另外,利用升序时间链表容器将所有定时器串联起来,若主循环接收到定时通知,则在链表中依次执行定时任务。
Linux下提供了三种定时的方法:
  • socket选项SO_RECVTIMEO和SO_SNDTIMEO
  • SIGALRM信号
  • I/O复用系统调用的超时参数
三种方法各有优劣。本项目使用SIGALRM信号。
利用alarm函数周期性地触发SIGALRM信号,信号处理函数利用管道通知主函数,主循环接收到该信号后对升序链表上所有定时器进行处理,若该时间段没有交换数据,则将该链接关闭,释放所占用的资源
定时器处理非活动连接模块,主要分为两部分,其一为定时方法与信号通知流程,其二为定时器及其容器设计与定时任务的处理。
struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}
  • sa_handler是一个函数指针,指向信号处理函数
  • sa_sigaction也指向信号处理函数,有三个参数,可以获得关于信号更详细的信息
  • sa_mask用来指定在信号处理函数执行期间需要被屏蔽的信号
  • sa_flags用于指定信号处理的行为
    • SA_RESTART,使被信号打断的系统调用自动重新发起
    • SA_NOCLDSTOP,使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号
    • SA_NOCLDWAIT,使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程
    • SA_NODEFER,使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号
    • SA_RESETHAND,信号处理之后重新设置为默认的处理方式
    • SA_SIGINFO,使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数
  • sa_restorer一般不使用
sigaction函数
sigaction 函数是一个在Unix/Linux系统中用于处理信号的函数。它允许程序修改某个信号的处理方式,使程序能够对信号做出指定的响应
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • signum表示操作的信号
  • act表示对信号设置新的处理方式
  • oldact表示信号原来的处理方式
  • 返回值0表示成功,-1表示有错误发生
sigfillset函数
用于将参数set信号集初始化,然后把所有的信号加入到此信号集里
#include <signal.h>
int sigfillset(sigset_t *set);
SIGALRM、SIGTERM信号
#define SIGALRM 14 //由alarm系统调用产生timer时钟信号
#define SIGTERM 15 //终端发送的终止信号
alarm函数
#include <unistd.h>;
unsigned int alarm(unsigned int seconds);
设置信号传送闹钟,即用来设置信号SIGALRM在经过参数seconds秒数后发送给目前的进程。如果未设置信号SIGALRM的处理函数,那么alarm()默认处理终止进程.
socketpair函数
在linux下,使用socketpair函数能够创建一对套接字进行通信,项目中使用管道通信。
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);
  • domain表示协议族,PF_UNIX或者AF_UNIX
  • type表示协议,可以是SOCK_STREAM或者SOCK_DGRAM,SOCK_STREAM基于TCP,SOCK_DGRAM基于UDP
  • protocol表示类型,只能为0
  • sv[2]表示套节字柄对,该两个句柄作用相同,均能进行读写双向操作
  • 返回结果, 0为创建成功,-1为创建失败
send函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
当套接字发送缓冲区变满时,send通常会阻塞,除非套接字设置为非阻塞模式。非阻塞模式下,当缓冲区变满时,返回EAGAIN或者EWOULDBLOCK错误,此时可以调用select函数来监视何时可以发送数据。
  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值