提要

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

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

环境: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. Windows phone 8 学习笔记(9) 集成

    原文:Windows phone 8 学习笔记(9) 集成 本节整理了之前并没有提到的Windows phone 8 系统相关集成支持,包括选择器.锁定屏幕的.联系人的访问等.选择器列举了若干内置应用 ...

  2. 黑马day07 注册案例(二)

    1依据index.jsp我们首先制定了注册的功能,当点击注册button什么时候.超链接到注册页面.下面是一个注册jsp页 <%@ page language="java" ...

  3. Codeforces Round #256 (Div. 2) D. Multiplication Table(二进制搜索)

    转载请注明出处:viewmode=contents" target="_blank">http://blog.csdn.net/u012860063?viewmod ...

  4. Debug with Eclipse

    In this post we are going to see how to develop applications using Eclipse and Portofino 4. The trad ...

  5. Java Drp项目实战——Drp知多少

    是什么 Drp是Distribution Resource Planning的缩写,意思是分销资源计划.它是用来管理企业的执行于Internet上的分销网络的系统,是以商业流程优化为基础,它的核心是销 ...

  6. Ajax的get和post两种请求方式区别

    Ajax的get和post两种请求方式区别 (摘录):http://ip-10000.blog.sohu.com/114437748.html 解get和post的区别. 1. get是把参数数据队列 ...

  7. HD2 Tmobile 重新分区代码(使用clk 1.6.5 de)

    fastboot oem part-resize misc: fastboot oem part-resize recovery: fastboot oem part-resize boot: fas ...

  8. 认识Require

    1. 加载require.js文件 <script type="text/javascript" src="js/require.js" data-mai ...

  9. Apache+Tomcat部署负载均衡(或集群)

    本来只打算写Tomcat集群部署,简化Apache和Tomcat整合过程的.后来想了想,这样不便于没有用过Apache的朋友来学习本文内容.于是干脆加大篇幅,让对Apache不了解的朋友能对Apach ...

  10. 乐在其中设计模式(C#) - 模板方法模式(Template Method Pattern)

    原文:乐在其中设计模式(C#) - 模板方法模式(Template Method Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 模板方法模式(Template Method ...