epoll socket 服務端中read和write的返回值討論

    先貼一段代碼,代碼很簡單要看過epoll如何使用,都應該能看懂。python

    這是服務端程序:socket

#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鏈接就行。code

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非阻塞模式。server

關於read的返回值:隊列

    一、返回值n=-1, errno = EAGAIN;當返回值等於你要讀取的數據時,說明你還有數據要讀。 當數據量比較大,屢次讀數據的時候非阻塞read的返回值爲-1,errno值爲11(EAGAIN)時,這個 並非發生了錯誤,而是數據已經被讀完,這個時候中止讀數據就行了。阻塞模式不會這樣,由於當沒有數據的時候,read就阻塞了。事件

    二、返回值n=0, errno = 0。說明客戶端已經關閉了。通過測試即便沒有數據的時候,阻塞的read就阻塞,非阻塞的read時爲第一種狀況。只有客戶端關閉時返回0(socket fd是這樣其餘io流爲測試) 。這個時候就就不要再註冊事件了,從隊列中刪除這個fd吧。並且當客戶端斷開時,epoll會主動通知一個EPOLLIN事件。get

    三、返回值n=-1,errno != EAGAIN;時是錯了,看一下錯誤碼是多吧。string

關於write的返回值:it

    一、返回值n=-1,errno = EAGAIN時; 說明系統緩衝區已經滿了,不能再寫進去了。當write爲阻塞時不會返回這個錯誤,會阻塞掉。

    二、返回值n<=0時;客戶端斷開鏈接或出錯。

什麼時候系統會通知一個寫事件:

    對於LT 模式,若是註冊了EPOLLOUT,只要該socket可寫(發送緩衝區)未滿,那麼就會觸發EPOLLOUT。

    對於ET模式,若是註冊了EPOLLOUT,只有當socket從不可寫變爲可寫的時候,會觸發EPOLLOUT。

    上面server端的例子,當你要寫入的數據比較大的時候,可能就會有困擾了,如何寫入的數據量較大,把系統緩衝區寫滿了,若是write設置是阻塞的,那就會阻塞了。這可能不是咱們想要的。

    這個時候就須要註冊寫事件,本身在應用層加個發送緩衝區,須要發送數據的時候,就寫到應用層的發送緩衝區。觸發write事件的時候,從緩衝區寫入socket。

相關文章
相關標籤/搜索