|
Boost Users : |
Subject: [Boost-users] Boost.Asio server application close get "This application has requested the Runtime to terminate it in an unusual way."
From: l.jay Yuan (pass86_at_[hidden])
Date: 2010-03-06 03:53:19
Boost.Asio server application close get "This application has
requested the Runtime to terminate it in an unusual way."
My code is too mush!
std.hpp:
/*
** author: ylj
** create: 2009/08/27
*/
#ifndef STD_HPP
#define STD_HPP
#include <algorithm>
#include <bitset>
#include <complex>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <iterator>
#include <limits>
#include <list>
#include <locale>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <strstream>
#include <utility>
#include <vector>
#include <cassert>
#include <cctype>
#include <cerrno>
#include <cfloat>
#include <climits>
#include <clocale>
#include <cmath>
#include <csetjmp>
#include <csignal>
#include <cstdarg>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cwchar>
#include <cwctype>
#endif // STD_HPP
tcontainer.hpp:
/*
** author: ylj
** create: 2010/02/10
*/
#ifndef TCONTAINER_HPP
#define TCONTAINER_HPP
#include <deque>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <vector>
#if 0
#include <boost/pool/pool_alloc.hpp>
template<typename T>
class tvector : public std::vector<T, boost::pool_allocator<T> >
{
};
template<typename T>
class tdeque: public std::deque<T, boost::pool_allocator<T> >
{
};
template<typename T>
class tlist: public std::list<T, boost::fast_pool_allocator<T> >
{
};
template<typename T>
class tstack: public std::stack<T, tdeque<T> >
{
};
template<typename T>
class tqueue: public std::queue<T, tdeque<T> >
{
};
template<typename T>
class tpriority_queue: public std::priority_queue<T, tdeque<T> > /*
NOTE: use tvector will crash if compiled by gcc, why? */
{
};
template<typename T>
class tset: public std::set<T, std::less<T>, boost::pool_allocator<T> >
{
};
template<typename T>
class tmultiset: public std::multiset<T, std::less<T>,
boost::pool_allocator<T> >
{
};
template<typename K, typename V>
class tmap: public std::map<K, V, std::less<K>,
boost::pool_allocator<std::pair<K, V> > >
{
};
template<typename K, typename V>
class tmultimap: public std::multimap<K, V, std::less<K>,
boost::pool_allocator<std::pair<K, V> > >
{
};
#else
template<typename T>
class tvector : public std::vector<T>
{
};
template<typename T>
class tdeque: public std::deque<T>
{
};
template<typename T>
class tlist: public std::list<T>
{
};
template<typename T>
class tstack: public std::stack<T, tdeque<T> >
{
};
template<typename T>
class tqueue: public std::queue<T, tdeque<T> >
{
};
template<typename T>
class tpriority_queue: public std::priority_queue<T>
{
};
template<typename T>
class tset: public std::set<T>
{
};
template<typename T>
class tmultiset: public std::multiset<T>
{
};
template<typename K, typename V>
class tmap: public std::map<K, V>
{
};
template<typename K, typename V>
class tmultimap: public std::multimap<K, V>
{
};
#endif
#endif // TCONTAINER_HPP
tstring.hpp:
/*
** author: ylj
** create: 2010/02/20
*/
#ifndef TSTRING_HPP
#define TSTRING_HPP
#include <string>
#include <sstream>
#include <boost/pool/pool_alloc.hpp>
typedef std::basic_string<char, std::char_traits<char>,
boost::pool_allocator<char> > tstring;
typedef std::basic_string<wchar_t, std::char_traits<wchar_t>,
boost::pool_allocator<wchar_t> > twstring;
typedef std::basic_stringstream<char, std::char_traits<char>,
boost::pool_allocator<char> > tstringstream;
typedef std::basic_stringstream<wchar_t, std::char_traits<wchar_t>,
boost::pool_allocator<wchar_t> > twstringstream;
typedef std::basic_istringstream<char, std::char_traits<char>,
boost::pool_allocator<char> > tistringstream;
typedef std::basic_istringstream<wchar_t, std::char_traits<wchar_t>,
boost::pool_allocator<wchar_t> > twistringstream;
typedef std::basic_ostringstream<char, std::char_traits<char>,
boost::pool_allocator<char> > tostringstream;
typedef std::basic_ostringstream<wchar_t, std::char_traits<wchar_t>,
boost::pool_allocator<wchar_t> > twostringstream;
typedef std::basic_stringbuf<char, std::char_traits<char>,
boost::pool_allocator<char> > tstringbuf;
typedef std::basic_stringbuf<wchar_t, std::char_traits<wchar_t>,
boost::pool_allocator<wchar_t> > twstringbuf;
#endif // TSTRING_HPP
tcp_packet.h:
/*
** author: ylj
** create: 2010/03/01
*/
#ifndef TCP_PACKET_H_
#define TCP_PACKET_H_
#define TCP_PACKET_HEARTBEAT_COMMAND -1
#include "std.hpp"
class tcp_packet
{
public:
typedef struct header {
unsigned short length;
int command;
} header;
enum { header_length = sizeof(header), max_body_length = 1024 * 4 };
public:
tcp_packet() : body_length_(0), command_(0) {}
inline const char * data() const { return data_; }
inline char * data() { return data_; }
inline size_t length() const { return header_length + body_length_; }
inline int command() const { return command_; }
inline void command(int cmd) { command_ = cmd; }
inline const char * body() const { return data_ + header_length; }
inline char * body() { return data_ + header_length; }
inline size_t body_length() const{ return body_length_; }
inline void body_length(size_t length) {
body_length_ = length;
if (body_length_ > max_body_length)
body_length_ = max_body_length;
}
inline bool decode_header() {
using namespace std;
header hd;
memcpy(&hd, data_, header_length);
body_length_ = hd.length;
command_ = hd.command;
if (body_length_ > max_body_length) {
body_length_ = 0;
command_ = 0;
return false;
}
return true;
}
inline void encode_header() {
using namespace std;
header hd;
hd.length = body_length_;
hd.command = command_;
memcpy(data_, &hd, header_length);
}
private:
char data_[header_length + max_body_length];
size_t body_length_;
int command_;
};
#endif // TCP_PACKET_H_
net_command.h:
/*
** author: ylj
** create: 2010/03/05
*/
#ifndef NET_COMMAND_H_
#define NET_COMMAND_H_
#define NET_CMD_GAME_CLIENT_TO_GAME_SERVER_START 1000
#define NET_CMD_GAME_SERVER_TO_GAME_CLIENT_START 2000
#define NET_CMD_LOBBY_CLIENT_TO_LOBBY_SERVER_START 3000
#define NET_CMD_LOBBY_SERVER_TO_LOBBY_CLIENT_START 4000
#endif // NET_COMMAND_H_
net_command_game_client_to_game_server.h:
/*
** author: ylj
** create: 2010/03/01
*/
#ifndef NET_COMMAND_GAME_CLIENT_TO_GAME_SERVER_H_
#define NET_COMMAND_GAME_CLIENT_TO_GAME_SERVER_H_
#include "net_command.h"
enum NET_CMD_GC_TO_GS {
NET_CMD_GC_TO_GS_START = NET_CMD_GAME_CLIENT_TO_GAME_SERVER_START,
NET_CMD_GC_TO_GS_TEST
};
typedef struct CMD_GC_TO_GS_TEST {
unsigned short x;
unsigned short y;
} CMD_GC_TO_GS_TEST;
#endif // NET_COMMAND_GAME_CLIENT_TO_GAME_SERVER_H_
net_command_game_server_to_game_client.h:
/*
** author: ylj
** create: 2010/03/01
*/
#ifndef NET_COMMAND_GAME_SERVER_TO_GAME_CLIENT_H_
#define NET_COMMAND_GAME_SERVER_TO_GAME_CLIENT_H_
#include "net_command.h"
enum NET_CMD_GS_TO_GC {
NET_CMD_GS_TO_GC_START = NET_CMD_GAME_SERVER_TO_GAME_CLIENT_START,
NET_CMD_GS_TO_GC_TEST,
};
typedef struct CMD_GS_TO_GC_TEST {
unsigned int id;
unsigned short x;
unsigned short y;
} CMD_GS_TO_GC_TEST;
#endif // NET_COMMAND_GAME_SERVER_TO_GAME_CLIENT_H_
tcp_server.h:
/*
** author: ylj
** create: 2010/02/26
*/
#ifndef TCP_SERVER_H_
#define TCP_SERVER_H_
#include "tcp_session.h"
#include "tcontainer.hpp"
#include <boost/asio.hpp>
class tcp_server_club;
class tcp_server
{
public:
tcp_server(boost::asio::io_service &io_service,
const boost::asio::ip::tcp::endpoint &endpoint,
tcp_server_club &club,
int max_session_count);
virtual ~tcp_server();
tcp_session * take_session();
void release_session(int session_id);
void accept();
int active_count() const;
void close();
private:
void handle_accept(tcp_session *session, const
boost::system::error_code &error);
void do_accept();
void do_close();
private:
typedef tdeque<tcp_session *> tcp_session_container;
typedef tmap<int, tcp_session *> tcp_session_map;
tcp_session_container idle_sessions_;
tcp_session_map active_sessions_;
boost::asio::io_service &io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
tcp_server_club &club_;
int max_session_count_;
};
#endif // TCP_SERVER_H_
tcp_server.cpp:
/*
** author: ylj
** create: 2010/02/26
*/
#include "tcp_server.h"
#include "tcp_session.h"
#include "tcp_server_club.h"
#include <boost/bind.hpp>
tcp_server::tcp_server(boost::asio::io_service &io_service,
const boost::asio::ip::tcp::endpoint &endpoint,
tcp_server_club &club,
int max_session_count)
: io_service_(io_service),
acceptor_(io_service, endpoint), club_(club)
{
max_session_count_ = max_session_count;
for (int i = 0; i < max_session_count; ++i) {
tcp_session *session(new tcp_session(io_service_, club_, this, i));
idle_sessions_.push_back(session);
}
}
tcp_server::~tcp_server()
{
tcp_session_container::iterator it = idle_sessions_.begin();
for ( ; it != idle_sessions_.end(); ++it)
delete *it;
tcp_session_map::iterator ufo = active_sessions_.begin();
for ( ; ufo != active_sessions_.end(); ++ufo)
delete ufo->second;
assert(max_session_count_ == (int)idle_sessions_.size() +
(int)active_sessions_.size());
}
tcp_session * tcp_server::take_session()
{
std::cout << "tcp_server::take_session" << std::endl;
if (idle_sessions_.empty())
return NULL;
tcp_session *ret = idle_sessions_.front();
idle_sessions_.pop_front();
active_sessions_.insert(std::make_pair(ret->id(), ret));
return ret;
}
void tcp_server::release_session(int session_id)
{
std::cout << "tcp_server::release_session" << std::endl;
tcp_session_map::iterator it = active_sessions_.find(session_id);
if (it == active_sessions_.end())
return;
tcp_session *session = it->second;
club_.leave(session);
session->socket().close();
idle_sessions_.push_front(session);
active_sessions_.erase(it);
/* can accept again */
if (idle_sessions_.size() == 1 && acceptor_.is_open()) {
do_accept();
}
}
void tcp_server::accept()
{
std::cout << "tcp_server::accept" << std::endl;
io_service_.post(boost::bind(&tcp_server::do_accept, this));
}
int tcp_server::active_count() const
{
return static_cast<int>(active_sessions_.size());
}
void tcp_server::close()
{
std::cout << "tcp_server::close" << std::endl;
io_service_.post(boost::bind(&tcp_server::do_close, this));
}
void tcp_server::handle_accept(tcp_session *session, const
boost::system::error_code &error)
{
std::cout << "tcp_server::handle_accept" << std::endl;
if (!error) {
club_.join(session);
session->start();
do_accept();
}
else {
std::cout << "tcp_server::handle_accept error" << std::endl;
session->socket().close();
}
}
void tcp_server::do_accept()
{
std::cout << "tcp_server::do_accept" << std::endl;
tcp_session *new_session = take_session();
if (new_session)
acceptor_.async_accept(new_session->socket(),
boost::bind(&tcp_server::handle_accept,
this, new_session, boost::asio::placeholders::error));
}
void tcp_server::do_close()
{
std::cout << "tcp_server::do_close" << std::endl;
acceptor_.close();
tcp_session_map all_active = active_sessions_;
tcp_session_map::iterator it = all_active.begin();
for ( ; it != all_active.end(); ++it) {
tcp_session *session = it->second;
session->socket().close();
}
}
tcp_session.h:
/*
** author: ylj
** create: 2010/02/26
*/
#ifndef TCP_SESSION_H_
#define TCP_SESSION_H_
#include "tcp_packet.h"
#include "tcp_server_member.h"
#include "tcontainer.hpp"
#include <boost/asio.hpp>
class tcp_server;
class tcp_server_club;
class tcp_session : public tcp_server_member
{
public:
tcp_session(boost::asio::io_service &io_service, tcp_server_club
&club, tcp_server *parent, int id);
~tcp_session();
boost::asio::ip::tcp::socket & socket();
void start();
void send(const tcp_packet &pkt);
virtual int id() const;
virtual tstring address() const;
virtual unsigned short port() const;
virtual void send(int command, void *data, int size);
virtual void close();
private:
void handle_read_header(const boost::system::error_code &error);
void handle_read_body(const boost::system::error_code &error);
void handle_write(const boost::system::error_code &error);
private:
typedef tdeque<tcp_packet> tcp_packet_queue;
boost::asio::ip::tcp::socket socket_;
tcp_server_club &club_;
tcp_packet read_packet_;
tcp_packet_queue write_packets_;
tcp_server *parent_;
int id_;
};
#endif // TCP_SESSION_H_
tcp_session.cpp:
/*
** author: ylj
** create: 2010/03/01
*/
#include "tcp_session.h"
#include "tcp_server.h"
#include "tcp_server_club.h"
#include <boost/bind.hpp>
tcp_session::tcp_session(boost::asio::io_service &io_service,
tcp_server_club &club,
tcp_server *parent,
int id)
: socket_(io_service),
club_(club),
parent_(parent),
id_(id)
{
}
tcp_session::~tcp_session()
{
}
boost::asio::ip::tcp::socket & tcp_session::socket()
{
return socket_;
}
void tcp_session::start()
{
boost::asio::async_read(socket_,
boost::asio::buffer(read_packet_.data(),
tcp_packet::header_length),
boost::bind(&tcp_session::handle_read_header, this,
boost::asio::placeholders::error));
}
void tcp_session::send(const tcp_packet &pkt)
{
bool write_in_progress = !write_packets_.empty();
write_packets_.push_back(pkt);
if (!write_in_progress) {
boost::asio::async_write(socket_,
boost::asio::buffer(write_packets_.front().data(),
write_packets_.front().length()),
boost::bind(&tcp_session::handle_write, this,
boost::asio::placeholders::error));
}
}
int tcp_session::id() const
{
return id_;
}
tstring tcp_session::address() const
{
return socket_.remote_endpoint().address().to_string().c_str();
}
unsigned short tcp_session::port() const
{
return socket_.remote_endpoint().port();
}
void tcp_session::send(int command, void *data, int size)
{
assert(tcp_packet::max_body_length >= size);
tcp_packet pkt;
pkt.command(command);
pkt.body_length(size);
memcpy(pkt.body(), data, pkt.body_length());
pkt.encode_header();
this->send(pkt);
}
void tcp_session::close()
{
socket_.close();
}
void tcp_session::handle_read_header(const boost::system::error_code &error)
{
if (!error && read_packet_.decode_header()) {
boost::asio::async_read(socket_,
boost::asio::buffer(read_packet_.body(), read_packet_.body_length()),
boost::bind(&tcp_session::handle_read_body, this,
boost::asio::placeholders::error));
}
else {
parent_->release_session(id_);
}
}
void tcp_session::handle_read_body(const boost::system::error_code &error)
{
if (!error) {
if (read_packet_.command() != TCP_PACKET_HEARTBEAT_COMMAND)
club_.receive(this, read_packet_.command(),
read_packet_.body(), read_packet_.body_length());
boost::asio::async_read(socket_,
boost::asio::buffer(read_packet_.data(), tcp_packet::header_length),
boost::bind(&tcp_session::handle_read_header, this,
boost::asio::placeholders::error));
}
else {
parent_->release_session(id_);
}
}
void tcp_session::handle_write(const boost::system::error_code &error)
{
if (!error) {
write_packets_.pop_front();
if (!write_packets_.empty()) {
boost::asio::async_write(socket_,
boost::asio::buffer(write_packets_.front().data(),
write_packets_.front().length()),
boost::bind(&tcp_session::handle_write, this,
boost::asio::placeholders::error));
}
}
else {
parent_->release_session(id_);
}
}
tcp_server_member.h:
/*
** author: ylj
** create: 2010/03/01
*/
#ifndef TCP_SERVER_MEMBER_H_
#define TCP_SERVER_MEMBER_H_
#include "tstring.hpp"
class tcp_server_member
{
public:
virtual ~tcp_server_member() {}
virtual int id() const = 0;
virtual tstring address() const = 0;
virtual unsigned short port() const = 0;
virtual void send(int command, void *data, int size) = 0;
virtual void close() = 0;
};
#endif // TCP_SERVER_MEMBER_H_
tcp_server_club.h:
/*
** author: ylj
** create: 2010/03/01
*/
#ifndef TCP_SERVER_CLUB_H_
#define TCP_SERVER_CLUB_H_
#include "tcontainer.hpp"
#include <boost/function.hpp>
class tcp_server_member;
class tcp_server_club
{
public:
virtual ~tcp_server_club();
void receive(tcp_server_member *member, int command, void *data, int size);
template <typename ReceiveHandler>
void bind_receive_handler(int command, ReceiveHandler handler);
virtual void join(tcp_server_member *member);
virtual void leave(tcp_server_member *member);
private:
typedef boost::function<void (tcp_server_member *, void *, int)>
receive_handler;
typedef tmap<int, receive_handler> receive_handler_map;
receive_handler_map receive_handlers_;
};
template <typename ReceiveHandler>
void tcp_server_club::bind_receive_handler(int command, ReceiveHandler
handler) {
receive_handlers_.insert(std::make_pair(command, handler));
}
#endif // TCP_SERVER_CLUB_H_
tcp_server_club.cpp:
/*
** author: ylj
** create: 2010/03/01
*/
#include "tcp_server_club.h"
#include "tcp_server_member.h"
#include "std.hpp"
tcp_server_club::~tcp_server_club()
{
}
void tcp_server_club::receive(tcp_server_member *member, int command,
void *data, int size)
{
receive_handler_map::iterator it = receive_handlers_.find(command);
if (it != receive_handlers_.end()) {
receive_handler handler = it->second;
handler(member, data, size);
}
else {
std::cout << "unhandle session(" << member->id() << ")'s
command: " << command << std::endl;
}
}
void tcp_server_club::join(tcp_server_member *member)
{
}
void tcp_server_club::leave(tcp_server_member *member)
{
}
game_server_club.h:
/*
** author: ylj
** create: 2010/03/01
*/
#ifndef GAME_SERVER_CLUB_H_
#define GAME_SERVER_CLUB_H_
#include "tcp_server_club.h"
class game_server_club : public tcp_server_club
{
public:
game_server_club();
virtual void join(tcp_server_member *member);
virtual void leave(tcp_server_member *member);
void ON_NET_CMD_GC_TO_GS_TEST(tcp_server_member *member, void
*data, int size);
};
#endif // GAME_SERVER_CLUB_H_
game_server_club.cpp:
/*
** author: ylj
** create: 2010/03/01
*/
#include "game_server_club.h"
#include "tcp_server_member.h"
#include "net_command_game_server_to_game_client.h"
#include "net_command_game_client_to_game_server.h"
#include "std.hpp"
#include <boost/bind.hpp>
game_server_club::game_server_club()
{
bind_receive_handler(NET_CMD_GC_TO_GS_TEST,
boost::bind(&game_server_club::ON_NET_CMD_GC_TO_GS_TEST, this, _1, _2,
_3));
}
void game_server_club::join(tcp_server_member *member)
{
std::cout << "id:" << member->id() << " join(" <<
member->address() << ":" << member->port() << ")" << std::endl;
}
void game_server_club::leave(tcp_server_member *member)
{
std::cout << "id:" << member->id() << " leave(" <<
member->address() << ":" << member->port() << ")" << std::endl;
}
void game_server_club::ON_NET_CMD_GC_TO_GS_TEST(tcp_server_member
*member, void *data, int size)
{
CMD_GC_TO_GS_TEST *rdata = (CMD_GC_TO_GS_TEST *)data;
CMD_GS_TO_GC_TEST sdata;
sdata.x = rdata->x;
sdata.y = rdata->y;
sdata.id= member->id();
std::cout << "id:" << sdata.id << " ON_NET_CMD_GC_TO_GS_TEST " <<
rdata->x << " " << rdata->y << std::endl;
member->send(NET_CMD_GS_TO_GC_TEST, &sdata, sizeof(sdata));
//member->close();
}
main.cpp:
/*
** author: ylj
** create: 2010/03/01
*/
#include "std.hpp"
#include "tcp_server.h"
#include "game_server_club.h"
#include "tstring.hpp"
#include <boost/thread.hpp>
using namespace std;
#ifdef _MSC_VER
#include <dbghelp.h>
#include <stdio.h>
#include <crtdbg.h>
#include <tchar.h>
#include <windows.h>
#pragma comment ( lib, "dbghelp.lib" )
///////////////////////////////////////////////////////////////////////////////
// Minidump creation function
//
void CreateMiniDump( EXCEPTION_POINTERS* pep )
{
// Open the file
HANDLE hFile = CreateFile( _T("MaxiDump.dmp"), GENERIC_READ | GENERIC_WRITE,
0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
if( ( hFile != NULL ) && ( hFile != INVALID_HANDLE_VALUE ) )
{
// Create the minidump
MINIDUMP_EXCEPTION_INFORMATION mdei;
mdei.ThreadId = GetCurrentThreadId();
mdei.ExceptionPointers = pep;
mdei.ClientPointers = FALSE;
MINIDUMP_TYPE mdt = (MINIDUMP_TYPE)(MiniDumpWithFullMemory |
MiniDumpWithFullMemoryInfo |
MiniDumpWithHandleData |
MiniDumpWithThreadInfo |
MiniDumpWithUnloadedModules );
BOOL rv = MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(),
hFile, mdt, (pep != 0) ? &mdei : 0, 0, 0 );
if( !rv )
_tprintf( _T("MiniDumpWriteDump failed. Error: %u \n"), GetLastError() );
else
_tprintf( _T("Minidump created.\n") );
// Close the file
CloseHandle( hFile );
}
else
{
_tprintf( _T("CreateFile failed. Error: %u \n"), GetLastError() );
}
}
#endif // _MSC_VER
void do_work(int argc, const char *argv[])
{
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <port>\n";
return;
}
boost::asio::io_service io_service;
boost::asio::ip::tcp::endpoint
endpoint(boost::asio::ip::tcp::v4(), atoi(argv[1]));
game_server_club game_club;
tcp_server game_server(io_service, endpoint, game_club, 2);
game_server.accept();
boost::thread io_thread(boost::bind(&boost::asio::io_service::run,
&io_service));
tstring line;
cout << "? for help" << endl;
while(getline(cin, line)) {
if (line == "exit") {
game_server.close();
break;
}
else if (line == "?") {
cout << "exit ->close server" << endl;
cout << "? ->help" << endl;
}
}
io_thread.join();
}
int main(int argc, const char *argv[])
{
#ifdef _MSC_VER
__try
{
#endif // _MSC_VER
do_work(argc, argv);
#ifdef _MSC_VER
}
__except( CreateMiniDump( GetExceptionInformation() ),
EXCEPTION_EXECUTE_HANDLER )
{
}
#endif // _MSC_VER
return 0;
}
If anyone conneted server and not close, I type "exit" to close server
then got "This application has requested the Runtime to terminate it
in an unusual way."
I have check and check and check.
I cannot find out why.
Anyone can help me?
Thank you first.
Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net