epoll socket 服务端中read和write的返回值讨论

    先贴一段代码,代码很简单要看过epoll如何使用,都应该能看懂。

    这是服务端程序:

#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#define MAXLINE		10
#define OPEN_MAX	100
#define LISTENQ		20
#define SERV_PORT	5555
#define INFTIM		1000

void setnonblocking(int sock)
{
	int opts;
	opts = fcntl(sock, F_GETFL);

	if(opts < 0) {
		perror("fcntl(sock, GETFL)");
		exit(1);
	}

	opts = opts | O_NONBLOCK;

	if(fcntl(sock, F_SETFL, opts) < 0) {
		perror("fcntl(sock, SETFL, opts)");
		exit(1);
	}
}

int main(int argc, char *argv[])
{
	printf("epoll socket begins.\n");
	int i, maxi, listenfd, connfd, sockfd, epfd, nfds, on = 1;
	ssize_t n;
	char line[MAXLINE];
	socklen_t clilen;

	struct epoll_event ev, events[20];

	epfd = epoll_create(256);

	struct sockaddr_in clientaddr;
	struct sockaddr_in serveraddr;

	listenfd = socket(AF_INET, SOCK_STREAM, 0);

	setnonblocking(listenfd);

	ev.data.fd = listenfd;
	ev.events = EPOLLIN | EPOLLET;

	epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);


	setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(int));
	bzero(&serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	char *local_addr = "0.0.0.0";
	inet_aton(local_addr, &(serveraddr.sin_addr));
	serveraddr.sin_port = htons(SERV_PORT);

	int ret = bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
	if(ret < 0)
	{
		printf("%s\n", "bind error!");
		exit(-1);
	}

	listen(listenfd, LISTENQ);

	maxi = 0;

	for(; ;) 
	{
		nfds = epoll_wait(epfd, events, 20, 500);

		for(i = 0; i < nfds; ++i) 
		{
			if(events[i].data.fd == listenfd) {
				printf("accept connection, fd is %d\n", listenfd);
				connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
				if(connfd < 0) {
					perror("connfd < 0");
					exit(1);
				}

				setnonblocking(connfd);

				char *str = inet_ntoa(clientaddr.sin_addr);
				printf("connect from %s\n", str);

				ev.data.fd = connfd;
				ev.events = EPOLLIN | EPOLLET;
				epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
			}
			else if(events[i].events & EPOLLIN) 
			{
				sockfd = events[i].data.fd;
				n = read(sockfd, line, MAXLINE);
				if(n < 0 && errno == EAGAIN)
				{
					printf("%s\n", "数据已经读完,没有更多的数据可以读了。");
				}
				else if(n == 0) //errno 
				{
					printf("%s\n", "客户端断开连接。");
					epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, &ev);
				}			
				else if(n <= 0)					
				{
					printf("%s\n错误代码为%d", "读出错。", errno);
					epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, &ev);
				}
				else
				{					 
					printf("%s\n", line);
					memset(line, 0, MAXLINE);
					n = write(sockfd, "hello client!", strlen("hello client!"));
					if(n < 0 && errno == EAGAIN)
					{
						printf("%s\n", "系统缓冲区数据已写满。");
					}
					else if(n <= 0)
					{
						printf("%s\n错误代码为%d", "客户端断开连接或出错。", errno);						
					}					
					else
					{					
						ev.data.fd = sockfd;
						ev.events = EPOLLIN | EPOLLET;					
						epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
					}
				}				
			}
			else if(events[i].events & EPOLLOUT) 
			{
			}
		}
	}
}

 

    客户端程序:

    可以不用代码写客户端,用telnet连接就行。

telnet localhost 5555

 

#!/usr/bin/python

from socket import *
from time import sleep

host = gethostname()
addr = (host, 5555)

sockCli = socket()

sockCli.connect(addr)

while True:
	sockCli.sendall("sadf")
	data = sockCli.recv(1024)
	print data
	sleep(1)

sockCli.close()

 

这段服务端的代码展示的是用,epoll写的一个简单的服务程序, ET非阻塞模式。

关于read的返回值:

    1、返回值n=-1, errno = EAGAIN;当返回值等于你要读取的数据时,说明你还有数据要读。 当数据量比较大,多次读数据的时候非阻塞read的返回值为-1,errno值为11(EAGAIN)时,这个 并不是发生了错误,而是数据已经被读完,这个时候停止读数据就好了。阻塞模式不会这样,因为当没有数据的时候,read就阻塞了。

    2、返回值n=0, errno = 0。说明客户端已经关闭了。经过测试即使没有数据的时候,阻塞的read就阻塞,非阻塞的read时为第一种情况。只有客户端关闭时返回0(socket fd是这样其他io流为测试) 。这个时候就就不要再注册事件了,从队列中删除这个fd吧。而且当客户端断开时,epoll会主动通知一个EPOLLIN事件。

    3、返回值n=-1,errno != EAGAIN;时是错了,看一下错误码是多吧。

关于write的返回值:

    1、返回值n=-1,errno = EAGAIN时; 说明系统缓冲区已经满了,不能再写进去了。当write为阻塞时不会返回这个错误,会阻塞掉。

    2、返回值n<=0时;客户端断开连接或出错。

何时系统会通知一个写事件:

    对于LT 模式,如果注册了EPOLLOUT,只要该socket可写(发送缓冲区)未满,那么就会触发EPOLLOUT。

    对于ET模式,如果注册了EPOLLOUT,只有当socket从不可写变为可写的时候,会触发EPOLLOUT。

    上面server端的例子,当你要写入的数据比较大的时候,可能就会有困扰了,如何写入的数据量较大,把系统缓冲区写满了,如果write设置是阻塞的,那就会阻塞了。这可能不是我们想要的。

    这个时候就需要注册写事件,自己在应用层加个发送缓冲区,需要发送数据的时候,就写到应用层的发送缓冲区。触发write事件的时候,从缓冲区写入socket。

 

转载于:https://my.oschina.net/u/2255341/blog/650661

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值