基于RTSP的实现多媒体文件下载

 

一、项目需求:

1.从指定的RTSP服务器(可使用live555)下载多媒体文件

2.需将RTSP服务器返回的RTP数据包解析出来存成文件。

3.存成的文件可以使用VLC的播放器正常播放。

 

二、所需知识:

1.网络编程异步通讯。

2.RTSP协议交互。

3.RTP协议包的格式(RTCP暂不考虑)

 

三、要求:

1.使用C语言实现,不得使用第三方库。

2.源码不可以使用单一文件尽可能按功能模块下函数,存为多个文件。

四、解题思路

仔细分析了题目的要求后,得知,这就是一个socket 网络编程。而最复杂的服务器已经给我们了。我们要做的工作就是编一个客户端程序。顿时心里的压力小了许多。简单的了解到RTSP为应用层协议,用来控制实时数据的传送。然后从协议入手,渐渐的,感觉就上手了,知道该干嘛了。

RTSP协议是一种流媒体控制协议,可以多流媒体进行暂停、快进、快退等操作。本次项目中,RTSP协议编程用来从指定的RTSP服务器(可使用live555)下载多媒体文件,并将其保存为.ts文件。这次的项目目的就是实现下载的功能。简单的说就是给服务器发送信息,并得到服务器的响应。

1RTSP协议解析

本协议旨在于控制多个数据发送会话,提供了一种选择传送途径(如UDP、组播UDPTCP)的方法,并提供了一种选择基于RTP (RFC1889)的传送机制的方法。

 

1RTSP(Real Time Streaming Protocol)

实时流协议:一种流媒体控制协议,可对流媒体进行暂停、快进、快倒等操作。而它本身并不传输数据,RTSP的作用相当于流媒体服务器的远程控制。传输数据可以通过传输层的TCP/UDP协议,RTSP也提供了基于RTP传输机制的一些有效的方法。

2RTSP消息格式:
RTSP的消息有两大类,一是请求消息(request),一是回应消息(response),两种消息的格式不同。一个消息一般由头和内容组成,不过也有很多的消息是只有消息头(message head or header)而没有消息体(message body)的。
1)请求消息:
        方法 URI RTSP版本 CR LF
        消息头 CR LF CR LF         
        消息体 CR LF
    其中方法包括OPTIONS回应中所有的命令,URI是接收方(服务端)的地址,例如:rtsp://192.168.22.136:5000/v0

RTSP版本一般都是RTSP/1.0。每行后面的CR LF表示回车换行需要接收端有相应的解析最后一个消息头需要有两个CR LF

一个请求消息(a request message)即可以由客户端向服务端发起也可以由服务端向客户端发起。请求消息的语法结构如下:

Request = Request-Line

*( general-header  | request-header | entity-header)

CRLF

[message-body]

 

①请求消息的第一行的语法结构如下:

Request-Line = Method 空格 URL空格 RTSP-Version CRLF

其中在消息行中出现的第一个单词即是所使用的信令标志。目前已经有的信息标志如下:

Method  = “DESCRIBE” 

| “ANNOUNCE”

| “GET_PARAMETER”

| “OPTIONS”

| “PAUSE”

| “PLAY”

| “RECORD”

| “REDIRECT”

| “SETUP”

| “SET_PARAMETER”

| “TEARDOWN”

| extension-method

extension-method 标志

我们可以使用自己定义的信令标示符

Request-URI = “*” | absolute_URI

请使用请求媒体存放的绝对路径   rtsp://192.168.2.102/zhen.ts

RTSP-Version = “RTSP” “/” 1*DIGIT “.” 1*DIGIT

RTSP的版本号

例子

OPTION  rtsp://192.168.2.102/zhen.ts  RTSP/1.0

 

② Request Header Fields

在消息头中除了第一行的内容外,还有一些需求提供附加信息。其中有些是一定要的,后续我们会详细介绍经常用到的几个域的含义。

Request-header = Accept

| Accept-Encoding

| Accept-Language

| Authorization

| From

| If-Modified-Since

| Range

| Referer

| User-Agent

 

 

 

2响应消息

   客户端或是服务端在接收并解释一个请求消息后会回复一个消息response message给请求方

       RTSP版本 状态码 解释 CR LF
       消息头 CR LF CR LF
       消息体 CR LF
    其中RTSP版本一般都是RTSP/1.0,状态码是一个数值,200表示成功,解释是与状态码对应的文本解释

 

消息格式如下:

 Response = Status-Line

*( general-header | response-header|entity-header)

CRLF

[message-body]

[message-body]

 Status-Line

    响应消息的第一行是状态行(status-line),每个元素之间用空格分开。除了最后的CRLF之外,在此行的中间不得有CR或是LF的出现。它的语法格式如下,

Status-Line = RTSP-Version 空格 Status-Code 空格 Reason-Phrase CRLF

 

RTSP-Version = “RTSP” “/” 1*DIGIT “.” 1*DIGIT    RTSP的版本号

 

Status-Code 是一个三位数的整数,用于描述接收方对所收到请求消息的执行结果,而Reason-Phrase是对Status-Code给出一个简短的文字描述,便于我们在收到一个消息后,不用每次都去查看code的解释,而只需要看Reason-Phrase就可以大概了解当前请求的执行状态。

 

 

Status-Code的第一位数字指定了这个回复消息的种类,一共有5类:

1XX: Informational – 请求被接收到,继续处理

 2XX: Success – 请求被成功的接收,解析并接受

3XX: Redirection – 为完成请求需要更多的操作

 4XX: Client Error – 请求消息中包含语法错误或是不能够被有效执行

 5XX: Server Error – 服务器响应失败,无法处理正确的有效的请求消息

 

我们在RTSP编程中,可能遇到的状态码如下

Status-Code  = “200” :OK

| “400” :Bad Request

| “404” :Not Found

| “500” Internal Server Error

 

PS:  RTSP状态码:

状态码(Status-Code)由3位数字组成,表示请求是否被理解或被满足。这些状态码的完整定义在第十一章。原因解释(Reason-Phrase)是用简短的文字来描述状态码产生的原因。状态码用来支持自动操作,原因解释用来方便人的查看。客户端不需要检查或显示原因解释。

状态码  =     "100"      ; 继续

                |     "200"      ; OK

                |     "201"      ; 已创建 录制

                |     "250"      ; 存储空间不足  录制

                |     "300"      ; 有多个选项

                |     "301"      ; 被永久移除

                |     "302"      ; 被临时移除

                |     "303"      ; 见其他

                |     "305"      ; 使用代理

                |     "400"      ; 错误的请求

                |     "401"      ; 未通过认证

                |     "402"      ; 需要付费

                |     "403"      ; 禁止

                |     "404"      ; 没有找到

                |     "405"      ; 不允许该方法

                |     "406"      ; 不接受

                |     "407"      ; 代理需要认证

                |     "408"      ; 请求超时

                |     "410"      ; 不在服务器

                |     "411"      ; 需要长度

                |     "412"      ; 预处理失败   DESCRIBE, SETUP

                |     "413"      ; 请求实体过长

                |     "414"      ; 请求-URI过长

                |     "415"      ; 媒体类型不支持

                |     "451"      ; 不理解此参数   SETUP

                |     "452"      ; 找不到会议   SETUP

                |     "453"      ; 带宽不足   SETUP

                |     "454"      ; 找不到会话

                |     "455"      ; 此状态下此方法无效

                |     "456"      ; 此头部域对该资源无效

 |     "457"      ; 无效范围   PLAY

     |     "458"      ; 参数是只读的   SET_PARAMETER

          |     "459"      ; 不允许合控制

                |     "460"      ; 只允许合控制

                |     "461"      ; 传输方式不支持

                |     "462"      ; 无法到达目的地址

                |     "500"      ; 服务器内部错误

                |     "501"      ; 未实现

                |     "502"      ; 网关错误

                |     "503"      ; 无法得到服务

                |     "504"      ; 网关超时

                |     "505"      ; 不支持此RTSP版本

                |     "551"      ; 不支持选项

                |     扩展码

扩展码 = 3位数字

未标识的表示所有适用

原因解释 = *<</span>文本包括 CR, LF>

 

Response Header Fields

      在响应消息的域中存放的是无法放在Status-Line,而又需要传送给请求者的一些附加信息。

Response-header  = Location

| Proxy-Authenticate

| Public

| Retry-After

| Server

| Vary

| WWW-Authenticate

 

2)简单的rtsp交互过程:

      C表示rtsp客户端,S表示rtsp服务端

第一步:查询服务器端有哪些方法可用

1.C->S:OPTION request       //询问S有哪些方法可用

1.S->C:OPTION response    //S回应信息中包括提供的所有可用方法

 

第二步:得到媒体的描述信息

2.C->S:DESCRIBE request      //要求得到S提供的媒体初始化描述信息

2.S->C:DESCRIBE response    //S回应媒体初始化描述信息,主要是sdp

 

第三步:建立RTSP会话

3.C->S:SETUP request             //设置会话的属性,以及传输模式,提醒S建立会话

3.S->C:SETUP response          //S建立会话,返回会话标识符,以及会话相关信息

 

第四步:请求开始传送数据

4.C->S:PLAY request           //C请求播放

4.S->C:PLAY response            //S回应该请求的信息

  

第五步:服务器发送数据,客户端开始接收数据

5.S->C:发送流媒体数据  //服务器开始不断的向客户端发送数据,RTP

 

第六步:关闭会话退出

6.C->S:TEARDOWN request      //C请求关闭会话

6.S->C:TEARDOWN response //S回应该请求

 

RTSP协议最基本的了解的差不多了,接下去我就在考虑如何拼接要发送给服务器的信息内容。经过一番考虑后。发现用strcpysprintf就能够完成这个工作。第一次用到sprintf,发现和fprintfprintf的用法相类似,只不过fprintf是打印在文件中,而printf是打印在屏幕上。考虑到这里,心里便明了了许多。知道该怎么拼接头部信息。

下面从程序上讲讲思路:

1) 创建TCP socket,用来与服务器连接。这个项目中,我是用TCP来连接,用UDP进行数据传输。端口号为554ip为服务器所在主机的ip

if((sock_fd = socket(AF_INET,SOCK_STREAM,0)) == -1)

{

perror("creat fail\n");

exit(1);

}

bzero(&ser_add,sizeof(ser_add));

ser_add.sin_family = AF_INET;

ser_add.sin_port = htons(PORT);

inet_pton(AF_INET,url_str,&ser_add.sin_addr.s_addr); //初始化TCPsocket

connect_fd = connect(sock_fd,(struct sockaddr *)&ser_add,sizeof(struct sockaddr));

 

这样连接服务器的socket就建好了。

2)下面开始给服务器发送消息:

OPTION方法

strcpy(szcmd,GetOptionCmd(url));//获取发送信息

  SendRTSPCmd(sock_fd,"OPTIONS",szcmd);//发送option请求消息

  memset(szcmd,0,100);

  memset(recv_buf,0,sizeof(recv_buf));

  RecvRTSPCmd(sock_fd,recv_buf, nlen,2);//receive option from servier

  printf("recv_buf:\n"); //show recv_buf

  printf("%s\n",recv_buf);

 

这里调用的是函数,函数体请见源程序

打印的OPTION请求消息如下

OPTIONS rtsp://192.168.1.102/zhen.ts RTSP/1.0

    CSeq:1

User-Agent:rtsp client(v1.0)

 

OPTION:方法

rtsp://192.168.1.102/zhen.tsURL

URLRTSP通过与HTTP相似的方式来定义URLRTSP完整的URL定义如下:      rtsp URL = ( rtsp: | rtspu: | rtsps: )      // host [ : port ] [ abs path ]      rtsp  = 使用可信的底层传输协议,例如TCP      rtspu = 使用不可信的底层传输协议,例如UDP      rtsps = 使用可信加密传输协议,例如TCP + TLS

RTSP/1.0:版本号

CSep: Cseq域指定一对RTSP请求-响应消息的序列号。在请求消息及响应消息中一定要指定这个域。对于请求消息,会有一个具有相同Cseq域内容的响应消息与之对应。请求消息与相应消息的CSeq是对应的。

User-Agent;这个域用于用户标识,不同公司或是型号的手机发出的消息中的这个域的内容都不大相同。有时会指出手机的版本号,播放器的型号等等。这里用rtsp client(v1.0)作为用户标识。

 

服务器响应消息如下:

RTSP/1.0 200 OK

CSeq: 1

Date: Sun, Mar 04 2012 06:16:39 GMT

Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER

 

RTSP/1.0  版本信息

200      状态码,表示成功,OK

OK:      Reason-Phrase 状态码的描述信息,简略的描述状态码的返回原因

CSep   1,与请求消息中的CSep相对应

Data:   系统时间 Sun, Mar 04 2012 06:16:39 GMT

Public:    返回消息给出的能够使用的方法 这里可以使用的方法有:

OPTIONS , DESCRIBE,  SETUP,  TEARDOWN,  PLAY,  PAUSE,    GET_PARAMETER, SET_PARAMETER

 

 

DESCRIBE方法

    strcpy(szcmd,GetDescribeCmd(url)); //get  send message

  SendRTSPCmd(sock_fd, "DESCRIBE", szcmd); //send message of describe

  memset(szcmd,0,100);

  memset(recv_buf,0,sizeof(recv_buf));

  RecvRTSPCmd(sock_fd,recv_buf,nlen,2); //receive from server

  printf("recv_buf:\n"); //show recv buf

  printf("%s\n",recv_buf);

DESCRIBE请求消息如下

DESCRIBE rtsp://192.168.1.102/zhen.ts RTSP/1.0

CSeq: 2

User-Agent:rtsp client(v1.0)

 

具体参数参照OPTION

 

DESCRIBE响应消息中,最主要的就是回应媒体初始化描述信息,主要是sdp

Sdp:信息是文本信息,UTF-8 编码采用 ISO 10646 字符设置。SDP 会话描述如下(标注*符号的表示可选字段):

v= (协议版本) 

o= (所有者/创建者和会话标识符) 

s= (会话名称) 

i=* (会话信息) 

u=* URI 描述) 

e=* Email 地址 

p=* (电话号码) 

c=* (连接信息 ― 如果包含在所有媒体中,则不需要该字段) 

b=* (带宽信息) 

  一个或更多时间描述(如下所示):

\ z=* (时间区域调整) 

k=* (加密密钥) 

a=* 0个或多个会话属性线路) 

  0个或多个媒体描述(如下所示) 

  时间描述

 t= (会话活动时间) 

 r=* 0或多次重复次数) 

  媒体描述

 m= (媒体名称和传输地址) 

 i=* (媒体标题) 

 c=* (连接信息 — 如果包含在会话层则该字段可选) 

 b=* (带宽信息) 

 k=* (加密密钥) 

 a=* 0个或多个会话属性线路)

 

DESCRIBE 响应消息如下:

v=0

o=- 1330841352577768 1 IN IP4 192.168.121.1

s=MPEG Transport Stream, streamed by the LIVE555 Media Server    //会话名称

i=zhen.ts //媒体标题

t=0 0 //会话活动时间

a=tool:LIVE555 Streaming Media v2011.11.20

a=type:broadcast

a=control:*

a=range:npt=0-  //range npt= 0- 表示从头开始播放

a=x-qt-text-nam:MPEG Transport Stream, streamed by the LIVE555 Media Server

a=x-qt-text-inf:zhen.ts

m=video 0 RTP/AVP 33       //媒体名称vedio RTP/AVP 表明传输方式为UDP(缺省)

c=IN IP4 0.0.0.0

b=AS:5000 //带宽信息

a=control:track1

 

.SETUP方法

sock_udp_fd1 = InitUDPSocket(port1); //新建两个接收端口

  sock_udp_fd2 = InitUDPSocket(port2);

  strcpy(szcmd,GetSetupCmd(url,port1,port2)); //get send message

  SendRTSPCmd(sock_fd, "SETUP", szcmd); //send message of SETUP

  memset(szcmd,0,100);

  memset(recv_buf,0,sizeof(recv_buf));

  RecvRTSPCmd(sock_fd,recv_buf,nlen,2); //receive from server

  memset(session,0,8);

  prass_setup_recv(recv_buf,session);//解析响应消息得到session

  printf("recv_buf:\n");  //show recv buf

  printf("%s\n",recv_buf);

客户端提醒服务器建立会话,并确定传输模式

我所写代码的SETUP方法请求消息如下:

SETUP rtsp://192.168.1.102/zhen.ts RTSP/1.0

CSeq: 3

Transport: RTP/AVP;unicast;client_port=6544-6545

User-Agent:rtsp client(v1.0)

6544:接收RTP包的端口

5545:接收RTCP包的端口

Transport这个域在请求消息中是标识哪一种传输协议将被使用,并指定一些在描述说明中没有指定的参数的值。使用的传输协议之间用逗号分隔,参数之间用分号分隔

Transport参数设置了传输模式。RTP/AVP/TCP表示通过TCP传输RTP包,RTP/AVP表示使用UDP传输RTP包。unicast表示单播。interleaved值有两个:010表示RTP包,1表示RTCP包,接收端根据interleaved的值来区别是哪种数据包。client_port值有300830093008表示客户端接收RTP包的端口,3009表示客户端接收RTCP包的端口,服务端要分别将RTP包和RTCP包发送到这两个端口。如下:

(1)TCP模式
SETUP rtsp://192.168.20.136:5000/v0/streamid=0 RTSP/1.0    
CSeq: 3
Authorization: Basic YWRtaW46YWRtaW4=
Transport: RTP/AVP/TCP;unicast;interleaved=0-1      
User-Agent: bestilyq

(2)UDP模式
SETUP rtsp://192.168.20.136:5000/v0/streamid=0 RTSP/1.0
CSeq: 3
Transport: RTP/AVP;unicast;client_port=3008-3009
Authorization: Basic YWRtaW46YWRtaW4=
User-Agent: bestilyq

 

本项目中,服务器的响应消息如下:

RTSP/1.0 200 OK

CSeq: 1

Date: Sun, Mar 04 2012 08:13:33 GMT

Transport:RTP/AVP;unicast;destination=192.168.1.113;source=192.168.1.102;client_port=6544-6545;server_port=6970-6971

Session: 0CCD8072

Transport解析:

RTP/AVP:此处缺省了UDP,表示是用UDP进行数据传输

destination=192.168.1.113:目标IP,虚拟机中系统IP

source=192.168.1.102IP,服务器所在的主机的IP

client_port=6544-6545:客户端的接收端口,6544,接收RTP包,6545接收RTCP

server_port=6990-6971:服务器的发送端口。

 

Session:头字段标识了一个RTSP会话。Session ID 是由服务器在SETUP的回应中选择的,客户端一当得到Session ID后,在以后的对Session 的操作请求消息中都要包含Session ID.

解析SETUP的消息得到session,我用了最土的办法。稍后尝试下用strstr函数。

int prass_setup_recv(char *recv_setup_buf,char *pstr)

{

int i = 0;

if(recv_setup_buf == NULL)

{

perror("recv buf is NULL \n");

return -1;

}

while(*recv_setup_buf != '\0')

{

if(*recv_setup_buf == 'o'&& *(recv_setup_buf+1) =='n'&&*(recv_setup_buf+2) == ':'&&*(recv_setup_buf+3) ==' ')

{

memset(pstr,0,8);

recv_setup_buf = recv_setup_buf+4;

for( i = 0 ; i < 8 ;i++)

{

pstr[i] = recv_setup_buf[i];

}

break; 

}

else

recv_setup_buf++;

}

return 0;

}

 

TCP接收数据时与UDP接收数据时,服务器的响应消息对比如下:

(1) TCP模式

RTSP/1.0 200 OK

CSeq: 3

Date: Sat Feb  5 22:35:27 2009 GMT

Session: a522bbb4335617db

Transport: RTP/AVP/TCP;interleaved=0-1

 

(2)UDP模式

RTSP/1.0 200 OK

CSeq: 3

Date: Sat Feb  5 22:49:39 2009 GMT

Session: 01fa4ca2566a6301      //服务器回应的会话标识符

Transport: RTP/AVP/UDP;unicast;client_port=3008-3009;server_port=1024-1025

 

 

 

④ PLAY方法解析

strcpy(play_buf,"Range: npt=0.000\r\n");

  strcpy(szcmd,GetPlayCmd(url,session,play_buf)); //get send message

  SendRTSPCmd(sock_fd, "PLAY", szcmd); //send message of PLAY

  memset(szcmd,0,100);

  memset(recv_buf,0,sizeof(recv_buf));

  RecvRTSPCmd(sock_fd,recv_buf, nlen, 2); //recvive from server

  printf("recv_buf:\n"); //show recv buf

  printf("%s\n",recv_buf);

 

session:是从SETUP的响应消息中解析得到的。

 

PLAY请求消息如下:

PLAY rtsp://192.168.1.102/zhen.ts RTSP/1.0

CSeq: 4

Session: 0CCD8072

Range: npt=0.000

User-Agent:rtsp client(v1.0)

 

Range: Range用于在请求消息和响应消息中指定播放的时间段。Range头可能包含一个时间参数。该参数以UTC格式指定了播放开始的时间。如果在这个指定时间后收到消息,那么播放立即开始。时间参数可能用来帮助同步从不同数据源获取的数据流。

不含Range头的PLAY请求也是合法的。它从媒体流开头开始播放,直到媒体流被暂停。如果媒体流通过PAUSE暂停,媒体流传输将在暂停点(the pause point)重新开始

此处表示从头开始播放。

 

 

 

 

 

PLAY 服务器响应消息:

RTSP/1.0 200 OK

CSeq: 4

Date: Sun, Mar 04 2012 09:32:53 GMT

Range: npt=0.000-

Session: 0CCD8072

RTP-Info: url=rtsp://192.168.1.102/zhen.ts/track1;seq=1787;rtptime=4008936136

服务器回应的是一个RTP

RTP-Info:这个域用于在回复PLAY消息中指定RTP特殊的参数

url: 与设置的RTP参数对应的流媒体链接

seq: 流媒体第一个包的序列号

rtptime: 用于回复range域对应的RTP时间戳

RTP-Info语法结构: 

  RTP-Info       = "RTP-Info" ":" 1#stream-url 1*parameter

   stream-url      = "url" "=" url

   parameter      = ";" "seq" "=" 1*DIGIT

                   | ";" "rtptime" "=" 1*DIGIT

 

PLAY方法接收到服务器的响应消息后,服务器就开始不停的发送数据给客户端,客户端不断的接收数据。

客户端接收数据的函数如下

int recv_data_from_serv(int sock_udp_fd1,int sock_udp_fd2,char *filename)

{

  struct sockaddr_in udp_addr;

  struct timeval tm;

  int maxfd =0;

  int filelen = 0;

  int fd = 0;

  int i = 0;

int udp_addr_len = 0;

tm.tv_sec = 5;

  tm.tv_usec = 0;

  fd_set read_fd; 

FD_SET(sock_udp_fd1,&read_fd);

  FD_SET(sock_udp_fd2,&read_fd);

int select_ret = 0;

  if( sock_udp_fd1 >= sock_udp_fd2)//find max fd

  {

  maxfd = sock_udp_fd1;

  }

  else 

  maxfd = sock_udp_fd2;

if((fd = open(filename, O_CREAT|O_WRONLY)) < 0)

{

perror("open zhen.ts error!\n");

return -1;

}

udp_addr_len = sizeof(udp_addr);

while(1)

{

select_ret = select(maxfd+1,&read_fd,NULL,NULL,&tm);

if(select_ret < 0)

{

perror("no such fd\n");

continue;

if(select_ret == 0)

{

printf("time out\n");

continue;

}

  if( FD_ISSET(sock_udp_fd1,&read_fd))

{

 memset(zhen_buf,0,sizeof(zhen_buf));

 memset(recv_buf,0,sizeof(recv_buf));

 filelen = recvfrom(sock_udp_fd1,recv_buf,sizeof(recv_buf),0,(struct sockaddr*)&udp_addr, &udp_addr_len);

 printf("sizoe of recvbuf is %d\n",sizeof(recv_buf));

 printf("filelen1:%d\n",filelen);

 if(filelen < 12)

{

perror("recv error\n");

continue;

}

filelen = filelen - 12;

write(fd,(zhen_buf+12),filelen);

printf("new filelen:%d\n",filelen);  

}

 

if( FD_ISSET(sock_udp_fd2,&read_fd))

{

 memset(zhen_buf,0,sizeof(zhen_buf));

 memset(recv_buf,0,sizeof(recv_buf));

 filelen = recvfrom(sock_udp_fd2,recv_buf,sizeof(recv_buf),0, (struct sockaddr*)&udp_addr, &udp_addr_len);

 

 printf("sizeof recvbuf is :%d\n",sizeof(recv_buf));

// recv_buf[filelen] = '\0';

if(filelen < 12)

{

perror("recv error\n");

continue;

}

filelen = filelen - 12;

write(fd,(zhen_buf+12),filelen);  

}                                   

tm.tv_sec = 5;

  tm.tv_usec = 0;

  printf("recv num is NO i:%d\n",i++);

 

}

 

}

 

转载于:https://my.oschina.net/mickelfeng/blog/168035

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值