Linux Socket学习(十八)--完 - Linux Socket - 乘风破浪会有...

来源:百度文库 编辑:神马文学网 时间:2024/04/27 17:04:24
不论我们的头脑是否在由上一章的学习中清醒过来,现在我们需要休息一下了。在这一章我们并不讨论新的内容,而是用我们所学到的这些东西来实现一些有趣的事情。在学习了这么多的东西之后来一些有趣的东西是十分重要的。
在这一章,我们将会:
应用TCP/IP套接口从网上下载股票行情信息
应用UDP广播和我们的局域网内发布股票行情信息
使用UDP客户端程序来接收局域网广播的股票行情信息
问题描述
在提供解决方案之前进行问题描述是一个很好的习惯。所以,在这里我们要描述一下在这一章我们将会解决的问题。
我们有一个全职或是兼职的小公司。我们的办公室很小,所以免费得到一些行情信息是十分重要的。另外,我们需要关注我们的主机也网络提供商之间的网络拥塞,因为我们还需要为其他的事情准备网络带宽。同时,我们的公司职员也坚持得到最好的免费的行情信息。
解决行情服务问题
由问题描述我们可以很清楚的看出我们需要由行情提供者处得到一组行情信息。我们没有理由使得我们的公司员工使用单独的TCP/IP连接到同一个行情提供者来获得同样的信息。这会耗尽宝贵的网络带宽。从免费的行情提供者的角度来看,这也是不必要的。
一个本地服务器程序可以持续的为大家获取更新的股票市场行情信息。然后,这个信息可以播报到网络中的所有参与者。这就是我们在这一章将会提供的解决方案。这个程序也会使得我们回忆复习流式套接口与数据报套接口。
得到股票市场行情
我们要使用的市场行情信息的数据源为finance.yahoo.com。在这一部分,我们将会了解程序如何由finance.yahoo.com获取行情信息。
为了确定如何猎取市场行情,我们可以使用网络浏览器来访问htt://finance.yahoo.com。此时,网站会提供给我们一个页面,允许我们输入符号并且点击Get Quotes按钮。输入RHAT并且按下按钮会将我们引入另一个页面,在这里我们可以得到关于Red Hat Inc的详细信息。在行情信息的底部,我们可以看到一个名为Download Spreadsheet Format的链接。这就是金矿所在。
在这里,我们有三个选择:
将鼠标移动该链接上,并且注意在状态行所显示的URL信息
从菜单中选择View->Page Source。查看整个HTML代码,找到如下的部分:Download Spreadsheet Format
在Netscape中获取这个信息的最好方法就是在链接上点击右键。从弹出菜单中选择Copy Link Location。这会将链接引用拷贝到剪切板中。然后,我们可以将这个信息粘贴到文件中。
从这个信息,我们可以得到我们需要的全部分内容。我们可以使用telnet来试验这些内容:
$ telnet finance.yahoo.com 80
Trying 204.71.201.75 . . .
Connected to finance.yahoo.com.
Escape character is '^]'.
GET /d/quotes.csv?s=RHAT&f=sl1d1t1c1ohgv&e=.csv
"RHAT",168.9375,"11/24/1999","4:00PM",0,147.5625,175.5,145.6875,3061700
Connection closed by foreign host.
$
在我们连接之后执行GET命令,同时指定一个奇怪的路径名并按下回车键。如果命令执行成功,我们就会得到一行扩展数据。将RHAT替换为另一个代码我们就会得到不同的数据。
如果我们没有这样好的运气,那么我们需要检查一下我们的拼写。精确在这里是非常重要的。如果可能可以使用剪切粘贴的操作。如果仍不起作用,那么我们需要查看一下finance.yahoo.com现在是如何工作的。由前面我们所指出的步骤框架我们可以发现所需要的新的主机名与路径名。
下面是行情获取步骤的小结:
1 在80商品连接到finance.yahoo.com
2 使用所显示的路径名来执行GET请求
3 一行表格格式数据会作为响应返回套接口
4 关闭套接口
这就是所有的步骤。当我们检测后面所列出的代码时我们要记住这些步骤。
后面的章节内容会演示并描述组成服务器与客户端程序所需要的各个模块。然而由于空间的原因,我们在这里并不能列出全部的代码。
下表列出了我们所需要的源码文件。其中一些有趣的部分会在这一章进行相关的描述。
源文件        描述
Makefile    工程make文件
bcast.c        实现执行向局域网广播的函数
connect.c    实现执行连接到远程行情服务器的函数
csvparse.c    分析返回的行情数据
gettick.c    从远程网行情服务器得到行情信息
load.c        由文件tickers.rc装入股票市场代码
misc.c        一些小函数
mkaddr.c    网络地址函数
mkwatch.c    市场监测客户端程序。这个程序会接收本地广播信息并显示
msgf.c        实现本地行情服务器的模块
quotes.h    所有源模块所用的通常头文件
tickers.rc    股票代码文件
检测行情服务器程序
我们将要开始检测源码模块为qserve.c源码模块。这个模块为行情服务器本身形成主程序。他负责获取股票市场行情并且向局域网进行广播。下面列出了qserve.c的源代码:
/*
* qserve.c:
*
* Stock Quote Connentrator Program:
*/
#include "quotes.h"
static char *command = NULL;
/*
* Remote Quote Server Address */
static char *cmdopt_a = DFLT_SERVER;
/*
* Quote Re-Broadcase Address
*/
static char *cmdopt_b = DFLT_BCASE;
/*
* Ticker Table:
*/
static TickReq tickers[MAX_TICKERS];
static int ntick=0;
/*
* Return server usage information:
*/
static void usage(void)
{
printf("Usage: %s [-h] [-a address:port]\n"
"where:\n"
"\t-h\t\tRequests usage info.\n"
"\t-a address:port\tSpecify "
"the server\n"
"\t\t\taddress and port number.\n"
"\t-b bcastport \tSpecify "
"the broadcase\n"
"\t\t\taddress and port number.\n",
command);
}
/*
* Server main program:
*/
int main(int argc,char **argv)
{
int rc=0;        /* Return code */
int optch;        /* Option char */
int z;            /* Status code */
int x;            /* Index */
int s;            /* Broadcast socket */
time_t tn=0;    /* Time Next */
time_t zzz;        /* Sleep Time */
time_t td;        /* Time & Date */
struct sockaddr_in bc_addr;        /* bc addr */
socklen_t bc_len;                /* bc addr len */
const int True = TRUE;            /* const TRUE */
static char cmdopts[]="ha:b:";
/*
* Process command line options:
*/
command = Basename(argv[0]);
while((optch = getopt(argc,argv,cmdopts)) != -1)
switch(optch)
{
case 'h':        /* -h for help */
usage();
return rc;
case 'a':        /* -a quote_server */
cmdopt_a = optarg;
break;
case 'b':        /* -b broadcast_addr */
cmdopt_b = optarg;
break;
default:
/* Option error */
rc=1;
}
/* check for option errors: */
if(rc)
{
usage();
return rc;
}
/*
* Form the broadcast server address:
*/
bc_len = sizeof bc_addr;        /* Max len */
z = mkaddr(
&bc_addr,        /* Returned addr */
&bc_len,        /* Returned len */
cmdopt_b,        /* Input address */
"udp");            /* UDP protocol */
if(z==-1)
{
msgf('e',"%s: -b %s",
strerror(errno),
cmdopt_b);
return 1;
}
/*
* Create a UDP socket to use:
*/
s = socket(PF_INET,SOCK_DGRAM,0);
if(s==-1)
{
msgf('e',"%s: socket(PF_INET,"
"SOCK_DGRAM,0)",
strerror(errno));
return 1;
}
/*
* Allow broadcasts on socket s:
*/
z = setsockopt(s,
SOL_SOCKET,
SO_BROADCAST,
&True,
sizeof True);
if(z==-1)
{
msgf('e',"%s: setsockopt(SO_BROADCAST)",
strerror(errno));
return 1;
}
/*
* Load tickers form tickers.rc:
*/
if(load(&tickers[0],&ntick,MAX_TICKERS))
goto errxit;
/*
* Now monitor the remote quote server:
*/
for(;;)
{
tn = 0;        /* Refresh tn */
time(&td);    /* current time */
/*
* Loop for all tickers:
*/
for(x=0;x{
/*
* Skip tickers that are either
* unknown,or are producing parse
* errors in the returned data:
*/
if(tickers[x].flags & FLG_UNKNOWN
|| tickers[x].flags & FLG_ERROR)
continue;        /* Ignore this */
/*
* Pick up the earliest "next" time:
*/
if(!tn
|| tickers[x].next_samp < tn)
tn = tickers[x].next_samp;
/*
* if the current time is > than
* the "next" time,it is time to
* fetch an update for this ticker:
*/
if(td>=tickers[x].next_samp)
{
/*
* Get Quote Update:
*/
z = get_tickinfo(
&tickers[x],cmdopt_a);
/*
* comute time for the next
* update for this ticker:
*/
time(&tickers[x].next_samp);
tickers[x].next_samp += tm;
/*
* if the uote fetch was OK,
* then broadcast its info:
*/
if(!z)
broadcast(s,&tickers[x],
(struct sockaddr *)&bc_addr,
bc_len);
}
}
/*
* Here the interval between updates is
* progressively increased to 5 minutes
* max.This provides a lot of initial
* acction for demonstration puoposes,
* without taxing the friendly quote
* providers if this program is run all
* day.Abuse will only force the kind
* providers to change things to break
* the operation of this program!
*/
if(tm < (time_t)5*60)
tm += 5;    /* progressively increase */
/*
* compute how long we need to snooze.
* the time to the next event is
* computed-sleep(3) is called if
* necessary:
*/
if(!tn)
tn = td + tm;
if(tn>=td)
if((zzz = tn-td))
sleep(zzz);
}
return rc;
/*
* Error Exit:
*/
errxit:
return rc=2;
}
通过get_tickinfo()函数获取行情信息
这一部分将会检测gettkick.c源码模块,从而我们可以看到如何通过C代码来获取行情信息。在显示这个模块之前,我们需要检测一些会用到的引用结构。下面列出了在这个工程中一些源码模块会用到的quotes.h头文件:
/*
* quotes.h:
*
* Project header file:
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
* Default Quote Server:
*/
#define DFLT_SERVER "finance.yahoo.com:80"
/*
* Default Broadcast Address:
*/
#define DFLT_BCAST "127.255.255.255:977"
/*
* *.CSV Parsing Parameter:
*/
typedef struct
{
char type;        /* 'S' or 'D' */
void *parm;        /* Ptr to parameter */
} Parm;
/*
* Timeout on Quote Fetch:
*/
#define TIMEOUT_SECS 10
/*
* Ticker load file:
*/
#define TICKPATH    "tickers.rc"
/*
* Maximum number of tickers:
*/
#define MAX_TICKERS    256
/*
* Ticker length:
*/
#define TICKLEN    8
/*
* Date Length:
*/
#define DTLEN    10
/*
* Time field length:
*/
#define TMLEN    7
/*
* Define TRUE & FALSE if not defined:
*/
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
/*
* Ticker Request Structure:
*/
typedef struct
{
char ticker[TICKLEN+1];    /* Symbol */
double last_trade;        /* Last Price */
char *date;                /* Date */
char *time;                /* Time of Last Trade */
double change;            /* +/- change */
double open_price;        /* Opening Price */
double high;            /* High Price */
double low;                /* Low Price */
double volume;            /* Volume of Trades */
int flags;                /* Server flags */
time_t next_samp;        /* Time of next evt */
} TickReq;
/*
* Ticker Flags:
*/
/* Ticker unknown */
#define     FLG_UNKNOWN 1
/* Data format error */
#define     FLG_ERROR 2
/*
* External Function References:
*/
extern int load(TickReq *tick,int *pntick,int nmax);
extern int extract_parms(Parm *plist,short n,char *src);
extern void msgf(char type,const char *format,...);
extern int Connect(const char *addr);
extern int mkaddr(void *addr,
int *addrlen,
char *str_addr,
char *protocol);
extern char *Basename(char *cmd);
extern char *strtick(char *str);
extern int get_tickinfo(TickReq *req,char *addr);
extern void broadcast(
int s,TickReq *quote,struct sockaddr *bc_addr,
socklen_t bc_len);
有了头文件,现在我们就可以检测gettick.c源码模块了。
/*
* gettick.c
*
* Get ticker info from inet:
*/
#include "quotes.h"
/*
* f is set TRUE when a request
* for a stock quote has timed
* out.
*/
static int f = FALSE;
/*
* Catch SIGALRM and Timeout:
*/
static void sig_ALRM(int signo)
{
f = TRUE;    /* Mark timeout */
}
/*
* Get ticker info:
*
* Returns:
* 0    success
* -1    failed
*
* errno:
*  ETIME    Timed out
*  EBADMSG    Failed data format
*  other    Network/system errors
*/
int get_tickinfo(TickReq *req,char *addr)
{
int z,er;        /* Status,errno */
int s;            /* Socket */
int n;            /* Byte count */
char buf[256];    /* Receive buffer */
char *tkr=NULL;    /* Extracted ticker */
struct sigaction
sa_new,        /* New signal action */
sa_old;        /* Saved signal action */
Parm parms[9];    /* Data parse table */
/*
* Initialize parsing parameters.This
* parameter list will need modification
* if yahoo or your quote provider uses
* a different format:
*/
parms[0].type = 'S';    /* String */
parms[0].parm = &tkr;    /* Ticker name */
parms[1].type = 'D';    /* Double */
parms[1].parm = &req->last_trade;
parms[2].type = 'S';
parms[2].parm = &req->date;
parms[3].type = 'S';
parms[3].parm = &req->time;
parms[4].type = 'D';
parms[4].parm = &req->change;
parms[5].type = 'D';
parms[5].parm = &req->open_price;
parms[6].type = 'D';
parms[6].parm = &req->high;
parms[7].type = 'D';
parms[7].parm = &req->low;
parms[8].type = 'D';
parms[8].parm = &req->volume;
/*
* Initialize to cat SIGALRM:
*/
sa_new.sa_handler = sig_ALRM;
sigemptyset(&sa_new.sa_mask);
sa_new.sa_flags = 0;
sigaction(SIGALRM,&sa_new,&sa_old);
/*
* Connect to finance.yahoo.com:
*/
f = FALSE;
alarm(TIMEOUT_SECS);
s = Connect(addr);
if(s==-1)
goto errxit;
/*
* Send GET request:
* Note: This is subject to change
* if finance.yahoo.com changes,you
* will need to adjust this formatting.
*/
sprintf(buf,"GET /d/quotes.csv?"
"s=%s"
"&f=slldltlclohgv"
"&e=.csv\r\n",
req->ticker);
write(s,buf,strlen(buf));
shutdown(s,1);
/*
* Read response with a timeout:
*/
do
{
z = read(s,buf,sizeof buf);
} while(!if && z==-1 && errno==EINTR);
er = errno;        /* Save error */
alarm(0);        /* Diable timeout */
close(s);        /* Close socket */
/* Restore the signal action */
sigaction(SIGALRM,&sa_old,NULL);
if(!f && z>0)
n=z;    /* Read n bytes OK */
else
{
if(f)            /* Timeout ? */
er = ETIME;    /* Yes - timeout */
/*
* Report error to log:
*/
msgf('e',"%s: Get ticker '%s'",
strerror(er),
req->ticker);
errno = er;        /* For caller */
return -1;    /* Failed */
}
/*
* Remove CR,LF,or CRLF */
buf[strcspn(buf,"\r\n")]=0;
/*
* check for the unknown ticker case:
*/
if(strstr(buf,"N/A,N/A,N/A,N/A,N/A"))
{
msgf('e',"Unknown Ticker: '%s'",
req->ticker);
req->flags |= FLG_UNKNOWN;
errno = EBADMSG;    /* For caller */
return -1;            /* Failed */
}
/*
* Parse quote results:
*/
if((z=extract_parms(parms,9,buf))<0)
{
/*
* Report failed parse of data */
msgf('e',"Field # %d: '%s'",z,buf);
req->flags |= FLG_ERROR;
errno = EBADMSG;    /* For caller */
return -1;            /* Failed */
}
/*
* Capture the exact case for this ticker
*/
strncpy(req->ticker,tkr,TICKLEN)[TICKLEN]=0;
/*
* Update sample time in entry:
*/
return 0;
/*
* Error Exit:
*/
errxit:
alarm(0);
sigaction(SIGALRM,&sa_old,NULL);
return -1;
}
下面将会检测服务器程序所调用的broadcast()函数。
通过broadcast()广播
服务器通过调用名为broadcast()函数来与本地的网络客户端共享他的得到的信息。这个函数的代码如下:
/*
* bcast.c:
*
* Broadcast Ticker Updates
*/
#include "quotes.h"
void broadcast(
int s,        /* Socket */
TickReq *quote,    /* Quote */
struct sockaddr *bc_addr,    /* addr */
socklen_t bc_len)    /* addr len */
{
int z;    /* status */
char buf[2048];    /* buffer */
char *cp = buf;    /* buf. ptr */
int msglen;        /* message length */
/*
* Format a datagram for broadcast:
*/
strcpy(buf,quote->ticker);
cp = buf + strlen(buf) +1;
sprintf(cp,"%E",quote->last_trade);
cp += strlen(cp)+1;
strcpy(cp,quote->date);
cp += strlen(cp)+1;
strcpy(cp,quote->time);
cp += strlen(cp)+1;
sprintf(cp,"%E",quote->change);
cp += strlen(cp)+1;
sprintf(cp,"%E",quote->open_price);
cp += strlen(cp)+1;
sprintf(cp,"%E",quote->high);
cp += strlen(cp)+1;
sprintf(cp,"%E",quote->low);
cp += strlen(cp)+1;
sprintf(cp,"%E",quote->volume);
cp += strlen(cp)+1;
msglen = cp-buf;
/*
* Broadcast the datagram:
*/
z = sendto(s,buf,msglen,0,bc_addr,bc_len);
if(z==-1)
msgf('e',"%s:sendto(2)",
strerror(errno));
}
检测客户端程序
客户端程序必须将其自身绑定到正在使用的广播地址,从而可以接收到广播市场行情信息。mktwatch.c模块代码如下:
/*
* mktwatch.c:
* Get datagram stock market
* quotes from central quotes
* server:
*/
#include "quotes.h"
/*
* -b option (broadcast) address:
*/
static char *cmdopt_b = DFLT_BCAST;
/*
* Dispaly command useage:
*/
static void usage(void)
{
puts("Usage:\tmktwatch [-b bcast]");
puts("where:");
puts("\t-b bcast\tBroadcast address");
}
/*
* Extract ticker information from
* broadcast datagram packet:
*/
static int extract(char *dgram,TickRe *tkr)
{
char *cp = dgram;
memset(tkr,0,sizeof *trk);
strncpy(tkr->ticker,dgram,TICKLEN)
[TICKLEN]=0;
cp += strlen(cp)+1;
if(sscanf(cp,"%lE",&tkr->last_trade) != 1)
return -1;
cp += strlen(cp)+1;
tkr->date = cp;
cp += strlen(cp)+1;
tkr->time = cp;
cp += strlen(cp)+1;
if(sscanf(cp,"%lE",&tkr->change)!=1)
return -1;
cp += strlen(cp)+1;
if(sscanf(cp,"%lE",&tkr->open_price) != 1)
return -1;
cp += strlen(cp)+1;
if(sscanf(cp,"%lE",&tkr->high) != 1)
return -1;
cp += strlen(cp)+1;
if(sscanf(cp,"%lE",&tkr->low) != 1)
return -1;
cp += strlen(cp)+1;
if(sscanf(cp,"%lE",&tkr->volume) != 1)
return -1;
return 0;
}
/*
* Market Watch Main Program:
*/
int main(int argc,char **argv)
{
int rc=0;    /* command return code */
int optch;    /* option character */
int z;        /* status code */
int s;        /* socket */
socklen_t bc_len;    /* length */
struct sockaddr_in bc_addr;    /* AF_INET */
socklen_t a_len;    /* Address length */
struct sockaddr_in adr;        /* AF_INET */
char dgram[2048];    /* Recv buffer */
const int True=TRUE;    /* True const. */
TickReq tkr;        /* Ticker Data */
const char cmdopts[] = "hb:";
/*
* Parse commnd line options:
*/
while((optch = getopt(argc,argv,cmdopt)) != -1)
switch(optch)
{
case 'h':    /* -h (help) */
usage();
return rc;
case 'b':    /* -b bc_addr */
cmdopt_b = optarg;
break;
default:
/* option error */
rc = 1;
}
if(rc)
{
usage();    /* option errors */
return rc;
}
/*
* Form broadcast address:
*/
bc_len = sizeof address;
z = mkaddr(
&bc_addr,    /* returned addr */
&bc_len,    /* returned len */
cmdopt_b,    /* input address */
"udp");        /* udp protocol */
if(z==-1)
{
fprintf(stderr,
"%s: -b %s",
strerror(errno),
cmdopt_b);
return 1;
}
/*
* Create a udp socket to read from :
*/
s = socket(PF_INET,SOCK_STREAM,0);
if(s==-1)
{
fprintf(stderr,
"%s: socket(2)\n",
strerror(errno));
return 1;
}
/*
* Allow multiple listeners on this
* broadcast address:
*/
z = setsockopt(s,
SOL_SOCKET,
SO_REUSEADDR,
&True,
sizeof True);
if(z==-1)
{
fprintf(stderr,
"%s: setsockopt(SO_REUSEADDR)\n",
strerror(errno));
return 1;
}
/*
* Bind to the broadcast address:
*/
z = bind(s,
(struct sockaddr *)&bc_addr,bc_len);
if(z==-1)
{
fprintf(stderr,
"%s: bind(%s)\n",
strerror(errno),
cmdopt_b);
return 1;
}
/*
* Now listen for and process broadcaste
* stock quotes:
*/
for(;;)
{
/*
* wait for a broadcast message:
*/
a_len = sizeof adr;    /* max addr len */
z = recvfrom(s,        /* socket */
dgram,        /* receiving buffer */
sizeof dgram,    /* max rcv buf size */
0,            /* flags: no options */
(struct sockaddr *)&adr,    /* Addr */
&a_len);        /* Addr len,in&out */
if(z<0)
{
fprintf(stderr,
"%s: recvfrom(2)\n",
strerror(errno));
break;
}
/*
* Extract and report quote:
*/
if(!extract(dgram,&tkr))
{
printf("%-*s %7.3f %s %7s %+7.3f %7.3f "
"%7.3f %7.3f %9.0f\n",
TICKLEN,
tkr.ticker,
tkr.last_trade,
tkr.date,
tkr.time,
tkr.change,
tkr.open_price,
tkr.high,
tkr.low,
tkr.volume);
fflush(stdout);
}
}
return 0;
}
编译并运行演示程序
要编译工程,我们可以使用Makefile文件,如下所示:
$ make
gcc -c -g -Wall -Wreturn-type -D_GNU_SOURCE qserve.c
gcc -c -g -Wall -Wreturn-type -D_GNU_SOURCE csvparse.c
gcc -c -g -Wall -Wreturn-type -D_GNU_SOURCE msgf.c
gcc -c -g -Wall -Wreturn-type -D_GNU_SOURCE load.c
gcc -c -g -Wall -Wreturn-type -D_GNU_SOURCE gettick.c
gcc -c -g -Wall -Wreturn-type -D_GNU_SOURCE bcast.c
gcc -c -g -Wall -Wreturn-type -D_GNU_SOURCE connect.c
gcc -c -g -Wall -Wreturn-type -D_GNU_SOURCE misc.c
gcc -c -g -Wall -Wreturn-type -D_GNU_SOURCE mkaddr.c
gcc -o qserve qserve.o csvparse.o msgf.o load.o gettick.o bcast.o connect.o
misc.o mkaddr.o
gcc -c -g -Wall -Wreturn-type -D_GNU_SOURCE mktwatch.c
gcc -o mktwatch mktwatch.o mkaddr.o
$
这会得到两个可执行文件:
qserve可执行文件,这是行情服务器程序
mktwatch可执行文件,这是广播监听客户端程序
http://www.quecorp.com/series/by_example/
PS:这本是一本在我的机子上放了很久的一本书,连在哪里下载得到的都不再记得了。直到一个偶然的机会试着看了一下,觉得一本很好的书,至少对于我来说是这样的,所以试着在这里翻译了一下,希望对大家会有一些小小的帮助。另外,最后的一个例子程序的源代码可以在上面的网址中得到,但是我自己访问是却打不开这个地址,不知道朋友们试了会是什么结果。
Linux Socket学习(十八)--完 - Linux Socket - 乘风破浪会有... Linux Socket学习(十八)--完 - Linux Socket - 【嵌入式Li... Linux Socket学习(十七) - Linux Socket - 乘风破浪会有时 直... Linux Socket学习(十四) - Linux Socket - 乘风破浪会有时 直... Linux Socket学习(十六) - Linux Socket - 乘风破浪会有时 直... Linux Socket学习(十三) - Linux Socket - 乘风破浪会有时 直... Linux Socket 学习(十) - Linux Socket - 乘风破浪会有时 直... Linux Socket学习(十五) - Linux Socket - 乘风破浪会有时 直... Linux Socket学习(一) - Linux Socket - 【嵌入式Linux】 Linux Socket学习(二) - Linux Socket - 【嵌入式Linux】 Linux Socket学习(三) - Linux Socket - 【嵌入式Linux】 Linux Socket学习(四) - Linux Socket - 【嵌入式Linux】 Linux Socket学习(五) - Linux Socket - 【嵌入式Linux】 Linux Socket学习(六) - Linux Socket - 【嵌入式Linux】 Linux Socket学习(七) - Linux Socket - 【嵌入式Linux】 Linux Socket学习(八) - Linux Socket - 【嵌入式Linux】 Linux Socket 学习(九) - Linux Socket - 【嵌入式Linux... Linux Socket 学习(十) - Linux Socket - 【嵌入式Linux... Linux Socket学习(十二) - Linux Socket - 【嵌入式Linux... Linux Socket学习(十三) - Linux Socket - 【嵌入式Linux... Linux Socket学习(十四) - Linux Socket - 【嵌入式Linux... Linux Socket学习(十五) - Linux Socket - 【嵌入式Linux... Linux Socket学习(十六) - Linux Socket - 【嵌入式Linux... Linux Socket学习(十七) - Linux Socket - 【嵌入式Linux...