环境
ubuntu20
GCC10.2
Python3.8
问题描述
- 运行client程序,通过TCP连接到server上
- client向server发送用户输入,server将输入按原样返回。
- 当用户键入‘q’时,client主动断开连接。
- 当用户键入‘quit’时,server主动断开连接。
- 程序平稳运行。
服务器代码
内含部分个人封装,接口规范参考python中的select和socket模块
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <string.h>
#include <iostream>
#include <map>
#include "epoll.h"
#include "socket.h"
using namespace std;
/*
基于epoll的TCP回射程序
*/
int main()
{
// initialize tcp socket
int val = true, ret, port = 9090;
class socket server(AF_INET, SOCK_STREAM, 0);
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1);
server.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1);
server.bind("127.0.0.1", 9090);
server.listen(1024);
cout << "server listen on: " << hex << INADDR_LOOPBACK << endl;
// initialize epoll fd
auto e = epoll();
e.mount(server, e.default_mask());
//
map<int,class socket> connections;
//
volatile bool running = true;
while(running) {
auto [size, ar] = e.poll(1000);
for(int i=0; i < size; ++i) {
if(ar[i].fileno() == server.fileno()) {
switch (ar[i].mask()) {
case EPOLL_EVENTS::EPOLLIN:
{
auto sock = server.accept();
e.mount(sock.fileno(), e.default_mask() | EPOLL_EVENTS::EPOLLRDHUP | EPOLL_EVENTS::EPOLLHUP);
connections.emplace(sock.fileno(), std::move(sock));
}
break;
default:
cout << "server incoming can't match any EPOLL flag" << endl;
break;
}
}else {
auto &a = ar[i];
auto iter = connections.find(a.fileno());
cout.flush() << "mask:" << hex << a.mask() << " ";
if(a.mask() & (EPOLL_EVENTS::EPOLLRDHUP | EPOLL_EVENTS::EPOLLHUP)){
cout << hex << "close " << a.mask() << endl;
connections.erase(iter);
}else if(a.mask() & EPOLL_EVENTS::EPOLLIN){
auto data = iter->second.recv();
string str(data.begin(), data.end());
if(str == "quit") {
connections.erase(iter);
e.umount(a.fileno());
}else{
cout << "received data:" << str << endl;
iter->second.send(data);
}
}
}
}
}
// close server
e.umount(server);
}
客户端代码
python实现
import time, timeit
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)
client.connect(('127.0.0.1', 9090))
data = input("\033[;32msend(q to quit)> \033[;0m")
if data == 'q':
client.close()
else:
while len(data) > 0:
client.send(data.encode('utf8'))
data = client.recv(1024).decode('utf8')
if len(data) == 0:
break
print('\033[;34mecho> \033[;0m', data)
data = input('\033[;32msend> \033[;0m')
if data == 'q':
client.close()
break
系统API特点
EPOLLRDHUP仅在对端关闭写接口后触发。
EPOLLHUP无论是主动调用close还是对端调用,都不触发,很纳闷,我的系统是ubuntu20。
封装一览
epoll.h
#ifndef EPOLL_H
#define EPOLL_H
extern "C" {
#include <sys/epoll.h>
}
#include <vector>
#include <tuple>
# define MAX_EVENTS 512 // 一般在编译时就能确定该机器上最合适的MAX_EVENTS,所以此处直接定义为宏,且代码中不再更改
class socket;
class epoll
{
public:
class event : protected epoll_event
{
public:
event(struct epoll_event&& ev={0, {0}}):epoll_event(ev){};
inline int fileno()const { return data.fd; }
inline int mask()const { return events; }
private:
};
epoll(int size=1024);
epoll(const epoll&)=delete;
epoll(epoll&&);
virtual~epoll();
inline operator int()const { return fd; }
inline int fileno()const { return int(*this); }
void mount(int fd, int event_mask);
void umount(int fd);
void modify(int fd, int event_mask);
std::tuple<size_t,event*> poll(int timeout_msec=-1);
static epoll fromfd(int &&fd);
inline uint32_t default_mask()const {
return EPOLL_EVENTS::EPOLLIN | EPOLL_EVENTS::EPOLLOUT | EPOLL_EVENTS::EPOLLET | EPOLL_EVENTS::EPOLLERR;
}
// convenience function
void mount(const class socket& sock, int event_mask);
void umount(const class socket& sock);
void modify(const class socket& sock, int event_mask);
private:
int fd;
event events[MAX_EVENTS];
struct epoll_event ev;
};
#endif // EPOLL_H
socket.h
#ifndef SOCKET_H
#define SOCKET_H
extern "C" {
#include <sys/socket.h>
#include <string.h>
}
#include <string>
#include <vector>
#include <system_error>
class socket
{
public:
socket(int fd=-1);
socket(int family, int type, int protocol);
socket(const socket&)=delete;
socket(socket&&);
virtual~socket();
inline int fileno()const { return fd; }
template<typename _T=int> inline void setsockopt(int level, int optname, const _T& val) {
int ret;
ret = ::setsockopt(fd, level, optname, static_cast<const void*>(&val), sizeof(val));
if(ret == -1) throw std::system_error(errno,
std::system_category(),
std::string("SetSockOpt Failure: ") + strerror(errno));
}
template<typename _T=int> inline _T getsockopt(int level, int optname) {
int ret;
_T result;
ret = ::getsockopt(fd, level, optname, static_cast<void*>(&result), sizeof(result));
if(ret == -1) throw std::system_error(errno,
std::system_category(),
std::string("GetSockOpt Failure: ") + strerror(errno));
return result;
}
void bind(const std::string& host, uint16_t port);
void listen(int backlog=1024);
void connect(const std::string& host, uint16_t port);
socket accept();
int send(const void *buffer, int size, int flag=0);
int recv(void *buffer, int size, int flag=0);
// convenience function
inline int send(const std::vector<uint8_t>& buffer) {
return this->send(static_cast<const void*>(buffer.data()), buffer.size());
}
inline int recv(std::vector<uint8_t>&buffer) {
return this->recv(static_cast<void*>(buffer.data()), buffer.size());
}
std::vector<uint8_t> recv(int size=-1);
private:
int fd;
};
#endif // SOCKET_H