Stable multi-user buffer and re-enabled push support. Renamed DDV_->Mist. Closes #25
This commit is contained in:
parent
9ae274b0c1
commit
6c588e51fc
21 changed files with 613 additions and 382 deletions
|
@ -1,6 +1,6 @@
|
|||
SRC = main.cpp ../util/json.cpp ../util/socket.cpp ../util/dtsc.cpp ../util/tinythread.cpp
|
||||
SRC = main.cpp ../util/json.cpp ../util/socket.cpp ../util/dtsc.cpp ../util/tinythread.cpp user.cpp stats.cpp stream.cpp
|
||||
OBJ = $(SRC:.cpp=.o)
|
||||
OUT = DDV_Buffer
|
||||
OUT = MistBuffer
|
||||
INCLUDES =
|
||||
DEBUG = 4
|
||||
OPTIMIZE = -g
|
||||
|
@ -15,9 +15,9 @@ default: $(OUT)
|
|||
.cpp.o:
|
||||
$(CC) $(INCLUDES) $(CCFLAGS) $(LIBS) -c $< -o $@
|
||||
$(OUT): $(OBJ)
|
||||
$(CC) $(LIBS) -o $(OUT) $(OBJ)
|
||||
$(CC) $(LIBS) -o ../bin/$(OUT) $(OBJ)
|
||||
clean:
|
||||
rm -rf $(OBJ) $(OUT) Makefile.bak *~
|
||||
rm -rf $(OBJ) ../bin/$(OUT) Makefile.bak *~
|
||||
install: $(OUT)
|
||||
cp -f ./$(OUT) /usr/bin/
|
||||
cp -f ../bin/$(OUT) /usr/bin/
|
||||
|
||||
|
|
169
Buffer/main.cpp
169
Buffer/main.cpp
|
@ -12,27 +12,14 @@
|
|||
#include <signal.h>
|
||||
#include <sstream>
|
||||
#include <sys/time.h>
|
||||
#include "../util/dtsc.h" //DTSC support
|
||||
#include "../util/socket.h" //Socket lib
|
||||
#include "../util/json.h"
|
||||
#include "../util/tinythread.h"
|
||||
#include "stream.h"
|
||||
|
||||
/// Holds all code unique to the Buffer.
|
||||
namespace Buffer{
|
||||
|
||||
class user;//forward declaration
|
||||
JSON::Value Storage; ///< Global storage of data.
|
||||
DTSC::Stream * Strm = 0;
|
||||
std::string waiting_ip = ""; ///< IP address for media push.
|
||||
Socket::Connection ip_input; ///< Connection used for media push.
|
||||
tthread::mutex stats_mutex; ///< Mutex for stats modifications.
|
||||
tthread::mutex transfer_mutex; ///< Mutex for data transfers.
|
||||
tthread::mutex socket_mutex; ///< Mutex for user deletion/work.
|
||||
bool buffer_running = true; ///< Set to false when shutting down.
|
||||
std::vector<user> users; ///< All connected users.
|
||||
std::vector<user>::iterator usersIt; ///< Iterator for all connected users.
|
||||
std::string name; ///< Name for this buffer.
|
||||
tthread::condition_variable moreData; ///< Triggered when more data becomes available.
|
||||
volatile bool buffer_running = true; ///< Set to false when shutting down.
|
||||
Stream * thisStream = 0;
|
||||
Socket::Server SS; ///< The server socket.
|
||||
|
||||
/// Gets the current system time in milliseconds.
|
||||
unsigned int getNowMS(){
|
||||
|
@ -50,40 +37,18 @@ namespace Buffer{
|
|||
default: return; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "stats.cpp"
|
||||
#include "user.cpp"
|
||||
|
||||
namespace Buffer{
|
||||
void handleStats(void * empty){
|
||||
if (empty != 0){return;}
|
||||
Socket::Connection StatsSocket = Socket::Connection("/tmp/ddv_statistics", true);
|
||||
Socket::Connection StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
|
||||
while (buffer_running){
|
||||
usleep(1000000); //sleep one second
|
||||
unsigned int now = time(0);
|
||||
unsigned int tot_up = 0, tot_down = 0, tot_count = 0;
|
||||
stats_mutex.lock();
|
||||
if (users.size() > 0){
|
||||
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
|
||||
tot_down += usersIt->curr_down;
|
||||
tot_up += usersIt->curr_up;
|
||||
tot_count++;
|
||||
}
|
||||
}
|
||||
Storage["totals"]["down"] = tot_down;
|
||||
Storage["totals"]["up"] = tot_up;
|
||||
Storage["totals"]["count"] = tot_count;
|
||||
Storage["totals"]["now"] = now;
|
||||
Storage["totals"]["buffer"] = name;
|
||||
if (!StatsSocket.connected()){
|
||||
StatsSocket = Socket::Connection("/tmp/ddv_statistics", true);
|
||||
StatsSocket = Socket::Connection("/tmp/mist/statistics", true);
|
||||
}
|
||||
if (StatsSocket.connected()){
|
||||
StatsSocket.write(Storage.toString()+"\n\n");
|
||||
Storage["log"].null();
|
||||
StatsSocket.write(Stream::get()->getStats()+"\n\n");
|
||||
}
|
||||
stats_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,8 +56,8 @@ namespace Buffer{
|
|||
user * usr = (user*)v_usr;
|
||||
std::cerr << "Thread launched for user " << usr->MyStr << ", socket number " << usr->S.getSocket() << std::endl;
|
||||
|
||||
usr->myRing = Strm->getRing();
|
||||
if (!usr->S.write(Strm->outHeader())){
|
||||
usr->myRing = thisStream->getRing();
|
||||
if (!usr->S.write(thisStream->getHeader())){
|
||||
usr->Disconnect("failed to receive the header!");
|
||||
return;
|
||||
}
|
||||
|
@ -108,10 +73,9 @@ namespace Buffer{
|
|||
if (usr->inbuffer != ""){
|
||||
if (usr->inbuffer[0] == 'P'){
|
||||
std::cout << "Push attempt from IP " << usr->inbuffer.substr(2) << std::endl;
|
||||
if (usr->inbuffer.substr(2) == waiting_ip){
|
||||
if (!ip_input.connected()){
|
||||
if (thisStream->checkWaitingIP(usr->inbuffer.substr(2))){
|
||||
if (thisStream->setInput(usr->S)){
|
||||
std::cout << "Push accepted!" << std::endl;
|
||||
ip_input = usr->S;
|
||||
usr->S = Socket::Connection(-1);
|
||||
return;
|
||||
}else{
|
||||
|
@ -122,38 +86,23 @@ namespace Buffer{
|
|||
}
|
||||
}
|
||||
if (usr->inbuffer[0] == 'S'){
|
||||
stats_mutex.lock();
|
||||
usr->tmpStats = Stats(usr->inbuffer.substr(2));
|
||||
unsigned int secs = usr->tmpStats.conntime - usr->lastStats.conntime;
|
||||
if (secs < 1){secs = 1;}
|
||||
usr->curr_up = (usr->tmpStats.up - usr->lastStats.up) / secs;
|
||||
usr->curr_down = (usr->tmpStats.down - usr->lastStats.down) / secs;
|
||||
usr->lastStats = usr->tmpStats;
|
||||
Storage["curr"][usr->MyStr]["connector"] = usr->tmpStats.connector;
|
||||
Storage["curr"][usr->MyStr]["up"] = usr->tmpStats.up;
|
||||
Storage["curr"][usr->MyStr]["down"] = usr->tmpStats.down;
|
||||
Storage["curr"][usr->MyStr]["conntime"] = usr->tmpStats.conntime;
|
||||
Storage["curr"][usr->MyStr]["host"] = usr->tmpStats.host;
|
||||
Storage["curr"][usr->MyStr]["start"] = (unsigned int) time(0) - usr->tmpStats.conntime;
|
||||
stats_mutex.unlock();
|
||||
thisStream->saveStats(usr->MyStr, usr->tmpStats);
|
||||
}
|
||||
}
|
||||
}
|
||||
usr->Send();
|
||||
}
|
||||
stats_mutex.lock();
|
||||
if (users.size() > 0){
|
||||
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
|
||||
if (!(*usersIt).S.connected()){
|
||||
users.erase(usersIt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
stats_mutex.unlock();
|
||||
thisStream->cleanUsers();
|
||||
std::cerr << "User " << usr->MyStr << " disconnected, socket number " << usr->S.getSocket() << std::endl;
|
||||
}
|
||||
|
||||
/// Loop reading DTSC data from stdin and processing it at the correct speed.
|
||||
void handleStdin(void * empty){
|
||||
if (empty != 0){return;}
|
||||
unsigned int lastPacketTime = 0;//time in MS last packet was parsed
|
||||
|
@ -167,28 +116,49 @@ namespace Buffer{
|
|||
while (std::cin.good() && buffer_running){
|
||||
//slow down packet receiving to real-time
|
||||
now = getNowMS();
|
||||
if ((now - lastPacketTime > currPacketTime - prevPacketTime) || (currPacketTime <= prevPacketTime)){
|
||||
if ((now - lastPacketTime >= currPacketTime - prevPacketTime) || (currPacketTime <= prevPacketTime)){
|
||||
std::cin.read(charBuffer, 1024*10);
|
||||
charCount = std::cin.gcount();
|
||||
inBuffer.append(charBuffer, charCount);
|
||||
transfer_mutex.lock();
|
||||
if (Strm->parsePacket(inBuffer)){
|
||||
Strm->outPacket(0);
|
||||
thisStream->getWriteLock();
|
||||
if (thisStream->getStream()->parsePacket(inBuffer)){
|
||||
thisStream->getStream()->outPacket(0);
|
||||
lastPacketTime = now;
|
||||
prevPacketTime = currPacketTime;
|
||||
currPacketTime = Strm->getTime();
|
||||
moreData.notify_all();
|
||||
currPacketTime = thisStream->getStream()->getTime();
|
||||
}
|
||||
transfer_mutex.unlock();
|
||||
thisStream->dropWriteLock();
|
||||
}else{
|
||||
if (((currPacketTime - prevPacketTime) - (now - lastPacketTime)) > 1000){
|
||||
usleep(1000000);
|
||||
if (((currPacketTime - prevPacketTime) - (now - lastPacketTime)) > 999){
|
||||
usleep(999000);
|
||||
}else{
|
||||
usleep(((currPacketTime - prevPacketTime) - (now - lastPacketTime)) * 1000);
|
||||
usleep(((currPacketTime - prevPacketTime) - (now - lastPacketTime)) * 999);
|
||||
}
|
||||
}
|
||||
}
|
||||
buffer_running = false;
|
||||
SS.close();
|
||||
}
|
||||
|
||||
/// Loop reading DTSC data from an IP push address.
|
||||
/// No changes to the speed are made.
|
||||
void handlePushin(void * empty){
|
||||
if (empty != 0){return;}
|
||||
std::string inBuffer;
|
||||
while (buffer_running){
|
||||
if (thisStream->getIPInput().connected()){
|
||||
if (thisStream->getIPInput().iread(inBuffer)){
|
||||
thisStream->getWriteLock();
|
||||
if (thisStream->getStream()->parsePacket(inBuffer)){
|
||||
thisStream->getStream()->outPacket(0);
|
||||
}
|
||||
thisStream->dropWriteLock();
|
||||
}
|
||||
}else{
|
||||
usleep(1000000);
|
||||
}
|
||||
}
|
||||
SS.close();
|
||||
}
|
||||
|
||||
/// Starts a loop, waiting for connections to send data to.
|
||||
|
@ -206,62 +176,49 @@ namespace Buffer{
|
|||
std::cout << "usage: " << argv[0] << " streamName [awaiting_IP]" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
name = argv[1];
|
||||
std::string name = argv[1];
|
||||
bool ip_waiting = false;
|
||||
std::string waiting_ip;
|
||||
if (argc >= 4){
|
||||
waiting_ip += argv[2];
|
||||
ip_waiting = true;
|
||||
}
|
||||
std::string shared_socket = "/tmp/shared_socket_";
|
||||
shared_socket += name;
|
||||
|
||||
Socket::Server SS(shared_socket, false);
|
||||
Strm = new DTSC::Stream(5);
|
||||
SS = Socket::makeStream(name);
|
||||
thisStream = Stream::get();
|
||||
thisStream->setName(name);
|
||||
if (ip_waiting){
|
||||
thisStream->setWaitingIP(waiting_ip);
|
||||
}
|
||||
Socket::Connection incoming;
|
||||
Socket::Connection std_input(fileno(stdin));
|
||||
|
||||
Storage["log"].null();
|
||||
Storage["curr"].null();
|
||||
Storage["totals"].null();
|
||||
|
||||
//tthread::thread StatsThread = tthread::thread(handleStats, 0);
|
||||
tthread::thread StatsThread = tthread::thread(handleStats, 0);
|
||||
tthread::thread * StdinThread = 0;
|
||||
if (!ip_waiting){
|
||||
StdinThread = new tthread::thread(handleStdin, 0);
|
||||
}else{
|
||||
StdinThread = new tthread::thread(handlePushin, 0);
|
||||
}
|
||||
|
||||
while (buffer_running){
|
||||
while (buffer_running && SS.connected()){
|
||||
//check for new connections, accept them if there are any
|
||||
//starts a thread for every accepted connection
|
||||
incoming = SS.accept(false);
|
||||
if (incoming.connected()){
|
||||
stats_mutex.lock();
|
||||
users.push_back(incoming);
|
||||
user * usr_ptr = &(users.back());
|
||||
stats_mutex.unlock();
|
||||
user * usr_ptr = new user(incoming);
|
||||
thisStream->addUser(usr_ptr);
|
||||
usr_ptr->Thread = new tthread::thread(handleUser, (void *)usr_ptr);
|
||||
}
|
||||
}//main loop
|
||||
|
||||
// disconnect listener
|
||||
/// \todo Deal with EOF more nicely - doesn't send the end of the stream to all users!
|
||||
buffer_running = false;
|
||||
std::cout << "Buffer shutting down" << std::endl;
|
||||
std::cout << "End of input file - buffer shutting down" << std::endl;
|
||||
SS.close();
|
||||
//StatsThread.join();
|
||||
if (StdinThread){StdinThread->join();}
|
||||
|
||||
if (users.size() > 0){
|
||||
stats_mutex.lock();
|
||||
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
|
||||
if ((*usersIt).S.connected()){
|
||||
(*usersIt).Disconnect("Terminating...");
|
||||
}
|
||||
}
|
||||
stats_mutex.unlock();
|
||||
}
|
||||
|
||||
delete Strm;
|
||||
StatsThread.join();
|
||||
StdinThread->join();
|
||||
delete thisStream;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,40 +1,32 @@
|
|||
#include "stats.h"
|
||||
#include <stdlib.h> //for atoi()
|
||||
|
||||
namespace Buffer{
|
||||
/// Converts a stats line to up, down, host, connector and conntime values.
|
||||
class Stats{
|
||||
public:
|
||||
unsigned int up;
|
||||
unsigned int down;
|
||||
std::string host;
|
||||
std::string connector;
|
||||
unsigned int conntime;
|
||||
Stats(){
|
||||
up = 0;
|
||||
down = 0;
|
||||
conntime = 0;
|
||||
}
|
||||
Stats(std::string s){
|
||||
size_t f = s.find(' ');
|
||||
if (f != std::string::npos){
|
||||
host = s.substr(0, f);
|
||||
s.erase(0, f+1);
|
||||
}
|
||||
f = s.find(' ');
|
||||
if (f != std::string::npos){
|
||||
connector = s.substr(0, f);
|
||||
s.erase(0, f+1);
|
||||
}
|
||||
f = s.find(' ');
|
||||
if (f != std::string::npos){
|
||||
conntime = atoi(s.substr(0, f).c_str());
|
||||
s.erase(0, f+1);
|
||||
}
|
||||
f = s.find(' ');
|
||||
if (f != std::string::npos){
|
||||
up = atoi(s.substr(0, f).c_str());
|
||||
s.erase(0, f+1);
|
||||
down = atoi(s.c_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
Buffer::Stats::Stats(){
|
||||
up = 0;
|
||||
down = 0;
|
||||
conntime = 0;
|
||||
}
|
||||
|
||||
Buffer::Stats::Stats(std::string s){
|
||||
size_t f = s.find(' ');
|
||||
if (f != std::string::npos){
|
||||
host = s.substr(0, f);
|
||||
s.erase(0, f+1);
|
||||
}
|
||||
f = s.find(' ');
|
||||
if (f != std::string::npos){
|
||||
connector = s.substr(0, f);
|
||||
s.erase(0, f+1);
|
||||
}
|
||||
f = s.find(' ');
|
||||
if (f != std::string::npos){
|
||||
conntime = atoi(s.substr(0, f).c_str());
|
||||
s.erase(0, f+1);
|
||||
}
|
||||
f = s.find(' ');
|
||||
if (f != std::string::npos){
|
||||
up = atoi(s.substr(0, f).c_str());
|
||||
s.erase(0, f+1);
|
||||
down = atoi(s.c_str());
|
||||
}
|
||||
}
|
||||
|
|
16
Buffer/stats.h
Normal file
16
Buffer/stats.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace Buffer{
|
||||
/// Converts a stats line to up, down, host, connector and conntime values.
|
||||
class Stats{
|
||||
public:
|
||||
unsigned int up;
|
||||
unsigned int down;
|
||||
std::string host;
|
||||
std::string connector;
|
||||
unsigned int conntime;
|
||||
Stats();
|
||||
Stats(std::string s);
|
||||
};
|
||||
}
|
216
Buffer/stream.cpp
Normal file
216
Buffer/stream.cpp
Normal file
|
@ -0,0 +1,216 @@
|
|||
#include "stream.h"
|
||||
|
||||
/// Stores the globally equal reference.
|
||||
Buffer::Stream * Buffer::Stream::ref = 0;
|
||||
|
||||
/// Returns a globally equal reference to this class.
|
||||
Buffer::Stream * Buffer::Stream::get(){
|
||||
static tthread::mutex creator;
|
||||
if (ref == 0){
|
||||
//prevent creating two at the same time
|
||||
creator.lock();
|
||||
if (ref == 0){ref = new Stream();}
|
||||
creator.unlock();
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
/// Creates a new DTSC::Stream object, private function so only one instance can exist.
|
||||
Buffer::Stream::Stream(){
|
||||
Strm = new DTSC::Stream(5);
|
||||
}
|
||||
|
||||
/// Do cleanup on delete.
|
||||
Buffer::Stream::~Stream(){
|
||||
delete Strm;
|
||||
while (users.size() > 0){
|
||||
stats_mutex.lock();
|
||||
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
|
||||
if ((**usersIt).S.connected()){
|
||||
if ((**usersIt).myRing->waiting){
|
||||
(**usersIt).S.close();
|
||||
printf("Closing user %s\n", (**usersIt).MyStr.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
stats_mutex.unlock();
|
||||
moreData.notify_all();
|
||||
cleanUsers();
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate and return the current statistics in JSON format.
|
||||
std::string Buffer::Stream::getStats(){
|
||||
unsigned int now = time(0);
|
||||
unsigned int tot_up = 0, tot_down = 0, tot_count = 0;
|
||||
stats_mutex.lock();
|
||||
if (users.size() > 0){
|
||||
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
|
||||
tot_down += (**usersIt).curr_down;
|
||||
tot_up += (**usersIt).curr_up;
|
||||
tot_count++;
|
||||
}
|
||||
}
|
||||
Storage["totals"]["down"] = tot_down;
|
||||
Storage["totals"]["up"] = tot_up;
|
||||
Storage["totals"]["count"] = tot_count;
|
||||
Storage["totals"]["now"] = now;
|
||||
Storage["totals"]["buffer"] = name;
|
||||
std::string ret = Storage.toString();
|
||||
Storage["log"].null();
|
||||
stats_mutex.unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Get a new DTSC::Ring object for a user.
|
||||
DTSC::Ring * Buffer::Stream::getRing(){
|
||||
return Strm->getRing();
|
||||
}
|
||||
|
||||
/// Drop a DTSC::Ring object.
|
||||
void Buffer::Stream::dropRing(DTSC::Ring * ring){
|
||||
Strm->dropRing(ring);
|
||||
}
|
||||
|
||||
/// Get the (constant) header data of this stream.
|
||||
std::string & Buffer::Stream::getHeader(){
|
||||
return Strm->outHeader();
|
||||
}
|
||||
|
||||
/// Set the IP address to accept push data from.
|
||||
void Buffer::Stream::setWaitingIP(std::string ip){
|
||||
waiting_ip = ip;
|
||||
}
|
||||
|
||||
/// Check if this is the IP address to accept push data from.
|
||||
bool Buffer::Stream::checkWaitingIP(std::string ip){
|
||||
if (ip == waiting_ip || ip == "::ffff:"+waiting_ip){
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the current socket for push data.
|
||||
bool Buffer::Stream::setInput(Socket::Connection S){
|
||||
if (ip_input.connected()){
|
||||
return false;
|
||||
}else{
|
||||
ip_input = S;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current socket for push data.
|
||||
Socket::Connection & Buffer::Stream::getIPInput(){
|
||||
return ip_input;
|
||||
}
|
||||
|
||||
|
||||
/// Stores intermediate statistics.
|
||||
void Buffer::Stream::saveStats(std::string username, Stats & stats){
|
||||
stats_mutex.lock();
|
||||
Storage["curr"][username]["connector"] = stats.connector;
|
||||
Storage["curr"][username]["up"] = stats.up;
|
||||
Storage["curr"][username]["down"] = stats.down;
|
||||
Storage["curr"][username]["conntime"] = stats.conntime;
|
||||
Storage["curr"][username]["host"] = stats.host;
|
||||
Storage["curr"][username]["start"] = (unsigned int) time(0) - stats.conntime;
|
||||
stats_mutex.unlock();
|
||||
}
|
||||
|
||||
/// Stores final statistics.
|
||||
void Buffer::Stream::clearStats(std::string username, Stats & stats, std::string reason){
|
||||
stats_mutex.lock();
|
||||
Storage["curr"].removeMember(username);
|
||||
Storage["log"][username]["connector"] = stats.connector;
|
||||
Storage["log"][username]["up"] = stats.up;
|
||||
Storage["log"][username]["down"] = stats.down;
|
||||
Storage["log"][username]["conntime"] = stats.conntime;
|
||||
Storage["log"][username]["host"] = stats.host;
|
||||
Storage["log"][username]["start"] = (unsigned int)time(0) - stats.conntime;
|
||||
std::cout << "Disconnected user " << username << ": " << reason << ". " << stats.connector << " transferred " << stats.up << " up and " << stats.down << " down in " << stats.conntime << " seconds to " << stats.host << std::endl;
|
||||
stats_mutex.unlock();
|
||||
cleanUsers();
|
||||
}
|
||||
|
||||
/// Cleans up broken connections
|
||||
void Buffer::Stream::cleanUsers(){
|
||||
bool repeat = false;
|
||||
stats_mutex.lock();
|
||||
do{
|
||||
repeat = false;
|
||||
if (users.size() > 0){
|
||||
for (usersIt = users.begin(); usersIt != users.end(); usersIt++){
|
||||
if ((**usersIt).Thread == 0 && !(**usersIt).S.connected()){
|
||||
delete *usersIt;
|
||||
users.erase(usersIt);
|
||||
repeat = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}while(repeat);
|
||||
stats_mutex.unlock();
|
||||
}
|
||||
|
||||
/// Blocks until writing is safe.
|
||||
void Buffer::Stream::getWriteLock(){
|
||||
rw_mutex.lock();
|
||||
writers++;
|
||||
while (writers != 1 && readers != 0){
|
||||
rw_change.wait(rw_mutex);
|
||||
}
|
||||
rw_mutex.unlock();
|
||||
}
|
||||
|
||||
/// Drops a previously gotten write lock.
|
||||
void Buffer::Stream::dropWriteLock(){
|
||||
rw_mutex.lock();
|
||||
writers--;
|
||||
rw_mutex.unlock();
|
||||
rw_change.notify_all();
|
||||
moreData.notify_all();
|
||||
}
|
||||
|
||||
/// Blocks until reading is safe.
|
||||
void Buffer::Stream::getReadLock(){
|
||||
rw_mutex.lock();
|
||||
while (writers > 0){
|
||||
rw_change.wait(rw_mutex);
|
||||
}
|
||||
readers++;
|
||||
rw_mutex.unlock();
|
||||
}
|
||||
|
||||
/// Drops a previously gotten read lock.
|
||||
void Buffer::Stream::dropReadLock(){
|
||||
rw_mutex.lock();
|
||||
readers--;
|
||||
rw_mutex.unlock();
|
||||
rw_change.notify_all();
|
||||
}
|
||||
|
||||
/// Retrieves a reference to the DTSC::Stream
|
||||
DTSC::Stream * Buffer::Stream::getStream(){
|
||||
return Strm;
|
||||
}
|
||||
|
||||
/// Sets the buffer name.
|
||||
void Buffer::Stream::setName(std::string n){
|
||||
name = n;
|
||||
}
|
||||
|
||||
/// Add a user to the userlist.
|
||||
void Buffer::Stream::addUser(user * new_user){
|
||||
stats_mutex.lock();
|
||||
users.push_back(new_user);
|
||||
stats_mutex.unlock();
|
||||
}
|
||||
|
||||
/// Blocks the thread until new data is available.
|
||||
void Buffer::Stream::waitForData(){
|
||||
stats_mutex.lock();
|
||||
moreData.wait(stats_mutex);
|
||||
stats_mutex.unlock();
|
||||
}
|
69
Buffer/stream.h
Normal file
69
Buffer/stream.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include "../util/tinythread.h"
|
||||
#include "../util/json.h"
|
||||
#include "user.h"
|
||||
|
||||
namespace Buffer{
|
||||
class Stream{
|
||||
public:
|
||||
/// Get a reference to this Stream object.
|
||||
static Stream * get();
|
||||
/// Get the current statistics in JSON format.
|
||||
std::string getStats();
|
||||
/// Get a new DTSC::Ring object for a user.
|
||||
DTSC::Ring * getRing();
|
||||
/// Drop a DTSC::Ring object.
|
||||
void dropRing(DTSC::Ring * ring);
|
||||
/// Get the (constant) header data of this stream.
|
||||
std::string & getHeader();
|
||||
/// Set the IP address to accept push data from.
|
||||
void setWaitingIP(std::string ip);
|
||||
/// Check if this is the IP address to accept push data from.
|
||||
bool checkWaitingIP(std::string ip);
|
||||
/// Sets the current socket for push data.
|
||||
bool setInput(Socket::Connection S);
|
||||
/// Gets the current socket for push data.
|
||||
Socket::Connection & getIPInput();
|
||||
/// Stores intermediate statistics.
|
||||
void saveStats(std::string username, Stats & stats);
|
||||
/// Stores final statistics.
|
||||
void clearStats(std::string username, Stats & stats, std::string reason);
|
||||
/// Cleans up broken connections
|
||||
void cleanUsers();
|
||||
/// Blocks until writing is safe.
|
||||
void getWriteLock();
|
||||
/// Drops a previously gotten write lock.
|
||||
void dropWriteLock();
|
||||
/// Blocks until reading is safe.
|
||||
void getReadLock();
|
||||
/// Drops a previously gotten read lock.
|
||||
void dropReadLock();
|
||||
/// Retrieves a reference to the DTSC::Stream
|
||||
DTSC::Stream * getStream();
|
||||
/// Sets the buffer name.
|
||||
void setName(std::string n);
|
||||
/// Add a user to the userlist.
|
||||
void addUser(user * new_user);
|
||||
/// Blocks the thread until new data is available.
|
||||
void waitForData();
|
||||
/// Cleanup function
|
||||
~Stream();
|
||||
private:
|
||||
volatile int readers;///< Current count of active readers;
|
||||
volatile int writers;///< Current count of waiting/active writers.
|
||||
tthread::mutex rw_mutex; ///< Mutex for read/write locking.
|
||||
tthread::condition_variable rw_change; ///< Triggered when reader/writer count changes.
|
||||
static Stream * ref;
|
||||
Stream();
|
||||
JSON::Value Storage; ///< Global storage of data.
|
||||
DTSC::Stream * Strm;
|
||||
std::string waiting_ip; ///< IP address for media push.
|
||||
Socket::Connection ip_input; ///< Connection used for media push.
|
||||
tthread::mutex stats_mutex; ///< Mutex for stats/users modifications.
|
||||
std::vector<user*> users; ///< All connected users.
|
||||
std::vector<user*>::iterator usersIt; ///< Iterator for all connected users.
|
||||
std::string name; ///< Name for this buffer.
|
||||
tthread::condition_variable moreData; ///< Triggered when more data becomes available.
|
||||
};
|
||||
};
|
169
Buffer/user.cpp
169
Buffer/user.cpp
|
@ -1,97 +1,76 @@
|
|||
namespace Buffer{
|
||||
/// Holds connected users.
|
||||
/// Keeps track of what buffer users are using and the connection status.
|
||||
class user{
|
||||
public:
|
||||
tthread::thread * Thread; ///< Holds the thread dealing with this user.
|
||||
DTSC::Ring * myRing; ///< Ring of the buffer for this user.
|
||||
int MyNum; ///< User ID of this user.
|
||||
std::string MyStr; ///< User ID of this user as a string.
|
||||
std::string inbuffer; ///< Used to buffer input data.
|
||||
int currsend; ///< Current amount of bytes sent.
|
||||
Stats lastStats; ///< Holds last known stats for this connection.
|
||||
Stats tmpStats; ///< Holds temporary stats for this connection.
|
||||
unsigned int curr_up; ///< Holds the current estimated transfer speed up.
|
||||
unsigned int curr_down; ///< Holds the current estimated transfer speed down.
|
||||
bool gotproperaudio; ///< Whether the user received proper audio yet.
|
||||
void * lastpointer; ///< Pointer to data part of current buffer.
|
||||
static int UserCount; ///< Global user counter.
|
||||
Socket::Connection S; ///< Connection to user
|
||||
/// Creates a new user from a newly connected socket.
|
||||
/// Also prints "User connected" text to stdout.
|
||||
user(Socket::Connection fd){
|
||||
S = fd;
|
||||
MyNum = UserCount++;
|
||||
std::stringstream st;
|
||||
st << MyNum;
|
||||
MyStr = st.str();
|
||||
curr_up = 0;
|
||||
curr_down = 0;
|
||||
currsend = 0;
|
||||
myRing = 0;
|
||||
Thread = 0;
|
||||
std::cout << "User " << MyNum << " connected" << std::endl;
|
||||
}//constructor
|
||||
/// Drops held DTSC::Ring class, if one is held.
|
||||
~user(){
|
||||
Strm->dropRing(myRing);
|
||||
}//destructor
|
||||
/// Disconnects the current user. Doesn't do anything if already disconnected.
|
||||
/// Prints "Disconnected user" to stdout if disconnect took place.
|
||||
void Disconnect(std::string reason) {
|
||||
if (S.connected()){S.close();}
|
||||
if (Thread != 0){
|
||||
if (Thread->joinable()){Thread->join();}
|
||||
Thread = 0;
|
||||
}
|
||||
tthread::lock_guard<tthread::mutex> lock(stats_mutex);
|
||||
Storage["curr"].removeMember(MyStr);
|
||||
Storage["log"][MyStr]["connector"] = lastStats.connector;
|
||||
Storage["log"][MyStr]["up"] = lastStats.up;
|
||||
Storage["log"][MyStr]["down"] = lastStats.down;
|
||||
Storage["log"][MyStr]["conntime"] = lastStats.conntime;
|
||||
Storage["log"][MyStr]["host"] = lastStats.host;
|
||||
Storage["log"][MyStr]["start"] = (unsigned int)time(0) - lastStats.conntime;
|
||||
std::cout << "Disconnected user " << MyStr << ": " << reason << ". " << lastStats.connector << " transferred " << lastStats.up << " up and " << lastStats.down << " down in " << lastStats.conntime << " seconds to " << lastStats.host << std::endl;
|
||||
}//Disconnect
|
||||
/// Tries to send the current buffer, returns true if success, false otherwise.
|
||||
/// Has a side effect of dropping the connection if send will never complete.
|
||||
bool doSend(const char * ptr, int len){
|
||||
int r = S.iwrite(ptr+currsend, len-currsend);
|
||||
if (r <= 0){
|
||||
if (errno == EWOULDBLOCK){return false;}
|
||||
Disconnect(S.getError());
|
||||
return false;
|
||||
}
|
||||
currsend += r;
|
||||
return (currsend == len);
|
||||
}//doSend
|
||||
/// Try to send data to this user. Disconnects if any problems occur.
|
||||
void Send(){
|
||||
if (!myRing){return;}//no ring!
|
||||
if (!S.connected()){return;}//cancel if not connected
|
||||
if (myRing->waiting){
|
||||
tthread::lock_guard<tthread::mutex> guard(transfer_mutex);
|
||||
moreData.wait(transfer_mutex);
|
||||
return;
|
||||
}//still waiting for next buffer?
|
||||
#include "user.h"
|
||||
#include "stream.h"
|
||||
#include <sstream>
|
||||
|
||||
if (myRing->starved){
|
||||
//if corrupt data, warn and get new DTSC::Ring
|
||||
std::cout << "Warning: User was send corrupt video data and send to the next keyframe!" << std::endl;
|
||||
Strm->dropRing(myRing);
|
||||
myRing = Strm->getRing();
|
||||
return;
|
||||
}
|
||||
int Buffer::user::UserCount = 0;
|
||||
|
||||
//try to complete a send
|
||||
if (doSend(Strm->outPacket(myRing->b).c_str(), Strm->outPacket(myRing->b).length())){
|
||||
//switch to next buffer
|
||||
currsend = 0;
|
||||
if (myRing->b <= 0){myRing->waiting = true; return;}//no next buffer? go in waiting mode.
|
||||
myRing->b--;
|
||||
}//completed a send
|
||||
}//send
|
||||
};
|
||||
int user::UserCount = 0;
|
||||
}
|
||||
/// Creates a new user from a newly connected socket.
|
||||
/// Also prints "User connected" text to stdout.
|
||||
Buffer::user::user(Socket::Connection fd){
|
||||
S = fd;
|
||||
MyNum = UserCount++;
|
||||
std::stringstream st;
|
||||
st << MyNum;
|
||||
MyStr = st.str();
|
||||
curr_up = 0;
|
||||
curr_down = 0;
|
||||
currsend = 0;
|
||||
myRing = 0;
|
||||
Thread = 0;
|
||||
std::cout << "User " << MyNum << " connected" << std::endl;
|
||||
}//constructor
|
||||
|
||||
/// Drops held DTSC::Ring class, if one is held.
|
||||
Buffer::user::~user(){
|
||||
Stream::get()->dropRing(myRing);
|
||||
}//destructor
|
||||
|
||||
/// Disconnects the current user. Doesn't do anything if already disconnected.
|
||||
/// Prints "Disconnected user" to stdout if disconnect took place.
|
||||
void Buffer::user::Disconnect(std::string reason) {
|
||||
if (S.connected()){S.close();}
|
||||
if (Thread != 0){
|
||||
if (Thread->joinable()){Thread->join();}
|
||||
Thread = 0;
|
||||
}
|
||||
Stream::get()->clearStats(MyStr, lastStats, reason);
|
||||
}//Disconnect
|
||||
|
||||
/// Tries to send the current buffer, returns true if success, false otherwise.
|
||||
/// Has a side effect of dropping the connection if send will never complete.
|
||||
bool Buffer::user::doSend(const char * ptr, int len){
|
||||
int r = S.iwrite(ptr+currsend, len-currsend);
|
||||
if (r <= 0){
|
||||
if (errno == EWOULDBLOCK){return false;}
|
||||
Disconnect(S.getError());
|
||||
return false;
|
||||
}
|
||||
currsend += r;
|
||||
return (currsend == len);
|
||||
}//doSend
|
||||
|
||||
/// Try to send data to this user. Disconnects if any problems occur.
|
||||
void Buffer::user::Send(){
|
||||
if (!myRing){return;}//no ring!
|
||||
if (!S.connected()){return;}//cancel if not connected
|
||||
if (myRing->waiting){
|
||||
Stream::get()->waitForData();
|
||||
return;
|
||||
}//still waiting for next buffer?
|
||||
if (myRing->starved){
|
||||
//if corrupt data, warn and get new DTSC::Ring
|
||||
std::cout << "Warning: User was send corrupt video data and send to the next keyframe!" << std::endl;
|
||||
Stream::get()->dropRing(myRing);
|
||||
myRing = Stream::get()->getRing();
|
||||
return;
|
||||
}
|
||||
//try to complete a send
|
||||
Stream::get()->getReadLock();
|
||||
if (doSend(Stream::get()->getStream()->outPacket(myRing->b).c_str(), Stream::get()->getStream()->outPacket(myRing->b).length())){
|
||||
//switch to next buffer
|
||||
currsend = 0;
|
||||
if (myRing->b <= 0){myRing->waiting = true; return;}//no next buffer? go in waiting mode.
|
||||
myRing->b--;
|
||||
}//completed a send
|
||||
Stream::get()->dropReadLock();
|
||||
}//send
|
||||
|
|
41
Buffer/user.h
Normal file
41
Buffer/user.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include "stats.h"
|
||||
#include "../util/dtsc.h"
|
||||
#include "../util/socket.h"
|
||||
#include "../util/tinythread.h"
|
||||
|
||||
namespace Buffer{
|
||||
/// Holds connected users.
|
||||
/// Keeps track of what buffer users are using and the connection status.
|
||||
class user{
|
||||
public:
|
||||
tthread::thread * Thread; ///< Holds the thread dealing with this user.
|
||||
DTSC::Ring * myRing; ///< Ring of the buffer for this user.
|
||||
int MyNum; ///< User ID of this user.
|
||||
std::string MyStr; ///< User ID of this user as a string.
|
||||
std::string inbuffer; ///< Used to buffer input data.
|
||||
int currsend; ///< Current amount of bytes sent.
|
||||
Stats lastStats; ///< Holds last known stats for this connection.
|
||||
Stats tmpStats; ///< Holds temporary stats for this connection.
|
||||
unsigned int curr_up; ///< Holds the current estimated transfer speed up.
|
||||
unsigned int curr_down; ///< Holds the current estimated transfer speed down.
|
||||
bool gotproperaudio; ///< Whether the user received proper audio yet.
|
||||
void * lastpointer; ///< Pointer to data part of current buffer.
|
||||
static int UserCount; ///< Global user counter.
|
||||
Socket::Connection S; ///< Connection to user
|
||||
/// Creates a new user from a newly connected socket.
|
||||
/// Also prints "User connected" text to stdout.
|
||||
user(Socket::Connection fd);
|
||||
/// Drops held DTSC::Ring class, if one is held.
|
||||
~user();
|
||||
/// Disconnects the current user. Doesn't do anything if already disconnected.
|
||||
/// Prints "Disconnected user" to stdout if disconnect took place.
|
||||
void Disconnect(std::string reason);
|
||||
/// Tries to send the current buffer, returns true if success, false otherwise.
|
||||
/// Has a side effect of dropping the connection if send will never complete.
|
||||
bool doSend(const char * ptr, int len);
|
||||
/// Try to send data to this user. Disconnects if any problems occur.
|
||||
void Send();
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue