代码做的是回射服务
客户端:
#include <errno.h>
#include <netdb.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
const int BUFFSIZ = 4096;
int connect(const char * host, const char * serv)
{
int connfd;
int n;
struct addrinfo hints;
struct addrinfo * res;
struct addrinfo * ressave;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if (0 != (n = getaddrinfo(host, serv, &hints, &res))) {
printf("getaddrinfo error for %s, %s: %s\n",
(NULL == host) ? "(no hostname)" : host,
(NULL == serv) ? "(no servname)" : serv,
gai_strerror(n));
exit(1);
}
ressave = res;
while (NULL != res) {
connfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (-1 != connfd) {
if (0 == ::connect(connfd, res->ai_addr, res->ai_addrlen)) {
break;
}
else {
close(connfd);
}
}
res = res->ai_next;
}
freeaddrinfo(ressave);
if (NULL == res) {
printf("connect failed\n");
exit(1);
}
return(connfd);
}
int main(int argc, char ** argv)
{
if (3 != argc) {
printf("usage: %s <host> <serv>\n", argv[0]);
exit(1);
}
int connfd = connect(argv[1], argv[2]);
char buff[BUFFSIZ] = { 0 };
while (NULL != fgets(buff, BUFFSIZ, stdin)) {
int len = strlen(buff);
int offset = 0;
while (offset < len) {
int nw = write(connfd, buff + offset, len - offset);
if (-1 == nw && EINTR == errno) {
continue;
}
else if (nw <= 0) {
perror("write");
exit(1);
}
else {
offset += nw;
}
}
offset = 0;
while (len > 0) {
int nr = read(connfd, buff + offset, len);
if (-1 == nr) {
if (EINTR != errno) {
perror("read");
exit(1);
}
continue;
}
else if (0 == nr) {
printf("connection close by peer\n");
exit(1);
}
else {
len -= nr;
offset += nr;
}
}
if (-1 == fputs(buff, stdout)) {
perror("fputs");
break;
}
}
if (ferror(stdin)) {
perror("fgets");
}
close(connfd);
exit(0);
}
客户端用的是阻塞式IO,所以服务器关闭时不能在第一时间得知,解决方法有:1. 改成非阻塞式IO,见UNP第十六章;2. 加上心搏函数,见UNP第二十四章。
服务器:
LT模式的epoll实现:
#include <errno.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
const int LISTENQ = 1024;
const int EVENTSZ = 64;
const int BUFFSIZ = 4096;
int listen(const char * host, const char * serv)
{
int listenfd;
int n;
const int on = 1;
struct addrinfo hints;
struct addrinfo * res;
struct addrinfo * ressave;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if (0 != (n = getaddrinfo(host, serv, &hints, &res))) {
printf("getaddrinfo error for %s, %s: %s\n",
(NULL == host) ? "(no hostname)" : host,
(NULL == serv) ? "(no servname)" : serv,
gai_strerror(n));
exit(1);
}
ressave = res;
while (NULL != res) {
listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (-1 != listenfd) {
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (0 == bind(listenfd, res->ai_addr, res->ai_addrlen)) {
break;
}
else {
close(listenfd);
}
}
res = res->ai_next;
}
freeaddrinfo(ressave);
if (NULL == res) {
printf("listen failed\n");
exit(1);
}
if (-1 == ::listen(listenfd, LISTENQ)) {
printf("listen error: %s\n", strerror(errno));
exit(1);
}
return(listenfd);
}
bool setnonblock(int sockfd)
{
int flag = fcntl(sockfd, F_GETFL, 0);
if (-1 == flag) {
perror("fcntl(F_GETFL)");
return(false);
}
if (-1 == fcntl(sockfd, F_SETFL, flag | O_NONBLOCK)) {
perror("fcntl(F_SETFL)");
return(false);
}
return(true);
}
int main(int argc, char ** argv)
{
if (3 != argc) {
printf("usage: %s <host> <serv>\n", argv[0]);
exit(1);
}
struct epoll_event ev;
struct epoll_event events[EVENTSZ];
char buff[BUFFSIZ] = { 0 };
int epollfd = epoll_create(LISTENQ);
if (-1 == epollfd) {
perror("epoll_create");
exit(1);
}
int listenfd = listen(argv[1], argv[2]);
ev.data.fd = listenfd;
ev.events = EPOLLIN;
if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev)) {
perror("epoll_ctl(EPOLL_CTL_ADD)");
exit(1);
}
for ( ; ; ) {
int n = epoll_wait(epollfd, events, EVENTSZ, -1);
if (-1 == n) {
perror("epoll_wait");
break;
}
for (int i = 0; i < n; ++i) {
const int sockfd = events[i].data.fd;
if (sockfd == listenfd) {
int accefd = accept(listenfd, NULL, NULL);
if (-1 == accefd) {
perror("accept");
exit(1);
}
ev.data.fd = accefd;
ev.events = EPOLLIN;
if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, accefd, &ev)) {
perror("epoll_ctl(EPOLL_CTL_ADD)");
close(accefd);
}
printf("connect by new client\n");
}
else {
int nr = read(sockfd, buff, BUFFSIZ);
if (nr <= 0) {
if (-1 == nr) {
perror("read");
}
else {
printf("connection close by peer\n");
}
if (-1 == epoll_ctl(epollfd, EPOLL_CTL_DEL,
sockfd, &ev)) {
perror("epoll_ctl(EPOLL_CTL_DEL)");
}
close(sockfd);
continue;
}
int nw = 0;
while (nw < nr) {
int n = write(sockfd, buff + nw, nr - nw);
if (-1 == n && EWOULDBLOCK == errno) {
continue;
}
else if (n <= 0) {
break;
}
else {
nw += n;
}
}
if (nw < nr) {
if (-1 == epoll_ctl(epollfd, EPOLL_CTL_DEL,
sockfd, &ev)) {
perror("epoll_ctl(EPOLL_CTL_DEL)");
}
close(sockfd);
}
}
}
}
close(epollfd);
close(listenfd);
exit(0);
}
ET模式的epoll实现:
#include <errno.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
const int LISTENQ = 1024;
const int EVENTSZ = 64;
const int BUFFSIZ = 4096;
int listen(const char * host, const char * serv)
{
int listenfd;
int n;
const int on = 1;
struct addrinfo hints;
struct addrinfo * res;
struct addrinfo * ressave;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if (0 != (n = getaddrinfo(host, serv, &hints, &res))) {
printf("getaddrinfo error for %s, %s: %s\n",
(NULL == host) ? "(no hostname)" : host,
(NULL == serv) ? "(no servname)" : serv,
gai_strerror(n));
exit(1);
}
ressave = res;
while (NULL != res) {
listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (-1 != listenfd) {
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (0 == bind(listenfd, res->ai_addr, res->ai_addrlen)) {
break;
}
else {
close(listenfd);
}
}
res = res->ai_next;
}
freeaddrinfo(ressave);
if (NULL == res) {
printf("listen failed\n");
exit(1);
}
if (-1 == ::listen(listenfd, LISTENQ)) {
printf("listen error: %s\n", strerror(errno));
exit(1);
}
return(listenfd);
}
bool setnonblock(int sockfd)
{
int flag = fcntl(sockfd, F_GETFL, 0);
if (-1 == flag) {
perror("fcntl(F_GETFL)");
return(false);
}
if (-1 == fcntl(sockfd, F_SETFL, flag | O_NONBLOCK)) {
perror("fcntl(F_SETFL)");
return(false);
}
return(true);
}
int main(int argc, char ** argv)
{
if (3 != argc) {
printf("usage: %s <host> <serv>\n", argv[0]);
exit(1);
}
struct epoll_event ev;
struct epoll_event events[EVENTSZ];
char buff[BUFFSIZ] = { 0 };
int epollfd = epoll_create(LISTENQ);
if (-1 == epollfd) {
perror("epoll_create");
exit(1);
}
int listenfd = listen(argv[1], argv[2]);
if (!setnonblock(listenfd)) {
exit(1);
}
ev.data.fd = listenfd;
ev.events = EPOLLIN | EPOLLET;
if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev)) {
perror("epoll_ctl(EPOLL_CTL_ADD)");
exit(1);
}
for ( ; ; ) {
int n = epoll_wait(epollfd, events, EVENTSZ, -1);
if (-1 == n) {
perror("epoll_wait");
break;
}
for (int i = 0; i < n; ++i) {
const int sockfd = events[i].data.fd;
if (sockfd == listenfd) {
for ( ; ; ) {
int accefd = accept(listenfd, NULL, NULL);
if (-1 == accefd) {
if (EAGAIN == errno) {
break;
}
continue;
}
if (!setnonblock(accefd)) {
close(accefd);
continue;
}
ev.data.fd = accefd;
ev.events = EPOLLIN | EPOLLET;
if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, accefd, &ev)) {
perror("epoll_ctl(EPOLL_CTL_ADD)");
close(accefd);
}
printf("connect by new client\n");
}
}
else {
for ( ; ; ) {
int nr = read(sockfd, buff, BUFFSIZ);
if (-1 == nr && EAGAIN == errno) {
break;
}
if (0 == nr) {
printf("connection close by peer\n");
}
if (nr <= 0) {
if (-1 == epoll_ctl(epollfd, EPOLL_CTL_DEL,
sockfd, &ev)) {
perror("epoll_ctl(EPOLL_CTL_DEL)");
}
close(sockfd);
break;
}
int nw = 0;
while (nw < nr) {
int n = write(sockfd, buff + nw, nr - nw);
if (-1 == n && EWOULDBLOCK == errno) {
continue;
}
else if (n <= 0) {
break;
}
else {
nw += n;
}
}
if (nw < nr) {
if (-1 == epoll_ctl(epollfd, EPOLL_CTL_DEL,
sockfd, &ev)) {
perror("epoll_ctl(EPOLL_CTL_DEL)");
}
close(sockfd);
break;
}
}
}
}
}
close(epollfd);
close(listenfd);
exit(0);
}
ET模式的epoll实现,在write时处理得不好,因为sockfd是非阻塞的,所以当发送缓存不足时,write会一直返回-1(errno为EWOULDBLOCK),直到有足够的发送缓存。我估计要把EPOLLOUT加到sockfd的对应event中,但这样就得为每个sockfd分配一块独立的缓存,更重要的是,我们可能得反复调用epoll_ctl(EPOLL_CTL_MOD)去增 / 减EPOLLIN / EPOLLOUT,这会降低多少效率是最值得关心的。
当然对于回射服务器没必要这样做。