Live555 分析(三):客服端

live555的客服端流程:建立任务计划对象--建立环境对象--处理用户输入的参数(RTSP地址)--创建RTSPClient实例--发出DESCRIBE--发出SETUP--发出PLAY--进入Loop循环接收数据--发出TEARDOWN结束连接。

可以抽成3个函数接口:rtspOpen rtspRead rtspClose。

首先我们来分析rtspOpen的过程


int rtspOpen(rtsp_object_t *p_obj, int tcpConnect)
{
     ... ...

TRACE1_DEC("BasicTaskScheduler::createNew !!!\n" );
if( ( p_sys->scheduler = BasicTaskScheduler::createNew() ) == NULL )
{
TRACE1_DEC("BasicTaskScheduler::createNew failed\n" );
goto error;
}

if( !( p_sys->env = BasicUsageEnvironment::createNew(*p_sys->scheduler) ) )
{
TRACE1_DEC("BasicUsageEnvironment::createNew failed\n");
goto error;
}

if( ( i_return = Connect( p_obj ) ) != RTSP_SUCCESS )
{
TRACE1_DEC( "Failed to connect with %s\n", p_obj->rtspURL );
goto error;
}

if( p_sys->p_sdp == NULL )
{
TRACE1_DEC( "Failed to retrieve the RTSP Session Description\n" );
goto error;
}

if( ( i_return = SessionsSetup( p_obj ) ) != RTSP_SUCCESS )
{
TRACE1_DEC( "Nothing to play for rtsp://%s\n", p_obj->rtspURL );
goto error;
}

if( ( i_return = Play( p_obj ) ) != RTSP_SUCCESS )
goto error;

     ... ...
}

1> BasicTaskScheduler::createNew()

2> BasicUsageEnvironment::createNew()

3> connect 


static int Connect( rtsp_object_t *p_demux )
{
     ... ...

sprintf(appName, "LibRTSP%d", p_demux->id);
if( ( p_sys->rtsp = RTSPClient::createNew( *p_sys->env, 1, appName, i_http_port ) ) == NULL )
{
TRACE1_DEC( "RTSPClient::createNew failed (%s)\n",
p_sys->env->getResultMsg() );

i_ret = RTSP_ERROR;
goto connect_error;
}

psz_options = p_sys->rtsp->sendOptionsCmd( p_demux->rtspURL, psz_user, psz_pwd );

if(psz_options == NULL)
TRACE1_DEC("RTSP Option commend error!!\n");

delete [] psz_options;

p_sdp = p_sys->rtsp->describeURL( p_demux->rtspURL );
     ... ...
}

  connect中做了三件事:RTSPClient类的实例,发送“OPTIONS”请求,发送“describeURL”请求。

  sendOptionsCmd()函数首先调用openConnectionFromURL()函数进程tcp连接,然后组包发送:


OPTIONS rtsp://120.90.0.50:8552/h264_ch2 RTSP/1.0
CSeq: 493
User-Agent: LibRTSP4 (LIVE555 Streaming Media v2008.04.02)

  收到服务器的应答:


RTSP/1.0 200 OK
CSeq: 493
Date: Mon, May 26 2014 13:27:07 GMT
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE

  describeURL()函数首先也会调用openConnectionFromURL()函数进行TCP连接(这里可以看出先发OPTIONS请求,也可以先发describeURL请求),然后组包发送:


DESCRIBE rtsp://120.90.0.50:8552/h264_ch2 RTSP/1.0
CSeq: 494
Accept: application/sdp
User-Agent: LibRTSP4 (LIVE555 Streaming Media v2008.04.02)

  收到服务器应答:


DESCRIBE rtsp://120.90.0.50:8552/h264_ch2 RTSP/1.0
CSeq: 494
Accept: application/sdp
User-Agent: LibRTSP4 (LIVE555 Streaming Media v2008.04.02)

Received DESCRIBE response:
RTSP/1.0 200 OK
CSeq: 494
Date: Mon, May 26 2014 13:27:07 GMT
Content-Base: rtsp://192.168.103.51:8552/h264_ch2/
Content-Type: application/sdp
Content-Length: 509

Need to read 509 extra bytes
Read 509 extra bytes: v=0
o=- 1401092685794152 1 IN IP4 192.168.103.51
s=RTSP/RTP stream from NETRA
i=h264_ch2
t=0 0
a=tool:LIVE555 Streaming Media v2008.04.02
a=type:broadcast
a=control:*
a=range:npt=0-
a=x-qt-text-nam:RTSP/RTP stream from NETRA
a=x-qt-text-inf:h264_ch2
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=000042;sprop-parameter-sets=h264
a=control:track1
m=audio 0 RTP/AVP 96
c=IN IP4 0.0.0.0
a=rtpmap:96 PCMU/48000/2
a=control:track2

4> SessionsSetup


static int SessionsSetup( rtsp_object_t *p_demux )
{
     ... ...
// unsigned const thresh = 1000000;
if( !( p_sys->ms = MediaSession::createNew( *p_sys->env, p_sys->p_sdp ) ) )
{
TRACE1_DEC( "Could not create the RTSP Session: %s\n", p_sys->env->getResultMsg() );
return RTSP_ERROR;
}

/* Initialise each media subsession */
iter = new MediaSubsessionIterator( *p_sys->ms );
while( ( sub = iter->next() ) != NULL )
{
 ... ...
bInit = sub->initiate();

if( !bInit )
{
TRACE1_DEC( "RTP subsession ‘%s/%s‘ failed (%s)\n",
sub->mediumName(), sub->codecName(), p_sys->env->getResultMsg() );
}
else
{
              ... ...
/* Issue the SETUP */
if( p_sys->rtsp )
{
if( !p_sys->rtsp->setupMediaSubsession( *sub, False, b_rtsp_tcp, False ) )
{
/* if we get an unsupported transport error, toggle TCP
* use and try again */
if( !strstr(p_sys->env->getResultMsg(),"461 Unsupported Transport")
|| !p_sys->rtsp->setupMediaSubsession( *sub, False, b_rtsp_tcp, False ) )
{
TRACE1_DEC( "SETUP of‘%s/%s‘ failed %s\n", sub->mediumName(), sub->codecName(), p_sys->env->getResultMsg() );
continue;
}
}
}

            ... .../* Value taken from mplayer */
if( !strcmp( sub->mediumName(), "audio" ) )
{
if( !strcmp( sub->codecName(), "MP4A-LATM" ) )
{
... ...
}
else if( !strcmp( sub->codecName(), "PCMA" ) || !strcmp( sub->codecName(), "PCMU" ))
{
tk->fmt.i_extra = 0;
tk->fmt.i_codec = RTSP_CODEC_PCMU;
}
}
else if( !strcmp( sub->mediumName(), "video" ) )
{
if( !strcmp( sub->codecName(), "H264" ) )
{
... ...
}
else if( !strcmp( sub->codecName(), "MP4V-ES" ) )
{
... ...
}
else if( !strcmp( sub->codecName(), "JPEG" ) )
{
tk->fmt.i_codec = RTSP_CODEC_MJPG;
}
}
               ... ...
}
}
     ... ...
}

  这个函数做了四件事:创建MediaSession类的实例,创建MediaSubsessionIterator类的实例,MediaSubsession的初始化,发送"SETUP"请求。

  创建MediaSession实例的同时,会调用initializeWithSDP()函数去解析SDP,解析出"s="相对应的fSessionName,解析出"s="相对应的fSessionName,解析出"i="相对应的fSessionDescription,解析出"c="相对应的connectionEndpointName,解析出"a=type:"相对应的fMediaSessionType等等。创建MediaSubsession类的实例,并且加入到fSubsessionsHead链表中,从上面的SDP描述来看,有两个MediaSubsession,一个video,一个audio。

  创建MediaSubsessionIterator类的实例,并且调用reset函数,将fOurSession.fSubsessionsHead赋值给fNextPtr,也就是将链表的头结点赋值给fNextPtr。当执行while循环的时候,执行了两次,一次video,一次audio。

  initiate函数,根据fSourceFilterAddr来判断是否是SSM,还是ASM,然后调用Groupsock的不同构造函数来创建实例fRTPSocket、fRTCPSocket;然后根据协议类型fProtocolName(这个值在sdp中的“m=”)来判断是基于udp还是rtp,我们只分析RTP,如果是RTP,则根据相应的编码类型fCodecName(这个值在sdp中的“a=rtpmap:”)来判断相应的fRTPSource,这里我们创建了H264和PCMU的RTPSource实例fRTPSource;创建RTCPInstance类的实例fRTCPInstance。

  setupMediaSubsession()函数,主要是发送“SETUP”请求,通过SDP的描述,知道我们采用的是RTP协议,根据rtspOpen传入的参数streamUsingTCP来请求rtp是基于udp传输,还是tcp传输,如果是TCP传输,只能是单播,如果udp传输,则根据connectionEndpointName和传入的参数forceMulticastOnUnspecified来判断是否多播还是单播,我们的服务端值支持单播,而且传入的参数false,所以这里采用单播;组包发送“SETUP”请求:


SETUP rtsp://192.168.103.51:8552/h264_ch2/track1 RTSP/1.0
CSeq: 495
Transport: RTP/AVP;unicast;client_port=33482-33483
User-Agent: LibRTSP4 (LIVE555 Streaming Media v2008.04.02)

  服务器应答:


RTSP/1.0 200 OK
CSeq: 495
Date: Mon, May 26 2014 13:27:07 GMT
Transport: RTP/AVP;unicast;destination=14.214.248.17;source=192.168.103.51;client_port=33482-33483;server_port=6970-6971
Session: 151

  最后,如果采用TCP传输,则调用setStreamSocket()->RTPInterface::setStreamSocket()->addStreamSocket()函数将RTSP的socket值fInputSocketNum加入到fTCPStreams链表中;如果是UDP传输的话,组播地址为空,则用服务端地址保存到fDests中,如果组播地址不为空,则加入组播组。


        ... ...
     if (streamUsingTCP) {
// Tell the subsession to receive RTP (and send/receive RTCP)
// over the RTSP stream:
if (subsession.rtpSource() != NULL)
subsession.rtpSource()->setStreamSocket(fInputSocketNum, subsession.rtpChannelId);
if (subsession.rtcpInstance() != NULL)
subsession.rtcpInstance()->setStreamSocket(fInputSocketNum, subsession.rtcpChannelId);
} else {
// Normal case.
// Set the RTP and RTCP sockets‘ destination address and port
// from the information in the SETUP response:
subsession.setDestinations(fServerAddress);
}
... ...

5> play


static int Play( rtsp_object_t *p_demux )
{
... ...
if( p_sys->rtsp )
{
/* The PLAY */
if( !p_sys->rtsp->playMediaSession( *p_sys->ms, p_sys->i_npt_start, -1, 1 ) )
{
TRACE1_DEC( "RTSP PLAY failed %s\n", p_sys->env->getResultMsg() );
return RTSP_ERROR;;
}
}
... ...return RTSP_SUCCESS;
}

  playMediaSession()函数,就是发送“PLAY”请求:


PLAY rtsp://120.90.0.50:8552/h264_ch2/ RTSP/1.0
CSeq: 497
Session: 151
Range: npt=0.000-
User-Agent: LibRTSP4 (LIVE555 Streaming Media v2008.04.02)

 服务器应答:


RTSP/1.0 200 OK
CSeq: 497
Date: Mon, May 26 2014 13:27:07 GMT
Range: npt=0.000-
Session: 151
RTP-Info: url=rtsp://192.168.103.51:8552/h264_ch2/track1;seq=63842;rtptime=1242931431,url=rtsp://192.168.103.51:8552/h264_ch2/track2;seq=432;rtptime=3179210581

接着我们分析rtspRead过程:


int rtspRead(rtsp_object_t *p_obj)
{
  ... ...
if(p_sys != NULL)
{
/* First warn we want to read data */
p_sys->event = 0;
for( i = 0; i < p_sys->i_track; i++ )
{
live_track_t *tk = p_sys->track[i];if( tk->waiting == 0 )
{
tk->waiting = 1;
tk->sub->readSource()->getNextFrame( tk->p_buffer, tk->i_buffer,
StreamRead, tk, StreamClose, tk );
}
}

/* Create a task that will be called if we wait more than 300ms */
task = p_sys->scheduler->scheduleDelayedTask( 300000, TaskInterrupt, p_obj );

/* Do the read */
p_sys->scheduler->doEventLoop( &p_sys->event );

/* remove the task */
p_sys->scheduler->unscheduleDelayedTask( task );

p_sys->b_error ? ret = RTSP_ERROR : ret = RTSP_SUCCESS;
}

return ret;
}

  这个函数首先要知道readSource()函数的fReadSource的值在哪里复制,在前面的initiate()函数里面有:


      
       ... ...
       } else if (strcmp(fCodecName, "H264") == 0) {
fReadSource = fRTPSource = H264VideoRTPSource::createNew(env(), fRTPSocket,
fRTPPayloadFormat,
fRTPTimestampFrequency);
} else if (strcmp(fCodecName, "JPEG") == 0) { // motion JPEG
          ... ...
} else if ( strcmp(fCodecName, "PCMU") == 0 // PCM u-law audio
|| strcmp(fCodecName, "GSM") == 0 // GSM audio
|| strcmp(fCodecName, "PCMA") == 0 // PCM a-law audio
|| strcmp(fCodecName, "L16") == 0 // 16-bit linear audio
|| strcmp(fCodecName, "MP1S") == 0 // MPEG-1 System Stream
|| strcmp(fCodecName, "MP2P") == 0 // MPEG-2 Program Stream
|| strcmp(fCodecName, "L8") == 0 // 8-bit linear audio
|| strcmp(fCodecName, "G726-16") == 0 // G.726, 16 kbps
|| strcmp(fCodecName, "G726-24") == 0 // G.726, 24 kbps
|| strcmp(fCodecName, "G726-32") == 0 // G.726, 32 kbps
|| strcmp(fCodecName, "G726-40") == 0 // G.726, 40 kbps
|| strcmp(fCodecName, "SPEEX") == 0 // SPEEX audio
) {
createSimpleRTPSource = True;
useSpecialRTPoffset = 0;
} else if (useSpecialRTPoffset >= 0) {
  ... ...
}

if (createSimpleRTPSource) {
char* mimeType = new char[strlen(mediumName()) + strlen(codecName()) + 2] ;
sprintf(mimeType, "%s/%s", mediumName(), codecName());
fReadSource = fRTPSource = SimpleRTPSource::createNew(env(), fRTPSocket, fRTPPayloadFormat,
fRTPTimestampFrequency, mimeType,
(unsigned)useSpecialRTPoffset,
doNormalMBitRule);
delete[] mimeType;
}
}

    如果是h264编码方式,则getNextFrame函数定义在FramedSource::getNextFrame:


void FramedSource::getNextFrame(unsigned char* to, unsigned maxSize,
afterGettingFunc* afterGettingFunc,
void* afterGettingClientData,
onCloseFunc* onCloseFunc,
void* onCloseClientData)
{
// Make sure we‘re not already being read:
if (fIsCurrentlyAwaitingData) {
envir() << "FramedSource[" << this << "]::getNextFrame(): attempting to read more than once at the same time!\n";
exit(1);
}

fTo = to;
fMaxSize = maxSize;
fNumTruncatedBytes = 0; // by default; could be changed by doGetNextFrame()
fDurationInMicroseconds = 0; // by default; could be changed by doGetNextFrame()
fAfterGettingFunc = afterGettingFunc;
fAfterGettingClientData = afterGettingClientData;
fOnCloseFunc = onCloseFunc;
fOnCloseClientData = onCloseClientData;
fIsCurrentlyAwaitingData = True;

doGetNextFrame();
}

  doGetNextFrame()函数定义在MultiFramedRTPSource::doGetNextFrame():


void MultiFramedRTPSource::doGetNextFrame()
{
if (!fAreDoingNetworkReads) {
// Turn on background read handling of incoming packets:
fAreDoingNetworkReads = True;
TaskScheduler::BackgroundHandlerProc* handler = (TaskScheduler::BackgroundHandlerProc*)&networkReadHandler;
fRTPInterface.startNetworkReading(handler);
}

fSavedTo = fTo;
fSavedMaxSize = fMaxSize;
fFrameSize = 0; // for now
fNeedDelivery = True;

doGetNextFrame1();
}

  doGetNextFrame1()函数定义在MultiFramedRTPSource::doGetNextFrame1():


void MultiFramedRTPSource::doGetNextFrame1()
{
while (fNeedDelivery) {
// If we already have packet data available, then deliver it now.
Boolean packetLossPrecededThis;
BufferedPacket* nextPacket = fReorderingBuffer->getNextCompletedPacket(packetLossPrecededThis);
if (nextPacket == NULL) break;

fNeedDelivery = False;

if (nextPacket->useCount() == 0) {
// Before using the packet, check whether it has a special header
// that needs to be processed:
unsigned specialHeaderSize;
if (!processSpecialHeader(nextPacket, specialHeaderSize)) {
// Something‘s wrong with the header; reject the packet:
fReorderingBuffer->releaseUsedPacket(nextPacket);
fNeedDelivery = True;
break;
}
nextPacket->skip(specialHeaderSize);
}

// Check whether we‘re part of a multi-packet frame, and whether
// there was packet loss that would render this packet unusable:
if (fCurrentPacketBeginsFrame) {
if (packetLossPrecededThis || fPacketLossInFragmentedFrame) {
// We didn‘t get all of the previous frame.
// Forget any data that we used from it:
fTo = fSavedTo; fMaxSize = fSavedMaxSize;
fFrameSize = 0;
}
fPacketLossInFragmentedFrame = False;
} else if (packetLossPrecededThis) {
// We‘re in a multi-packet frame, with preceding packet loss
fPacketLossInFragmentedFrame = True;
}
if (fPacketLossInFragmentedFrame) {
// This packet is unusable; reject it:
fReorderingBuffer->releaseUsedPacket(nextPacket);
fNeedDelivery = True;
break;
}

// The packet is usable. Deliver all or part of it to our caller:
unsigned frameSize;
nextPacket->use(fTo, fMaxSize, frameSize, fNumTruncatedBytes,
fCurPacketRTPSeqNum, fCurPacketRTPTimestamp,
fPresentationTime, fCurPacketHasBeenSynchronizedUsingRTCP,
fCurPacketMarkerBit);
fFrameSize += frameSize;

if (!nextPacket->hasUsableData()) {
// We‘re completely done with this packet now
fReorderingBuffer->releaseUsedPacket(nextPacket);
}

if (fCurrentPacketCompletesFrame || fNumTruncatedBytes > 0) {
// We have all the data that the client wants.
if (fNumTruncatedBytes > 0) {
envir() << "MultiFramedRTPSource::doGetNextFrame1(): The total received frame size exceeds the client‘s buffer size ("
<< fSavedMaxSize << "). "<< fNumTruncatedBytes << " bytes of trailing data will be dropped!\n";
}
// Call our own ‘after getting‘ function, so that the downstream object can consume the data:
if (fReorderingBuffer->isEmpty()) {
// Common case optimization: There are no more queued incoming packets, so this code will not get
// executed again without having first returned to the event loop. Call our ‘after getting‘ function
// directly, because there‘s no risk of a long chain of recursion (and thus stack overflow):
afterGetting(this);
} else {
// Special case: Call our ‘after getting‘ function via the event loop.
nextTask() = envir().taskScheduler().scheduleDelayedTask(0, (TaskFunc*)FramedSource::afterGetting, this);
}
} else {
// This packet contained fragmented data, and does not complete
// the data that the client wants. Keep getting data:
fTo += frameSize; fMaxSize -= frameSize;
fNeedDelivery = True;
}
}
}

   FramedSource::afterGetting(FramedSource* source) :


void FramedSource::afterGetting(FramedSource* source)
{
source->fIsCurrentlyAwaitingData = False;
// indicates that we can be read again
// Note that this needs to be done here, in case the "fAfterFunc"
// called below tries to read another frame (which it usually will)

if (source->fAfterGettingFunc != NULL) {
(*(source->fAfterGettingFunc))(source->fAfterGettingClientData,
source->fFrameSize,
source->fNumTruncatedBytes,
source->fPresentationTime,
source->fDurationInMicroseconds);
}
}

  fAfterGettingFunc函数指针在FramedSource::getNextFrame()中被赋值afterGettingFunc,afterGettingFunc的值则是rtspRead()函数调用getNextFrame()函数时,传入的StreamRead()。这样就获取了一帧数据。

在MultiFramedRTPSource::doGetNextFrame()函数中,我们发现了fRTPInterface.startNetworkReading(handler),这个函数主要做了什么作用?


void RTPInterface::startNetworkReading(TaskScheduler::BackgroundHandlerProc* handlerProc)
{
// Normal case: Arrange to read UDP packets:
envir().taskScheduler().turnOnBackgroundReadHandling(fGS->socketNum(), handlerProc, fOwner);

// Also, receive RTP over TCP, on each of our TCP connections:
fReadHandlerProc = handlerProc;
for (tcpStreamRecord* streams = fTCPStreams; streams != NULL; streams = streams->fNext) {
// Get a socket descriptor for "streams->fStreamSocketNum":
SocketDescriptor* socketDescriptor = lookupSocketDescriptor(envir(), streams->fStreamSocketNum);
if (socketDescriptor == NULL) {
socketDescriptor = new SocketDescriptor(envir(), streams->fStreamSocketNum);
socketHashTable(envir())->Add((char const*)(long)(streams->fStreamSocketNum), socketDescriptor);
}

// Tell it about our subChannel:
socketDescriptor->registerRTPInterface(streams->fStreamChannelId, this);
}
}

  这个函数主要做了两个作用,一个是注册UDP
socket的读取任务函数MultiFramedRTPSource::networkReadHandler()到任务队列,一个是注册TCP
socket的读取任务函数SocketDescriptor::tcpReadHandler()到任务队列,最终还是会调用MultiFramedRTPSource::networkReadHandler()函数获取一帧数据。

Live555 分析(三):客服端,布布扣,bubuko.com

时间: 2024-10-14 10:28:54

Live555 分析(三):客服端的相关文章

Spring Cloud 客服端负载均衡 Ribbon

一.简介   Spring Cloud Ribbon 是一个基于Http和TCP的客服端负载均衡工具,它是基于Netflix Ribbon实现的.它不像服务注册中心.配置中心.API网关那样独立部署,但是它几乎存在于每个微服务的基础设施中.包括前面的提供的声明式服务调用也是基于该Ribbon实现的.理解Ribbon对于我们使用Spring Cloud来讲非常的重要,因为负载均衡是对系统的高可用.网络压力的缓解和处理能力扩容的重要手段之一.在上节的例子中,我们采用了声明式的方式来实现负载均衡.实际

瘦客户端和富客服端

今天看webservice的时候看到了这个这个瘦客服端的,之前没有听说过,所以专门去查了一些资料的解释 1 瘦客服端 瘦客户端(Thin Client)指的是在客户端-服务器网 络体系中的一个基本无需应用程序的计算机终端. 它通过一些协议和服务器通信,进而接入局域网.作为应用程序平台的Internet的到来为企业应用程序提供了一个全新的领域:一个基于 Internet/intranet的应用程序运用一个只包含一个浏览器的瘦客户端.这个浏览器负责解释.显示和处理应用程序的图形用户界面(GUI)和它

TCP/IP网络编程 学习笔记_7 --基于UDP的服务端/客服端

理解UDP UDP套接字的特点:在笔记2中讲套接字类型有提,类似信件或邮件的传输.UDP在数据传输过程中可能丢失,如果只考虑可靠性,TCP的确比UDP好.但UDP在结构上比TCP更简洁.UDP没有ACK,SEQ那样的操作,因此,UDP的性能有时比TCP高出很多.编程中实现UDP也比TCP简单.另外,虽然UDP是不可靠的数据传输,但也不会像想象中那么频繁地发生数据丢失.因此,在更重视性能而非可靠性的情况下(如传输视频,音频时),UDP是一种很好的选择.而如果是传递压缩文件则必须要用TCP,因为压缩

CXF 客服端调用报错

服务端已经发布了WSDL,现在在客服端生成web service客服端代码,在eclipse中新建一个project,然后new->web services->web service client生产客户端代码 在调用的时候报如下错误 解决:缺少axis相应的jar包,加入包: <dependency> <groupId>axis</groupId> <artifactId>axis</artifactId> <version&

mvc 防止客服端多次提交

但凡web开发中都会有户多次点击了提交按钮导致多次提交的情况,一般的集中做法 1.通过js在用户点击的时候将按钮disabled掉,但是这样并不是很可靠(我就可以跳过这个,用一个for循环 我直接自己post数据过去) 2.在生成客服端html的时候存放一个隐藏的input,input里面存放一个随机生成的值(一般为guid),服务器端会将此值保存,等用户提交的时候 判断提交过来的guid是否匹配(这种方法相对第一种安全性更高,但是如果是全静态的页面的话就不满足了,于是自己琢磨了第3中解决方案)

TCP服务器端和客服端(一)

就是一个客服端(Socket)和服务器(ServerSocket)端的链接间.我的理解是一个服务端可以链接多个客服端. 在客服端有输入流outPutStream. 用于发送数据 在服务器端有输出流.inputStream. 用于接受数据. 其他的我觉得多写几次就能够理解了.   客服端Socket package Text; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; publ

Linux 下基于多线程服务器/客服端聊天程序源码

Linux 下基于多线程服务器/客服端聊天程序,采用阻塞的socket技术,和多线程技术实现. 客服端程序:client.c #include<stdio.h> #include<stdlib.h> #include<string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h>

Android客服端和Php服务端互通,实现一套应用市场(含服务端)!

Android客服端和Php服务端互通,实现一套应用市场(含服务端) 真正的商城项目,由于个人原因无力继续下去了,代码是几个月前写的,现在放出.基本的功能都有,由于此项目不完整,我也不做多的介绍了.自己去发现吧. 服务端地址:http://dengxiaohua.hk127.wsdns.cc/app/ 后台无需输入账号和密码,直接登录即可. 下载地址:http://www.devstore.cn/code/info/822.html 运行截图:   

服务器端 和 客服端 通信的 疑问点记录

为什么作为服务器端的电脑必须有唯一的ip地址,而又为什么客服端的电脑不受唯一的ip地址限制呢? MAC地址有局限性,如果两台电脑不在同一个子网络,就无法知道对方的MAC地址(MAC地址是广播机制找到的 也就是发送一个ip附加上MAC :FF FF FF FF FF FF  对应ip的主机收到后才会返回mac地址数据  但是只能在一个子网络进行广播) 访问某一不在一个子网络的网络资源时候 请求的只是一个ip地址 没有附加要访问服务器的MAC地址 但是同时会把自身的ip地址 和 MAC地址附加上到请