Linux环境:C编程之网络通信进阶操作

12 篇文章 0 订阅
4 篇文章 0 订阅

参考链接:IO多路复用之epoll总结
参考链接:套接字的阻塞与非阻塞

套接字选项

套接字选项详细规定了套接字的属性,影响socket的各项操作。我们可以通过setsockopt函 数和getsockopt函数愉快的设定和获取socket的属性。
首先来看一下setsockopt函数:

setsockopt函数
  • 作用:设定socket的属性,注意不是修改,setsockopt操作应该在bind之前
  • 原型:int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
  • 参数:
    • sockfd:要设置的socket的文件描述符
    • level:指定了套接字选项所适用的协议,比如IPPROTO_TCP、IPPROTO_IP 和 IPPROTO_IPV6分别代表了tcp、ip和ipv6协议,通常情况下为SOL_SOCKET,表示作用于套接字API层。
    • optname:需设置的socket选项
    • optval:指向缓冲区的指针,用来指定要设置的值,根据选项不同指向整数或者结构体
    • opilen:表明缓冲区的长度、
  • 返回值:成功返回0,失败返回-1
getsockopt函数
  • 作用:获取socket的属性
  • 原型:int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
  • 参数:
    • sockfd:要获取选项的socket的文件描述符
    • level:指定了套接字选项所适用的协议,比如IPPROTO_TCP、IPPROTO_IP 和 IPPROTO_IPV6分别代表了tcp、ip和ipv6协议,通常情况下为SOL_SOCKET,表示作用于套接字API层。
    • optname:需获取的socket选项
    • optval:传出参数,指向缓冲区的指针,用来存储要获取的选项,根据选项不同指向整数或者结构体
    • opilen:传出参数,返回缓冲区的长度、
  • 返回值:成功返回0,失败返回-1
套接字API层常用的socket属性
名称选项数值类型
SO_BROADCAST允许发送广播数据int
SO_DEBUG允许调试int
SO_DONTROUTE不查找路由int
SO_ERROR获得套接字错误int
SO_KEEPALIVE保持连接int
SO_LINGER延迟关闭连接struct linger
SO_OOBINLINE带外数据放入正常数据流int
SO_RCVBUF接收缓冲区大小int
SO_SNDBUF发送缓冲区大小int
SO_RCVLOWAT接收缓冲区下限int
SO_SNDLOWAT发送缓冲区下限int
SO_RCVTIMEO接收超时struct timeval
SO_SNDTIMEO发送超时struct timeval
SO_REUSEADDR允许重用本地地址和端口int
SO_TYPE获得套接字类型int
SO_BSDCOMPAT与 BSD 系统兼容int

epoll 多路复用模型

epoll多路复用模型是linux中特有的,相比之前学过的select,参见linux环境:C编程文件操作,epoll更加灵活,性能也更出色,支持水平触发和边缘触发两种触发模式。

接口函数

epoll模型由三个函数组成:
int epoll_create(int size);//创建一个epoll实例
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//添加监控的套接字
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);//返回I/O就绪的套接字
函数原理:

  • epoll_creat函数创建epoll实例,返回对应的文件描述符。size参数指定文件的初始大小,即要监控的套接字的个数,没有太大意义,因为epoll实例可以根据需要修改添加监控对象。
  • epoll_ctl事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
    • 第一个参数是epoll_create()的返回值,
    • 第二个参数表示动作,用三个宏来表示:
      EPOLL_CTL_ADD:注册新的fd到epfd中;
      EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
      EPOLL_CTL_DEL:从epfd中删除一个fd;
    • 第三个参数是需要监听的fd
    • 第四个参数是告诉内核需要监听什么事,
      • struct epoll_event结构如下
    //联合体,事件触发时返回的信息,一般填对应的文件描述符,来表示该事件由谁触发
    	typedef union epoll_data {
    	void *ptr;
    	int fd;
    	__uint32_t u32;
    	__uint64_t u64;
    	} epoll_data_t;
    
    	struct epoll_event {
    	__uint32_t events; //触发事件
    	epoll_data_t data; 
    	};
    
    events可以是以下几个宏的集合:
    EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
    EPOLLOUT:表示对应的文件描述符可以写;
    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
    EPOLLERR:表示对应的文件描述符发生错误;
    EPOLLHUP:表示对应的文件描述符被挂断;
    EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
    EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
  • epoll_wait函数返回就绪的套接字列表,第一个参数是epoll实例,第二个参数是返回的结构体数组,每个元素对应一个套接字的信息,第三个列表是该结构体数组的容量, 值不能大于epoll实例中就绪列表的最大长度,第四个参数是监视计时器(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
触发模式

LT模式:工作逻辑和select一致,只要缓冲区中存在没有读写的数据就一直触发。
ET模式:仅当缓冲区状态发生变化的时候才获得通知。这里的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,否则未读写的数据就会丢失。

套接字的阻塞和非阻塞模式

套接字的默认状态是阻塞状态,即在进行bind,read,write,send,recv,connect,accept等操作时,如果没有输入,则会阻塞进程。

设置非阻塞套接字

可以通过设置文件描述符的标志位来把套接字设置为非阻塞模式。
用到以下函数:
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
fcntl函数可以获取或者设置文件的标志位,fd是文件描述符,cmd是命令,如果有第三个参数则是命令的参数。
通过以下代码可以设置非阻塞套接字

	//获取当前文件的文件描述标志位
	int status=fcntl(fd,F_GETFL);
	//将该标志位或上非阻塞标志
    status=status|O_NONBLOCK;
    //重新设置文件的标志位
    fcntl(fd,F_SETFL,status);
阻塞和非阻塞的区别

套接字调用可分为四种

  • 输入操作,包括read,readv,recv,recvfrom,recvmsg。

    阻塞:

    • TCP:如果接收缓冲区没有数据读,则阻塞,直到数据到达。

    • UDP:如果接收缓冲区没有数据读,则阻塞,直到UDP数据报到达。

    非阻塞:

    • 如果输入操作不能被满足(对于TCP套接字即至少有一个字节的数据可读,对于UDP套接字即有一个完整的数据报可读),相应调用将立即返回EWOULDBLOCK。
  • 输出操作,包括write,writev,send,sendto,sendmsg
    阻塞:

    • TCP:如果发送缓冲区没有空间,则阻塞。有一些空间时,则返回不足计数

    非阻塞:

    • TCP:如果发送缓冲区没有空间,会立即返回一个EWOULDBLOCK错误。如
      果有一些空间,返回值将是内核能够复制到该缓冲区中的字节数。
    • UDP:没有发送缓冲区,不会因与TCP套接字一样的原因而阻塞,不过有可能会因其他原因而阻塞。
  • 接收外来连接,即用于accept函数

    • 阻塞:阻塞在accept,直到有新的连接
    • 非阻塞:没有新的连接来时,accept调用将立即返回一个EWOULDBLOCK
  • 发起外出连接,即用于TCP的connect函数。

    • 阻塞:connect函数一直要等到客户收到对于自己的SYN的ACK为止才返回。所以TCP的每一个connect总会阻塞其调用的进程至少一个到服务器的RTT时间。

    • 非阻塞:调用connect,并连接不能立即建立,那么连接的建立能照样发起,不过会返回一个EINPROGRESS错误。这个错误不同于上述三个情形,但客户端和服务端在同一主机,这些连接会立即建立。所以也要预备connect成功返回的情况。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值