live555推流----局域网延迟的分析

============20191212--追加---------以下主要追究发送比较大的网络数据时多次调用 系统调用send而产生的耗时问题,不过对于实际上传输音视频实时流的使用场景,往往数据不会这么大,延迟的性能瓶颈主要不在于此========================

最近在做局域网投屏相关,发现使用vlc做推流和接受,延迟过大(1s以上),改用live555推流和接受,延迟还是过大,局域网中还打不到<200ms的延迟。于是乎研究了好一阵子live555源代码,也没有发现其代码层面故意的休眠的情况出现,苦思冥想数日,不得解,直到这个阳光明媚的周六,突然就想到一个方法,排除live555代码,有没有一个方法来直接了当地验证网络本来就性能不够呢,虽然说是局域网,似乎这个怀疑本生就不太然人觉得可靠。 周六就在家敲起了代码:
测试的代码是这样的:
live555推流----局域网延迟的分析_第1张图片

一个服务端,一个客户端,tcp建立连接,服务端以每秒30帧,(每个SendFrame操作之间休眠 usleep(100*10000/30)),每帧分成1500 Byte的小帧多次发送(小帧之间的发送间隔时间,不休眠)。服务端记录下发送端每一次发送一帧前的时间点,图中Time_send,客户端记录下每一帧接受完的时间点Time_reciv. 这两个时间差,即一帧数据的传输延迟。(前提是服务端和客户端都有相同的系统时间的情况下,记录的时间才有参考价值,为保证这一点,先在一个系统上测试,这里在虚拟机上 开启两个终端,使用本地回环网络 127.0.0.1 ,一个终端作为server, 另一个终端作为client。 同时为方便比较,记录下时间的同时也把整个发送/接受到的数据一并写入了log文件。)结果如下:
live555推流----局域网延迟的分析_第2张图片
可以看到,竟然达到了975231-736331=238900 微秒,也就是239毫秒。这可是本地回环网络,虽然我的环境是虚拟机ubuntu,这结果,相当不能接受。回头来看,每一次发送一帧,300Kb, 分成了205个小包发送。每一个包调用一次sendto() 发送。如果把上面的rtp分包操作去掉,不进行分包,直接调用一次 sendto()发送300Kb. 测试结果
live555推流----局域网延迟的分析_第3张图片
 

298075-296098=1977微秒,也就是2ms. 这,才是应该有的速度。。。。。。。
如果说硬要强行解释一番的话,只能说,sendto() 系统调用,需要消耗资源,要尽量减少频繁调用,上诉的一帧数据分成 205 次sendto()发送,明显比一次调用sendto() 耗时多得多。 而mtu分包,内核的tcp/ip协议栈会自行处理,如期交给应用层自己分包,多次调用系统调用sendto,还不如一次调用sendto,分包工作就交给 tcp/ip 协议栈自行处理。
至于为什么RTP要在应用层协议自己分包,还不太明白。总之,live555也好,vlc也好,发送rtp都在应用层(rtp层)自行分了包,也就是上面的200多ms延迟的做法。

实际验证:
使用 live555 testOnDemandRTSPServer.cpp 测试h264服务, 同时使用 testRTSPClient.cpp 接受流,开启 testRTSPClient.cpp中的 #define REQUEST_STREAMING_OVER_TCP True, 用rtp-over-tcp, 然后在RTPInterface.cpp 的函数 sendDataOverTCP()的开始出添加打印信息:(也就是系统调用send()函数之前添加)
 printf("send overt tcp :size %d \n",dataSize); //或者用live555里面通用的 env <<  输出。

Boolean RTPInterface::sendDataOverTCP(int socketNum, u_int8_t const* data, unsigned dataSize, Boolean forceSendToSucceed) {
 printf("send overt tcp :size %d \n",dataSize);或者用live555里面通用的 env <<  输出

  int sendResult = send(socketNum, (char const*)data, dataSize, 0/*flags*/);
....
}


以下为实际输出,可以看到server端,每次调用send()发送数据,都不会超过145Byte字节,这个数值对应在 源码MultiFramedRTPSink.cpp  宏定义#define RTP_PAYLOAD_MAX_SIZE 1456 。
不仅如此,每次发送一帧数据多次调用send(),还在每次发送之前单独发送一个 rtp-overt-tcp 需要的额外的 4字节数据。【这个是由于需要同一套上层发送接口,要同时支持rtp-over-tcp 和 rtp-over-udp的原因吧】因为这个机制,在用live555 tcp推流时,wireshark抓的网络包就不便于分析,字节的通道头和数据可能是分别在两个tcp包中的
live555推流----局域网延迟的分析_第4张图片
上述测试,在android真机上测试,晓龙625 cpu。
live555发送一第一帧 40521 byte的h264帧,在本机loop网络端接受,调用了send() 28X2 = 56次, 耗时 833496-781336=52160 微秒
52ms的传输延迟。

测试代码后续github上传。
#追加 20200506---,直接在这里贴上上述测试 demo的源了#
live555推流----局域网延迟的分析_第5张图片

clinet.cpp
 

#include 
#include 
#include 
#include 
#include 
#include
#include "com_net.h"

static FILE* fp_log = NULL;
static bool bFirst = true;
static int logIndex = 0;

//以下写log的耗时,也被记入帧传输时延 ,但占比很小可以忽略
void logRecive(char* data,int datalen)
{
	if(bFirst)
	{
		bFirst = false;
		fp_log = fopen("./get_log","w+");
		if(fp_log == NULL)
		{
			printf("fopen erro [%d%s]!\n",__LINE__,__FUNCTION__);
		}
		else
		{
			setbuf(fp_log,0);
		}
	}
	if(fp_log)
	{
		struct timeval tv;
		struct tm *info;
		gettimeofday(&tv,NULL);
		info = gmtime(&tv.tv_sec);
		fprintf(fp_log,"index:%04d datalen:%08d [%02d:%02d:%02d:%06ld]",logIndex++,datalen,info->tm_hour,info->tm_min,info->tm_sec,tv.tv_usec);
		fwrite(data,1,datalen,fp_log);
	}
}

int main(int argc,const char*argv[])
{
	if(argc != 4)
	{
		printf("usage:serverIp, serverPort, frameLen\n");
		return -1;
	}
	int connect_fd=0;
	int frameLen = atoi(argv[3]);
	
	connect_fd = tcp_net_conncet(argv[1],atoi(argv[2]));
	if(connect_fd < 0)
	{
		printf("connect erro\n");
		return -1;
	}
	
	char*buf = (char*)malloc(frameLen);
	if(buf == NULL)
	{
		printf("malloc erro %d\n",frameLen);
		return -1;
	}
	
	increaseReceiveBufferTo(connect_fd,20000);
	while(1)
	{
		int dataRead =0;
		while(dataRead

server.cpp
 

#include 
#include 
#include 
#include 
#include 
#include
#include "com_net.h"

static bool bTCP = true;

static FILE* fp_log = NULL;
static bool bFirst = true;
static int logIndex = 0;

//以下写log的耗时,也被记入帧传输时延 ,但占比很小可以忽略
void logSend(char* data,int datalen)
{
	if(bFirst)
	{
		bFirst = false;
		fp_log = fopen("./send_log","w+");
		if(fp_log == NULL)
		{
			printf("fopen erro [%d%s]!\n",__LINE__,__FUNCTION__);
		}
		else
		{
			setbuf(fp_log,0);
		}
	}
	if(fp_log)
	{
		struct timeval tv;
		struct tm *info;
		gettimeofday(&tv,NULL);
		info = gmtime(&tv.tv_sec);
		fprintf(fp_log,"index:%04d datalen:%08d [%02d:%02d:%02d:%06ld]",logIndex++,datalen,info->tm_hour,info->tm_min,info->tm_sec,tv.tv_usec);
		fwrite(data,1,datalen,fp_log);
	}
}
int SendFrame(int fd, char *data,  int datalen, int mtu=1500, int sendGap=0);
int SendFrame(int fd, char *data,  int datalen, int mtu, int sendGap)
{
	logSend(data,datalen);
	
	
	if(mtu == -1)
	{
		int sendcount=0;
		while(datalen > 0)
		{
			sendcount = writeSocket(fd, data, datalen);
			if(sendcount < 0)
			{//send over
				printf("send erro \n");
				break ;
			}
			datalen -= sendcount;
			data += sendcount;
			//usleep(sendGap);//微秒
		}
	}
	else
	{
		int sendcount=0;
		while(datalen > 0)
		{
			sendcount = writeSocket(fd, data, datalen>mtu?mtu:datalen);
			if(sendcount < mtu)
			{//send over
				break ;
			}
			datalen -= sendcount;
			data += sendcount;
			usleep(sendGap);//微秒
		}
	}
}
int SendFileLoop(const char*file,int connect_fd,int frameLen,int fps, int sendGap)
{
	if(file == NULL || connect_fd < 0 || fps <= 0 || frameLen <= 0 || sendGap<0 )
	{
		printf("input param erro !file[%s],connect_fd[%d],frameLen[%d],fps[%d],sendGap[%d]:",file,connect_fd,frameLen,fps,sendGap);
		return -1;
	}
	char* buffer=(char *)malloc(frameLen);
	if(buffer == NULL)
	{
		printf("malloc erro :%d [%d%s]\n",frameLen,__LINE__,__FUNCTION__);
		return -1;
	}
	
	FILE *fp_in = fopen(file,"r");
	if(fp_in == NULL)
	{
		printf("fopen erro: %s[%d%s]\n",file,__LINE__,__FUNCTION__);
		return -1;
	}
	
	while(1)
	{
		if(fread(buffer,1,frameLen,fp_in)

需要的网络公关函数:
com_debug.h

#ifndef __hearder_com_debug__
#define __hearder_com_debug__
#include


#define RET_SUCESS 0
#define RET_ERR  -1
#define RET_ERR_NEEDMOREDATA -2


//从 第0开始算,start
#define LEFT_CHAR_BIT_GET_RAW(pData,start,len) ( (u_int8_t)(pData)[0] & ((1<<(8-start)) - (1<<(8-start-len))) )
#define LEFT_CHAR_BIT_GET(pData,start,len) (LEFT_CHAR_BIT_GET_RAW(pData,start,len)>>(8-start-len))

#define RIGHT_CHAR_BIT_GET(pData,start,len) ( ((u_int8_t)(pData)[0]>>(start)) & ((0x01<<(len)) -1) )

#define LITTLE_MAKE_UNINT16(pData) (((u_int8_t)(pData)[0]<<8)  | ((u_int8_t)(pData)[1]))
#define LITTLE_MAKE_UNINT32(pData) (((u_int8_t)(pData)[0]<<24) | ((u_int8_t)(pData)[1]<<16) | ((u_int8_t)(pData)[2]<<8) | (u_int8_t)(pData)[3])

#ifndef DEBUG_LEVE
#define DEBUG_LEVE 3
#endif

#define DEBUG_WARN(fmt, args...) do {printf("WARN![%s,%s,%d]",__FILE__,__FUNCTION__,__LINE__);printf(fmt,##args);} while(0)
#define DEBUG_ERR(fmt, args...) do {printf("ERR!!!![%s,%s,%d]",__FILE__,__FUNCTION__,__LINE__);printf(fmt,##args);} while(0)

#if DEBUG_LEVE >=3
#define DEBUG_INFO1(fmt, args...) do {printf("[%s,%s,%d]",__FILE__,__FUNCTION__,__LINE__);printf(fmt,##args);} while(0)
#define DEBUG_INFO2(fmt, args...) do {printf("[%s,%s,%d]",__FILE__,__FUNCTION__,__LINE__);printf(fmt,##args);} while(0)
#define DEBUG_INFO3(fmt, args...) do {printf("[%s,%s,%d]",__FILE__,__FUNCTION__,__LINE__);printf(fmt,##args);} while(0)
#elif DEBUG_LEVE ==2
#define DEBUG_INFO1(fmt, args...) do {printf("[%s,%s,%d]",__FILE__,__FUNCTION__,__LINE__);printf(fmt,##args);} while(0)
#define DEBUG_INFO2(fmt, args...) do {printf("[%s,%s,%d]",__FILE__,__FUNCTION__,__LINE__);printf(fmt,##args);} while(0)
#define DEBUG_INFO3(fmt, args...)   
#elif DEBUG_LEVE ==1
#define DEBUG_INFO1(fmt, args...) do {printf("[%s,%s,%d]",__FILE__,__FUNCTION__,__LINE__);printf(fmt,##args);} while(0)
#define DEBUG_INFO2(fmt, args...)  
#define DEBUG_INFO3(fmt, args...)  
#endif
//还未实现

/*
* 最好是单例模式,互斥同步, 用宏定义重新包装 接口
*/
class CDebugInfo
{
	public: 
		CDebugInfo(char infoLeve=-1);
		~CDebugInfo();
		void debug_warn();
		void debug_err();
		void debug_info_leve1();
		void debug_info_leve2();
		void debug_info_leve3();
	private:
		//info leve, 
		//-1 :all 
		//0 : warn and err,
		//3 : info3 info2 info 1 , warn and err,
		//2 : info2 info1 , warn and err,
		//1 : info1, warn and err
		char m_infoLeve;
};
#endif

com_debug.cpp
 

#include "com_debug.h"
CDebugInfo::CDebugInfo(char infoLeve)
:m_infoLeve(infoLeve)
{
	
}
CDebugInfo::~CDebugInfo()
{
	
}
void CDebugInfo::debug_warn()
{
	
}
void CDebugInfo::debug_err()
{
	
}
void CDebugInfo::debug_info_leve1()
{
	
}
void CDebugInfo::debug_info_leve2()
{
	
}
void CDebugInfo::debug_info_leve3()
{
	
}

com_net.h
 

#ifndef __header_com_net__
#define __header_com_net__
#include 			/* See NOTES */
#include 


//typedef char int8_t;
typedef unsigned char u_int8_t;

typedef short int16_t;
typedef unsigned short u_int16_t;

typedef int int32_t;
typedef unsigned int u_int32_t;


int udp_net_setup();
int udp_connect(int socket,const char*host, int port );

int udp_get_port(int socket, int16_t *port);


int tcp_net_conncet(const char *psz_host,int i_port);
int readSocket(  int socket,  char* buffer, unsigned bufferSize,
	        struct sockaddr *fromAddress = NULL);

int writeSocket(int socket, char* buffer, unsigned bufferSize,
			struct sockaddr* destAddress = NULL); 

int closeSocket(int socket);

bool makeSocketNonBlocking(int sock);

bool makeSocketBlocking(int sock, unsigned writeTimeoutInMilliseconds);

unsigned getReceiveBufferSize( int socket);

unsigned increaseReceiveBufferTo(int socket, unsigned requestedSize);
int accept(const char *ip, unsigned port);
#endif

com_net.cpp
 

#include "com_debug.h"
#include "com_net.h"

#include 
#include 			/* See NOTES */
#include 
#include 
#include 

#include 

#include   
#include 
#include 
#include 

#include"memwatch.h"

int udp_net_setup()
{//udp 可以直接用recevfrom  指明从哪个主机和端口接受,但是每次都要填写地址信息
//所以udp可以用connet 确定好服务端地址端口
//同样也可以先用bind绑定好自己客户端的端口地址,避免自己调用send的时候系统给客户端自己分配一个端口
	int sockfd;
	struct sockaddr_in cli_addr;

	//close_on_exec。当父进程打开文件时,只需要应用程序设置FD_CLOSEXEC标志位,
	//则当fork后exec其他程序的时候,内核自动会将其继承的父进程FD关闭,避免子进程也同时操作,造成意外
	//

    //vlc 和live555的socket都有 |SOCK_CLOEXEC , 解释如上
	sockfd = socket(AF_INET,SOCK_DGRAM | SOCK_CLOEXEC,0);
	if(sockfd < 0){
		DEBUG_ERR("Fail to socket\n");
		return -1;
	}

	//屏蔽掉SIGPIPE 消息
  	//  setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, sizeof (int));
	int data =1;
	setsockopt(sockfd, SOL_SOCKET, MSG_NOSIGNAL, &data, sizeof (int));


	

	cli_addr.sin_family = AF_INET;
	#if 1//这里使用内核自动分配的端口
	cli_addr.sin_port   = 0;//!!!!!!!!!!!!
	#else

	cli_addr.sin_port   = htons(8554);//!!!!!!!!!!!!
	#endif
	cli_addr.sin_addr.s_addr = INADDR_ANY;
	if( bind(sockfd, (struct sockaddr*)&cli_addr, sizeof(struct sockaddr_in)) < 0)
	{
		//当前使用默认阻塞方式。
		DEBUG_ERR("Fail to bind :INADDR_ANY:0 err:%d %s\n",errno,strerror(errno));
		close(sockfd);
		return -1;
	}

	return sockfd;

}

int udp_connect(int sockfd,const char*host, int port)
{
	
	if(sockfd <= -1)
	{
		return RET_ERR;
	}
	
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port   = htons(port);
	server_addr.sin_addr.s_addr = inet_addr(host);

	DEBUG_INFO3("connect host:%s,port:%d\n",host,port);
	if(connect(sockfd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)) < 0)
	{	
		//当前使用默认阻塞方式。
		DEBUG_ERR("Fail to connect :%s:%d err:%d %s\n",host,port,errno,strerror(errno));
		close(sockfd);
		return RET_ERR;
	}
	return RET_SUCESS;
}
//in net byte order
int udp_get_port(int socket, int16_t *port)
{//参考live555 GroupsockHelper.cpp getSourcePort

  sockaddr_in test; 
  test.sin_port = 0;
  socklen_t len = sizeof(test);
  if (getsockname(socket, (struct sockaddr*)&test, &len) < 0) 
  {
	  DEBUG_ERR("Fail to getsockname err:%d %s\n",errno,strerror(errno));
  	  return RET_ERR;
  }
  
  DEBUG_INFO3("GETCLIENT_UDP_PORT test.sin_port %d \n",test.sin_port);
  //*port = ntohs(test.sin_port);
  *port = test.sin_port;
  DEBUG_INFO3("GETCLIENT_UDP_PORT *port %d \n",*port);
  return RET_SUCESS;
}

//服务器端 ip,和port, 本地的port会自动分配
int tcp_net_conncet(const char *psz_host,int i_port)
{
	int sockfd;
	struct sockaddr_in server_addr;

	//close_on_exec。当父进程打开文件时,只需要应用程序设置FD_CLOSEXEC标志位,
	//则当fork后exec其他程序的时候,内核自动会将其继承的父进程FD关闭,避免子进程也同时操作,造成意外
	//

    //vlc 和live555的socket都有 |SOCK_CLOEXEC , 解释如上
	sockfd = socket(AF_INET,SOCK_STREAM|SOCK_CLOEXEC,0);
	if(sockfd < 0){
		DEBUG_ERR("Fail to socket\n");
		return -1;
	}

	//屏蔽掉SIGPIPE 消息
  	//  setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, sizeof (int));
	int data =1;
	setsockopt(sockfd, SOL_SOCKET, MSG_NOSIGNAL, &data, sizeof (int));

	// int curFlags = fcntl(sockfd, F_GETFL, 0);
    //	 fcntl(sockfd, F_SETFL, curFlags|O_NONBLOCK); //

	server_addr.sin_family = AF_INET;
	server_addr.sin_port   = htons(i_port);
	server_addr.sin_addr.s_addr = inet_addr(psz_host);
	if(connect(sockfd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)) < 0)
	{	//这里可以实现自定义超时时间,不过比较麻烦:
		//https://www.cnblogs.com/alantu2018/p/8469502.html
		//https://blog.csdn.net/CodeHeng/article/details/44625495
		
		//当前使用默认阻塞方式。
		DEBUG_ERR("Fail to connect :%s:%d err:%d %s\n",psz_host,i_port,errno,strerror(errno));
		close(sockfd);
		return -1;
	}

	return sockfd;
	
}


int readSocket(  int socket,   char* buffer, unsigned bufferSize,
	       struct sockaddr *fromAddress)
{
	socklen_t addrlen = sizeof( struct sockaddr);
	int bytesRead = recvfrom(socket, buffer, bufferSize, 0,
                       fromAddress, &addrlen);
	if(bufferSize < 0)
	{//erro
		DEBUG_ERR("err:%d:%s\n",errno,strerror(errno));
	}
	return bytesRead;
}
		   
int writeSocket(int socket,char* buffer, unsigned bufferSize,
				struct sockaddr * destAddress) 
{
	socklen_t addrlen = sizeof( struct sockaddr);
	int bytesSent = sendto(socket, (char*)buffer, bufferSize, 0,
			  destAddress, addrlen);
	if (bytesSent != (int)bufferSize) 
	{//erro
		DEBUG_ERR("writeSocket erro %d/%d \n",bytesSent,bufferSize);
	}
	return bytesSent;
}

int closeSocket(int socket)
{
	return close(socket);
}
bool makeSocketNonBlocking(int sock) 
{
  int curFlags = fcntl(sock, F_GETFL, 0);
  return fcntl(sock, F_SETFL, curFlags|O_NONBLOCK) >= 0;
}


bool makeSocketBlocking(int sock, unsigned writeTimeoutInMilliseconds)
{
  bool result;
#if defined(__WIN32__) || defined(_WIN32)
  unsigned long arg = 0;
  result = ioctlsocket(sock, FIONBIO, &arg) == 0;
#elif defined(VXWORKS)
  int arg = 0;
  result = ioctl(sock, FIONBIO, (int)&arg) == 0;
#else
  int curFlags = fcntl(sock, F_GETFL, 0);
  result = fcntl(sock, F_SETFL, curFlags&(~O_NONBLOCK)) >= 0;
#endif

  if (writeTimeoutInMilliseconds > 0) {
#ifdef SO_SNDTIMEO
#if defined(__WIN32__) || defined(_WIN32)
    DWORD msto = (DWORD)writeTimeoutInMilliseconds;
    setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&msto, sizeof(msto) );
#else
    struct timeval tv;
    tv.tv_sec = writeTimeoutInMilliseconds/1000;
    tv.tv_usec = (writeTimeoutInMilliseconds%1000)*1000;

	//send block and recv block
     setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof tv);
	 setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO, (char *)&tv, sizeof tv);
#endif
#endif
  }

  return result;
}

unsigned getReceiveBufferSize( int socket)
{
	unsigned curSize;
	socklen_t  sizeSize = sizeof(curSize);
	if (getsockopt(socket, SOL_SOCKET, SO_RCVBUF,(char*)&curSize, &sizeSize) < 0)
	{
		DEBUG_ERR("get buffsize erro\n");
		return 0;
	}
	return curSize;
}


unsigned increaseReceiveBufferTo(int socket, unsigned requestedSize)
{
	// First, get the current buffer size.  If it's already at least
	// as big as what we're requesting, do nothing.
	unsigned curSize = getReceiveBufferSize(socket);

	// Next, try to increase the buffer to the requested size,
	// or to some smaller size, if that's not possible:
	while (requestedSize > curSize)
	{
		socklen_t sizeSize = sizeof(requestedSize);
		if (setsockopt(socket, SOL_SOCKET, SO_RCVBUF,(char*)&requestedSize, sizeSize) >= 0)
		{
			// success
			return requestedSize;
		}
		requestedSize = (requestedSize+curSize)/2;
	}
	return getReceiveBufferSize(socket);
}


int accept(const char *ip, unsigned port)
{
	int ret;
	pthread_t tid;
	int listen_fd;
	int connect_fd;
	struct sockaddr_in peer_addr;
	struct sockaddr_in server_addr;
	socklen_t addrlen = sizeof(struct sockaddr);

	listen_fd = socket(AF_INET,SOCK_STREAM,0);
	if(listen_fd < 0){
		perror("Fail to socket");
		return -1;
	}
	
	server_addr.sin_family = AF_INET;
	server_addr.sin_port   = htons(port);
	server_addr.sin_addr.s_addr  = inet_addr(ip);
	if(bind(listen_fd,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
	{
		perror("Fail to bind");
		return -1;
	}
	//设为监听模式
	listen(listen_fd,128);
	printf("Listen ....\n");
	connect_fd = accept(listen_fd,(struct sockaddr *)&peer_addr,&addrlen);
	if(connect_fd < 0){
		perror("Fail to accept");
		return -1;
	}

	printf("--------------------------------------\n");
	printf("Ip : %s\n",inet_ntoa(peer_addr.sin_addr));
	printf("Port : %d\n",ntohs(peer_addr.sin_port));
	printf("--------------------------------------\n");
	
	return connect_fd;
	
}



memwatch.h
这个是之前用来排除内存泄漏等问题的,现在没用,为空文件。

你可能感兴趣的:(live555,vlc,rtp)