提要

学过非常多遍计算机网络,依旧不会网络编程。

看完这篇文章之后就不会是这样了。

环境:Ubuntu14.04 64bit

何为Socket

是基于TCP/IP的网络应用编程中使用的有关数据通信的概念,通常也称作"套接字",用于描写叙述IP地址和port,是一个通信链的句柄。在Internet上的主机一般执行了多个服务软件,同一时候提供几种服务。每种服务都打开一个Socket,并绑定到一个port上,不同的port相应于不同的服务。Socket正如其英文原意那样,像一个多孔插座。一台主机宛如布满各种插座的房间,每一个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就能够得到不同的服务。

基于TCP协议的网络程序

下图是基于TCP协议的client/server程序的一般流程:

借助这个图,我们能够非常清晰地把socket的一些接口和TCP/IP中server和client状态转换的相应关系。

准备状态

server调用socket()、bind()、listen()完毕初始化后,调用accept()堵塞等待,处于监听port的状态,client调用socket()初始化后,调用connect()发出SYN段并堵塞等待server应答,server应答一个SYN-ACK段,client收到后从connect()返回,同一时候应答一个ACK段,server收到后从accept()返回。

传输数据的过程

建立连接后,TCP协议提供全双工的通信服务,可是一般的client/server程序的流程是由client主动发起请求,server被动处理请求,一问一答的方式。因此,server从accept()返回后立马调用read(),读socket就像读管道一样,假设没有数据到达就堵塞等待,这时client调用write()发送请求给server,server收到后从read()返回,对client的请求进行处理,在此期间client调用read()堵塞等待server的应答,server调用write()将处理结果发回给client,再次调用read()堵塞等待下一条请求,client收到后从read()返回,发送下一条请求,如此循环下去。

假设client没有很多其它的请求了,就调用close()关闭连接,就像写端关闭的管道一样,server的read()返回0,这样server就知道client关闭了连接,也调用close()关闭连接。注意,不论什么一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。假设一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。

在学习socket API时要注意应用程序和TCP协议层是怎样交互的: *应用程序调用某个socket函数时TCP协议层完毕什么动作,比方调用connect()会发出SYN段 *应用程序怎样知道TCP协议层的状态变化,比方从某个堵塞的socket函数返回就表明TCP协议收到了某些段,再比方read()返回0就表明收到了FIN段。

码砖開始

先跑代码再解释。

服务端,server.c

/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> #define MAXLINE 80
#define SERV_PORT 8000 int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20); printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd,
(struct sockaddr *)&cliaddr, &cliaddr_len); n = read(connfd, buf, MAXLINE);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port)); for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(connfd, buf, n);
close(connfd);
}
}

client,client.c

/* client.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> #define MAXLINE 80
#define SERV_PORT 8000 int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
char *str; if (argc != 2) {
fputs("usage: ./client message\n", stderr);
exit(1);
}
str = argv[1]; sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); write(sockfd, str, strlen(str)); n = read(sockfd, buf, MAXLINE);
printf("Response from server:\n");
write(STDOUT_FILENO, buf, n); close(sockfd);
return 0;
}

编译

gcc client.c -o client
gcc server.c -o server

执行结果

从头文件開始解释

unistd.h - unistd.h 中所定义的接口通常都是大量针对系统调用的封装(英语:wrapper functions),如 fork、pipe 以及各种 I/O 原语(read、write、close 等等)。 
 sys/socket.h - 提供 socket 函数及数据结构 .
netinet/in.h - 定义数据结构sockaddr_in,包含IPv4和IPv6的地址格式定义。
arpa/inet.h - 提供IP地址转换函数,比方大小端的转换,还有ip地址的表达方式的转换。

看server的main函数。

16行,声明两个socketaddr_in结构体。IPv4地址用sockaddr_in结构体表示,包含16位port号和32位IP地址.IPv6地址用sockaddr_in6结构体表示,包含16位port号、128位IP地址和一些控制字段。

17行,声明一个记录client地址长度的变量,socklen_t就是socket中用来表示长度的类型。Linus Torvalds(他希望有很多其它的人,但显然不是非常多) 努力向他们解释使用size_t是全然错误的,由于在64位结构中 size_t和int的长度是不一样的,而这个參数(也就是accept函数 的第三參数)的长度必须和int一致,由于这是BSD套接字接口 标准.终于POSIX的那帮家伙找到了解决的办法,那就是创造了 一个新的类型"socklen_t".

23行,打开一个网络通信port,函数原型是:

int socket(int family, int type, int protocol);

socket()打开一个网络通讯port,假设成功的话,就像open()一样返回一个文件描写叙述符,应用程序能够像读写文件一样用read/write在网络上收发数据,假设socket()调用出错则返回-1。对于IPv4,family參数指定为AF_INET。对于TCP协议,type參数指定为SOCK_STREAM,表示面向流的传输协议。

25-28行,首先将整个结构体清零,然后设置地址类型为AF_INET,网络地址为INADDR_ANY,这个宏表示本地的随意IP地址,由于server可能有多个网卡,每一个网卡也可能绑定多个IP地址,这样设置能够在全部的IP地址上监听,直到与某个client建立了连接时才确定下来究竟用哪个IP地址,port号为SERV_PORT,我们定义为8000。

30行,调用bind绑定一个固定的网络地址和port号。bind()成功返回0,失败返回-1。函数原型:

int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

bind()的作用是将參数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描写叙述符监听myaddr所描写叙述的地址和port号。

32行,声明listenfd处于监听状态,并且最多同意有20个client处于连接待状态,假设接收到很多其它的连接请求就忽略。listen()成功返回0,失败返回-1。

25-49行,整个是一个while死循环,每次循环处理一个client连接。由于cliaddr_len是传入传出參数,每次调用accept()之前应该又一次赋初值。accept()的參数listenfd是先前的监听文件描写叙述符,而accept()的返回值是另外一个文件描写叙述符connfd,之后与client之间就通过这个connfd通讯,最后关闭connfd断开连接,而不关闭listenfd,再次回到循环开头listenfd仍然用作accept的參数。accept()成功返回一个文件描写叙述符,出错返回-1。

client少了bind,多了个connect函数。由于client不须要固定的port号,因此不必调用bind(),client的port号由内核自己主动分配。注意,client不是不同意调用bind(),仅仅是没有必要调用bind()固定一个port号,server也不是必须调用bind(),但假设server不调用bind(),内核会自己主动给server分配监听port,每次启动server时port号都不一样,client要连接server就会遇到麻烦。connect函数原型:

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

client须要调用connect()连接server,connect和bind的參数形式一致,差别在于bind的參数是自己的地址,而connect的參数是对方的地址。connect()成功返回0,出错返回-1。

小小的封装一下

代码尽管能够work,可是看着着实有点蛋疼,并且整个代码有流程的报错,出错了也非常难处理,首先是把socket的相关函数封装一遍,

wrap.h

#include <stdlib.h>
#include <sys/socket.h>
#include <errno.h> //Print error message and exit.
void perr_exit(const char *s); //Server accept message from client.
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); void Bind(int fd, const struct sockaddr *sa, socklen_t salen); void Connect(int fd, const struct sockaddr *sa, socklen_t salen); void Listen(int fd, int backlog); int Socket(int family, int type, int protocol); ssize_t Read(int fd, void *ptr, size_t nbytes); ssize_t Readn(int fd, void *vptr, size_t n); ssize_t Write(int fd, const void *ptr, size_t nbytes); ssize_t Writen(int fd, const void *vptr, size_t n); static ssize_t my_read(int fd, char *ptr); ssize_t Readline(int fd, void *vptr, size_t maxlen); void Close(int fd);

wrap.c

#include "wrap.h"

//Print error message and exit
void perr_exit(const char *s)
{
perror(s);
exit(1);
} //Server accept message from client.
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
int n; again:
if((n = accept(fd, sa, salenptr)) < 0)
{
if((errno == ECONNABORTED) || (errno == EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
} void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
if(bind(fd, sa, salen) < 0)
{
perr_exit("bind error");
}
} void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
if(connect(fd, sa, salen) < 0)
{
perr_exit("connet error");
}
} void Listen(int fd, int backlog)
{
if(listen(fd, backlog) < 0)
{
perr_exit("listen error");
}
} int Socket(int family, int type, int protocol)
{
int n;
if((n = socket(family, type, protocol)) < 0)
perr_exit("socket error");
return n;
} ssize_t Read(int fd, void *ptr, size_t nbytes)
{
ssize_t n;
again:
if((n = read(fd, ptr, nbytes)) == -1)
{
if(errno == EINTR)
goto again;
else
return -1;
}
return n;
} ssize_t Readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr; ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0;
else
return -1;
} else if (nread == 0)
break; nleft -= nread;
ptr += nread;
}
return n - nleft;
} ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
ssize_t n;
again:
if((n = write(fd, ptr, nbytes)) == -1)
{
if(errno == EINTR)
goto again;
else
return -1;
}
return n;
} ssize_t Writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while(nleft > 0)
{
if((nwritten = write(fd, ptr, nleft)) <= 0)
{
if(nwritten < 0 && errno == EINTR)
{
nwritten = 0;
}
else
{
return -1;
}
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
} static ssize_t my_read(int fd, char *ptr)
{
static int read_cnt;
static char *read_ptr;
static char read_buf[100]; if (read_cnt <= 0) {
again:
if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
if (errno == EINTR)
goto again;
return -1;
} else if (read_cnt == 0)
return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
} ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr; ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ( (rc = my_read(fd, &c)) == 1) {
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
*ptr = 0;
return n - 1;
} else
return -1;
}
*ptr = 0;
return n;
} void Close(int fd)
{
if(close(fd) == -1)
{
perr_exit("close error");
}
}

这里的错误处理主要使用了erron和perr函数。

当linux中的C api函数发生异常时,通常会将errno变量(需include errno.h)赋一个整数值,不同的值表示不同的含义,能够通过查看该值猜測出错的原因,在实际编程中用这一招攻克了不少原本看来莫名其妙的问题。可是errno是一个数字,代表的详细含义还要到errno.h中去阅读宏定义,而每次查阅是一件非常繁琐的事情。有以下几种方法能够方便的得到错误信息

(1)void perror(const char *s)
函数说明
perror ( )用来将上一个函数错误发生的原因输出到标准错误(stderr),參数s 所指的字符串会先打印出,后面再加上错误原因 字符串。此错误原因按照全局变量
errno 的值来决定要输出的字符串。

(2) char *strerror(int errno)
将错误代码转换为字符串错误信息,能够将该字符串和其它的信息组合输出到用户界面比如
fprintf(stderr,"error in CreateProcess %s, Process ID %d
",strerror(errno),processID)
注:假设processID是一个已经获取了的整形ID

(3)printf("%m", errno);
另外不是全部的地方错误发生的时候都能够通过error获取错误代码,比如以下的代码段

将client改成连接成功后能够多次发送消息的模式。

client.c

/* client.c */

#define MAXLINE 80
#define SERV_PORT 8000 int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
char *str; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while(fgets(buf, MAXLINE, stdin) != NULL)
{
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if(n == 0)
{
printf("The other side has been closed.\n");
}
else
{
Write(STDOUT_FILENO, buf, n);
}
} Close(sockfd);
return 0;
}

sever.c也要做一点改动,使它能够多次处理同一client的请求。

/* server.c */
#define MAXLINE 80
#define SERV_PORT 8000 int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20); printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd,
(struct sockaddr *)&cliaddr, &cliaddr_len); while(1)
{
n = Read(connfd, buf, MAXLINE);
if(n == 0)
{
printf("The other side has been closed!\n" );
break;
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port)); for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
Write(connfd, buf, n);
}
Close(connfd);
}
}

执行结果

小作业 - 简单的Webserver

先按以下的步骤做:

1.终端输入ifconfig,查看自己的ip地址;

2.执行上面编译好的server程序;

3.在浏览器的地址栏里面输入:X.X.X.X:80

(X.X.X.X是你的ip地址)

得到浏览器的执行结果是

这就是一个简单的http请求。

在这个通信过程中,server就是我们的server程序,浏览器就是client。server启动之后,就開始监听相应的port,这时在浏览器中输入本机的ip和port,这时浏览器向server发送的HTTP协议头,server中的处理就是将全部字母都改成大写,然后再write回socket,client最后得到的就是toupper的请求信息。

我们实现的Webserver仅仅要能正确解析第一行即可了,这是一个GET请求,请求的是服务文件夹的根文件夹/(在本例中实际上是/var/www),Webserver应该把该文件夹下的索引页(默认是index.html)发给浏览器,也就是把/var/www/index.html发给浏览器。假如该文件的内容例如以下(HTML文件不是必需以"\r\n"换行,以"\n"换行就能够了)。简单的index.html

<html>
<head><title>Test Page</title></head>
<body>
<p>Hello,Server.</p>
</body>
</html>

则server返回的数据就是

HTTP/1.1 200 OK
Content-Type: text/html <html>
<head><title>Test Page</title></head>
<body>
<p>Test OK</p>
</body>
</html>

server应答的HTTP头也是每行末尾以回车加换行结束,最后跟一个空行的回车加换行。

HTTP头的第一行是协议版本号和应答码,200表示成功,后面的消息OK事实上能够随意写,浏览器是不关心的,主要是为了调试时给开发者看的。尽管网络协议终于是程序与程序之间的对话,可是在开发过程中却是人与程序之间的对话,一个设计透明的网络协议能够提供非常多直观的信息给开发者,因此,非常多应用层网络协议,如HTTP、FTP、SMTP、POP3等都是基于文本的协议,为的是透明性(transparency)。

HTTP头的第二行表示即将发送的文件的类型(称为MIME类型),这里是text/html,纯文本文件是text/plain,图片则是image/jpg、image/png等。

然后就发送文件的内容,发送完毕之后主动关闭连接,这样浏览器就知道文件发送完了。这一点比較特殊:通常网络通信都是client主动发起连接,主动发起请求,主动关闭连接,server仅仅是被动地处理各种情况,而HTTP协议规定server主动关闭连接(有些Webserver能够配置成Keep-Alive的,我们不讨论这样的情况)。

终于实现的server代码例如以下

/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h> #include "wrap.h" #define MAXLINE 80 void loadConfig(int *port, char *path); int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
int pd_index,ret; int port;
char *path = NULL;
loadConfig(&port, path); listenfd = Socket(AF_INET, SOCK_STREAM, 0); int opt=1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port); Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(listenfd, 20); printf("Accepting connections ...\n");
while (1)
{
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); while(1)
{
n = Read(connfd, buf, MAXLINE);
if(n == 0)
{
printf("The other side has been closed!\n" );
break;
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port)); i=0;
char title[15];
while(buf[i]!='\r')
{
title[i]=buf[i];
i++;
}
title[i]='\0'; if(0 == (strcmp(title,"GET / HTTP/1.1")))
{
bzero(buf,MAXLINE);
strcpy(buf,"HTTP/1.1 200 0K\r\n");
write(connfd,buf,strlen(buf));
bzero(buf,MAXLINE);
strcpy(buf,"Conent_Type: text/html\r\n");
write(connfd,buf,strlen(buf));
bzero(buf,MAXLINE);
buf[0]='\r';
buf[1]='\n';
write(connfd,buf,2); pd_index=open("/var/www/index.html",O_RDONLY);
if(pd_index<0)
{
perr_exit("open file failed.\n");
} bzero(buf,MAXLINE);
while((ret=read(pd_index,buf,MAXLINE))>0)
{
write(connfd,buf,ret);
printf("send index data...\n");
bzero(buf,MAXLINE);
} }
}
Close(connfd);
}
} void loadConfig(int *port, char *path)
{
FILE *fd;
fd = fopen("/etc/myhttpd.conf", "r");
if(fd < 0)
perr_exit("Can't load config.\n"); fscanf(fd, "Port=%d\nDirectory=%s",port, path);
}

终于执行结果

參考

Linux C编程一站式学习 - http://c4linux.letaoba.info/

在Ubuntu下实现一个简单的Webserver - http://www.linuxidc.com/Linux/2013-10/91160.htm

Linux网络编程一站式学习的更多相关文章

  1. [Linux] Linux C编程一站式学习 Part.3

    Linux系统编程 文件与I/O C标准I/O库函数与Unbuffered I/O函数 C标准I/O库函数printf().putchar().fputs(),会在用户空间开辟I/O缓冲区 系统函数o ...

  2. 有关于《Linux C编程一站式学习》(备份)

    Linux C编程一站式学习 -- PDF版本,共37章: Linux C编程一站式学习 -- 在线版,来自灰狐: Linux C编程一站式学习 -- 在线版,来自亚嵌教育: Linux C一站式学习 ...

  3. gdb笔记 ---《Linux.C编程一站式学习》

    gdb笔记 ---<Linux.C编程一站式学习> 单步执行和跟踪函数调用 函数调试实例 #include <stdio.h> int add_range(int low, i ...

  4. Linux网络编程&内核学习

    c语言: 基础篇 1.<写给大家看的C语言书(第2版)> 原书名: Absolute Beginner's Guide to C (2nd Edition) 原出版社: Sams 作者: ...

  5. Linux C编程一站式学习读书笔记——socket编程

    前言 研一的时候写过socket网络编程,研二这一年已经在用php写api都快把之前的基础知识忘干净了,这里回顾一下,主要也是项目里用到了,最近博客好杂乱啊,不过确实是到了关键时刻,各种复习加巩固准备 ...

  6. [Linux] Linux C编程一站式学习 Part.1

    C语言入门 程序基本概念 程序和编程语言 C语言--(编译器)--汇编语言--(汇编器)--机器语言(目标代码 / 可执行代码) 可移植 / 平台无关:平台指计算机体系结构或操作系统,或二者的组合.不 ...

  7. [Linux] Linux C编程一站式学习 Part.2

    C语言本质 计算机中数的表示 浮点数:符号位+指数部分(2的多少次方)+尾数部分(小数点后的数字) 用偏移的指数(Biased Exponent)表示负指数 正规化(Normalize):尾数部分最高 ...

  8. Linux C 编程一站式学习

    个人认为这是一个挺不错的从C语言到Linux系统开发的教程,这本是两个网上的文档. 其中 一本是<How To Think Like A Computer Scientist: Learning ...

  9. Linux C编程一站式学习

    http://docs.linuxtone.org/ebooks/C&CPP/c/ 很全面的介绍

随机推荐

  1. Hibernate 配置详解(11)

    hibernate.session_factory_name_is_jndi 配置hibernate.cfg.xml中SessionFactory的name属性是否作为JNDI名称绑定.默认是true ...

  2. 用数组array代替CActiveRecord构建CArrayDataProvider

    当需要构建 GridView的时候: 常常用 CArrayDataProvider 或者 CActiveDataProvider 这是就需要一个CActiveRecord 比如:  857       ...

  3. java split小结(转)

    2016.03.27下午参加华为机试,简单扫了一眼几个题的标题,选择了一道字符串问题,其实该题非常非常的简单,可以说是简单的不能再简单了,而且有很多种解法,上机时我选择了直接借用java提供的一些函数 ...

  4. Vb.net/VB 声明API功能父窗口功能

    回想第一次敲房费,他说自己是api函数实现父窗口及其子窗口最小化的功能.现在再次遇到,自己就在思考,能不能继续使用API函数呢?答案当然是Of Course! 事实上细致看两者并没有多大的差别,先看看 ...

  5. 谁的用户在世界上是&#160;&#160;明基决心保时捷设计标准

        谈到保时捷.相信非常多人都非常了解,世界名车啊,仅仅有高富帅才玩儿得起.只是,假设由保时捷的设计师来设计一款显示器,水准一流.质地厚道,且价格亲民,你怎么看?     如近期京东上热销的明基G ...

  6. HDU4870:Rating(DP)

    Problem Description A little girl loves programming competition very much. Recently, she has found a ...

  7. javascript于boolean类型转换,运营商&amp;&amp;和|| 返回值

    javascript它是弱类型语言,不管是什么类型的数据可以被转换成boolean种类.转换规则如下面的: 数据类型                 转换为boolean后的值 NAN         ...

  8. 最佳新秀SSH十六Struts2它是如何工作的内部

    前面说完了Spring.Hibernate,非常自然今天轮到struts了.struts的核心原理就是通过拦截器来处理client的请求,经过拦截器一系列的处理后,再交给Action.以下先看看str ...

  9. 移动web:转盘抽奖(幸运大转盘)

    为了获取客户.回馈客户,平台一般会推出抽奖活动类的营销页.因此web页面中,有各式各样的抽奖效果. 格子式(九宫格),背景滚动式(数字/文字/图案),旋转式(转盘),游戏式(砸蛋/拼图...).... ...

  10. git commit -s -m 注释中的换行 [加入signed-off-by

    windows环境下的Git Bash中注释的换行: 使用单引号. 或者是在Linux系统里面用终端 git add . git commit -m ' . this is the test . up ...