iOS-----使用CFNetwork实现TCP协议的通信
使用CFNetwork实现TCP协议的通信
TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个通信接口,从而在通信的两端之间形成网络虚拟链路.一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信.CFNetwork对基于TCP协议的网络通信提供了良好的封装,CFNetwork使用CFSocket来代表两端的通信接口,还可以通过CFStream读/写数据.
IP地址与端口号
IP地址用于唯一地标识网络中的一个通信实体,这个通信实体既可以是一台主机,也可以是一台打印机,或者是路由器的某一个端口.在基于IP协议的网络中传输的数据包,都必须使用IP地址来进行标识.
IP地址是数字型的,是一个32位(32Bit)整数,通常把它分成4个8位的二进制数,每8为之间用圆点隔开,每个8位整数可以转换成一个0-255的十进制整数,因此看到的IP地址常常是这样的形式:198.162.8.10.
NIC(Internet Network Information Center)统一负责全球Internet IP地址的规划、管理,而Inter NIC、APNIC、RIPE三大网络信息中心具体负责美国及其他地区的IP地址分配。其中APNIC负责亚太地区的IP管理,我国申请IP地址也要通过APNIC,APNIC的总部设在日本东京大学。
IP地址被分成了A、B、C、D、E五类,每个类别的网络标识和主机标识各有规则。
- A类:10.0.0.0~10.255.255.255
- B类:172.16.0.0~172.31.255.255
- C类:192.168.0.0~192.168.255.255
IP地址用于唯一地标识网络上的一个通信实体,但一个通信实体可以有多个通信程序同时提供网络服务,此时还需要使用端口.
端口是一个16位的整数,用于标识数据交给那个通信程序处理.因此,端口就是应用程序与外界交流的出入口,它是一种抽象的软件结构,包括一些数据结构和I/O(基本输入/输出)缓冲区.
不同的应用程序处理不同端口上的数据,同一台机器上不能有两个程序使用同一个端口,端口号可以从0到65535,通常将它分为3类.
- 公认端口(Well Know Ports):从0到1023,它们紧密绑定(Binding)一些特定的服务.
- 注册端口(Registered Ports):从1024到49151,它们松散地绑定一些服务。应用程序通常使用这个范围内的端口。
- 动态和/或私有端口(Dynamic and/or Private Ports):从49152到65535,这些端口是应用程序使用的动态端口,应用程序一般不会主动使用这些端口。
TCP协议基础
IP协议是 Internet 上使用的一个关键协议,它是全称是 Internet Protocol, 即Internet协议,通常简称 IP 协议.通过使用 IP协议,使 Internet 成为一个允许连接不同类型的计算机和不同操作系统的网络.
要使两台计算机之间彼此进行通信,必须使两台计算机使用同一种”语言”,IP 协议只保证计算机能发送和接收分组数据. IP协议负责将消息从一个主机传送到另一个主机,消息在传送的过程中被分割成一个个小包.
安装 IP 协议之后,可保证计算机之间发送和接收数据,但 IP协议还不能解决数据分组在传输过程中可能出现的问题.因此,若要解决可能出现的问题,连接上 Internet 的计算机还需要安装 TCP 协议来提供可靠并且无差错的通信服务.
TCP协议被称作一种端对端协议.这是因为它为两台计算机之间的连接起到了重要作用-----当一台计算机需要与另一台远程计算机连接时, TCP 协议会让它们建立一个连接:用于发送和接收数据的虚拟链路.
TCP 协议负责收集这些信息包,并将其按适当的次序放好传送,在接收端收到后再将其正确地还原. TCP协议保证了数据包在传送中准备无误. TCP 协议使用重发机制:当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体的确认信息,如果没有收到另一个通信实体的确认信息,则会再次重发刚才发送的信息.
通过这种重发机制, TCP协议向应用程序提供可靠的通信连接,使它能够自动适应网上的各种变化.即使在 Internet 暂时出现堵塞的情况下, TCP也能够保证通信的可靠性.
使用 CFSocket 实现 TCP服务器端
|
使用CFSocket建立服务器的步骤如下. |
|||||||||
|
1 |
创建一个监听Socket Accept(Socket连接)的CFSocket,并为kCFSocketAcceptCallBack事件绑定回调函数. |
||||||||
|
2 |
调用CFSocketSetAddress()函数,将服务器端的CFSocket绑定到本地IP地址和端口 |
||||||||
|
3 |
将CFSocket作为source添加到指定线程的CFRunLoop上,并运行该线程的CFRunLoop,从而保证该CFSocket能持续不断地接受来自客户端的连接. |
||||||||
|
代码片段 |
#import <sys/socket.h>
#import <arpa/inet.h>
#import<Foundation/Foundation.h>
// 读取数据的回调函数
void readStream(CFReadStreamRef iStream, CFStreamEventType eventType, void *clientCallBackInfo)
{
UInt buff[];
CFIndex hasRead = CFReadStreamRead(iStream, buff, );
if(hasRead > )
{
// 强制只处理hasRead前面的数据
buff[hasRead]=’\’;
printf(“接收到数据: %s\n”, buff);
}
}
// 有客户端连接进来的回调函数
void TCPServerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info )
{
// 如果有客户端Socket连接进来
if(kCFSocketAcceptCallBack == type)
{
// 获取本地Socket的Handle
CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle*)data;
uint8_t name[SOCk_MAXADDRLEN];
socklen_t nameLen = sizeof(name);
// 获取对方Socket信息,还有getsocketname()函数则用于获取本程序所在Socket信息
if(getpeername(nativeSocketHandle, (struct sockaddr *)name, &nameLen) != )
{
NSLog(@”error”);
exit();
}
// 获取连接信息
struct sockeaddr_in * addr_in = (struct socketaddr_in*) name;
NSLog(@”%s: %d 连接进来了”, inet_ntoa(addr_in->sin_addr)
, addr_in->sin_port);
CFReadStreamRef iStream;
CFWriteStreamRef oStream;
// 创建一组可读/写的CFStream
CFStreamCreatePairWithSocket(kCFAllocatorDefault , nativeSocketHandle, &iStream, &oStream);
if(iStream && oStream)
{
// 打开输入流和输入流
CFReadStreamOpen(iStream);
CFWriteStreamOpen(oStream);
CFStreamClientContext streamContext = {, NULL, NULL, NULL};
if(!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvailable,
readStream/*回调函数,当有可读的数据时调用*/, &streamContext))
{
exit();
}
CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(),
kC FRunLoopCommonModes);
const char* str = “您好,您收到Mac服务器的新年祝福!\n”;
// 向客户端输出数据
CFWriteStreamWrite(oStream, (UInt8 *)str, strlen(str) + );
}
}
}
int main(int argc, char * argv[])
{
@autoreleasepool{
// 创建Socket,指定TCPServerAcceptCallBack
// 作为kCFSocketAcceptCallBack事件的监听函数
CFSocketRef _socket = CFSocketCreate(kCFAllocatorDefault
, PF_INEF // 指定协议族,如果该参数为0或者负数,则默认为PF_INEF
// 指定Socket类型,如果协议族为PF_INEF,且该参数为0或负数
// 则它会默认为SOCK_STREAM,如果要使用UDP协议,则该参数指定为SOCK_DGRAM
, SOCK_STREAM
// 指定通信协议。如果前一个参数为SOCK_STREAM,则默认使用TCP协议
// 如果前一个参数为SOCK_DGRAM,则默认使用UDP协议
,IPPROTO_TCP
// 该参数指定下一个回调函数所监听的事件类型
,kCFSocketAcceptCallBack
,TCPServerAcceptCallBack // 回调函数
,NULL);
if(_socket == NULL)
{
NSLog(@”创建Socket失败!”);
return ;
}
int optval = ;
// 设置允许重用本地地址和端口
setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR,
(void *)&optval, sizeof(optval));
// 定义sockaddr_in类型的变量,该变量将作为CFSocket的地址
struct sockaddr_in addr4;
memset(&addr4, , sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INEF;
// 设置该服务器监听本机任意可用的IP地址
// addr4.sin_addr.s_addr =htonl(INADDR_ANY);
// 设置服务器监听地址
addr4.sin_addr.s_addr = inet_addr(“192.168.1.100”);
// 设置服务器监听端口
addr4.sin_port = htons();
// 将IPv4的地址转换成CFDataRef
CFDataRef address = CFDataCreate(kCFAllocatorDefault
, (UInt8 *)&addr4, sizeof(addr4));
// 将CFSocket绑定到指定IP地址
if(CFSocketSetAddress(_socket, address) != kCFSocketSuccess)
{
NSLog(@”地址绑定失败!”);
// 如果_socket不为NULL,则释放_socket
if(_socket)
{
CFRelease(_socket);
exit();
}
_socket = NULL;
}
NSLog(@”---启动循环监听客户端连接-----”);
// 获取当前线程的CFRunLoop
CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
// 将_socket包装成CFRunLoopSource
CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(
kCFAllocatorDefault , _socket, );
// 为CFRunLoop对象添加source
CFRunLoopAddSource(cfRunLoop, source, kCFRunLoopCommonModes);
CFRelease(source);
// 运行当前线程的CFRunLoop
CFRunLoopRun();
}
return ;
}
|
||||||||
|
说明 |
上面程序中的main()函数作为程序的入口,程序从该函数开始执行.main()函数的第1段红色字代码创建了一个CFSocket对象,并指定了该CFSocket使用TCP协议,基于流经行输入/输出. sockaddr_in类型的结构体变量,该结构体变量将会作为CFSocket绑定的监听地址,因此程序为socketaddr_in类型的结构体变量指定了IP地址和端口,然后程序中的红色字代码调用了CFSocketSetAddress()函数将指定CFSocket绑定到指定的IP地址和端口. main()函数的最后一段红色体代码将该CFSocket作为source添加到主线程的CFRunLoop上,并运行主线程的CFRunLoop,从而保证该CFSocket能持续不断地接受来自客户端的连接. |
||||||||
|
该程序的另一个重点是TCPServerAcceptCallBack回调函数---当CFSocket接受来自客户端的连接后,该函数将会被调用.该函数主要做了如下4件事情.
|
|||||||||
使用CFSocket实现TCP客户端
创建TCP客户端同样通过CFSocket完成。使用CFSocket创建Socket客户端的步骤如下。
|
1 |
创建一个不监听任何事件或监听Connection的CFSocket。如果要监听Connection,则需要为kCFSocketConnectCallBack事件绑定回调函数. |
|
2 |
调用CFSocketConnectToAddress()函数,将客户端的CFSocket连接到指定IP地址和端口的服务器上. |
|
3 |
得到客户端CFSocket之后,既可直接使用CFSocket对应的CFSocketNativeHandle进行读/写,也可通过CFSocket获取CFReadStreamRef、CFWriteStreamRef后进行读/写。 |
|
代码片段 |
ViewController.m
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <ViewController.h>
@implementation ViewController
CFSocketRef _socket;
BOOL isOnline;
- (void)viewDidLoad
{
[super viewDidLoad];
// 创建Socket,无须回调函数
_socket = CFSocketCreate(kCFAllocatorDefault,
PF_INEF // 指定协议族,如果该参数为0或者负数,则默认为PF_INET
// 指定Socket类型,如果协议族为PF_INEF,且该参数为0或负数
// 则它会默认为SOCK_STREAM,如果要使用UDP协议,则该参数指定为SOCK_DGRAM
, SOCK_STREAM
// 指定通信协议.如果前一个参数为SOCK_STREAM,则默认使用TCP协议
// 如果前一个参数SOCK_DGRAM,则默认使用UDP协议
, IPPROTO_TCP
// 该参数指定下一个回调函数所监听的事件类型
, kCFSocketNoCallBack
, nil
, NULL);
if(_socket != nil)
{
// 定义sockaddr_in类型的变量,该变量将作为CFSocket的地址
struct sockaddr_in addr4;
memset(&addr4, , sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
// 设置连接远程服务器的地址
addr4.sin_addr.s_addr = inet_addr(“192.168.1.88”);
// 设置连接远程服务器的监听端口
addr4.sin_port = htons();
// 将IPv4的地址转换为CFDataRef
CFDateRef addres = CFDataCreate(kCFAllocatorDefault
, (UInt8 *)&addr4, sizeof(addr4));
// 连接远程服务器的Socket,并返回连接结果
CFSocketError result = CFSocketConnectionToAddress(_socket
, address // 指定远程服务器的IP地址和端口
, // 指定连接超时时长, 如果该参数为负数, 则把连接操作放在后台进行
// 当_socket消息类型为kCFSocketConnectCallBack时
// 将会在连接成功或失败的时候在后台触发回调函数
);
// 如果连接远程服务器成功
if(result == kCFSocketSuccess)
{
isOnline = YES;
// 启动新线程来读取服务器响应的数据
[NSThread detachNewThreadSelector:@selector(readStream)
toTarget:self withObject:nil];
}
}
}
// 读取接收的数据
- (void)readStream
{
char buffer[];
int hasRead;
// 与本机关联的Socket如果已经失效,则返回-1:INVALID_SOCKET
while ((hasRead = recv(CFSocketGetNative(_socket)
, buffer, sizeof(buffer), )))
{
NSLog(@”%@”,[[NSString alloc] initWithBytes:buffer
length:hasRead encoding:NSUTF8StringEncoding]);
}
}
- (IBAction)clicked:(id)sender
{
if(isOnline)
{
NSString* stringTosend = @”来自iOS客户端的问候”;
const char* data = [stringTosend UTF8String];
send(CFSocketGetNative(_socket), data, strlen(data) +, );
}
else
{
NSLog(@”暂未连接服务器”);
}
}
@end
|
|
说明 |
上面程序中的第1段红色字代码创建了一个CFSocket, 该CFSocket同样适用了TCP协议,并且是基于SOCK_STREAM流的Socket,然后程序创建了一个struct sockaddr_in 结构体变量,该结构体变量代表远程服务器的地址. 上面程序中的第2段红色字代码调用了CFSocketConnectToAddress()函数将CFSocket连接到远程服务器地址---如果连城成功,就可得到一个进行网络读/写的CFSocket.剩下的事情是程序以readStream作为新线程的执行体,启动了一个新线程,其中readStream方法中的红色字代码调用了recv()函数从指定CFSocket读取数据.而clicked:方法则用于向服务器发送数据,该方法中的红色字代码调用了send()函数向CFSocket发送数据. |
使用CocoaAsyncSocket实现TCP客户端
CocoaAsyncSocket封装了CFNetwork底层的CFSocket和CFStream,并提供了异步操作,从而可简化Socket网络编程.CocoaAsyncSocket不仅支持TCP协议的网络编程,也支持UDP协议的网络编程.CocoaAsyncSocket是CFSocket的绝佳替代者.
|
CocoaAsyncSocket主要有以下特性 |
|
|
1 |
非阻塞方式的读和写,而且可设置超时时长. |
|
2 |
自动的Socket接收。如果调用它接受连接,它将为每个连接启动新的实例,当然也可以立即关闭这些连接。 |
|
3 |
委托(delegate)支持。错误、连接、接收、完整的读取、完整的写入、进度以及断开连接,都可通过委托模式调用。 |
|
4 |
所有操作都封装在一个类中,无须操作Socket或流,该类封装了所有操作。 |
|
下载和安装CocoaAsyncSocket的步骤如下 |
|
|
1.CocoaAsyncSocket的官方网站是https://github.com/robbiehanson/CocoaAsyncSocket,登录该站点,单击页面中间的release链接. |
|
|
2.浏览器将会打开一个新的列表页面,该列表页面中列出了CocoaAsyncSocket的所有发布版本,建议下载最新版的CocoaAsyncSocket. |
|
|
3.下载完成将可以得到一个CocoaAsyncSocket.zip文件,解压该压缩包,将可以看到如下文件结构. RunLoop:该目录下包含了AsyncSocket\AsyncUDPSocket两个类的源文件和Xcode目录.其中AsyncSocket就是基于TCP协议的CocoaAsyncSocket实现,AsyncUDPSocket就是基于UDP协议的AsyncUDPSocket实现。而Xcode目录下则包含了使用CocoaAsyncSocket开发服务器端与客户端的示例项目。 GCD:该目录下的内容与RunLoop目录下的内容基本相似,只是类名变成了GCDAsyncSocket、GCDAsyncUDPSocket,这是因为该目录下的CocoaAsyncSocket是基于GCD的实现。 Vendor:其他相关类。 其他杂项文件。 需要为项目增加CFNetwork.framework框架。 |
|
|
添加CocoaAsyncSocket支持之后,使用AsyncSocket开发TCP客户端的步骤如下 |
|
|
|
|
代 码 片 段 |
|
ViewController.h
#import <UIKit/UIKit.h>
#import “AsyncSocket.h”
@interface ViewController : UIViewController<AsyncSocketDelegate>
@property (strong, nonatomic) IBOutlet UITextView *showView;
@property (strong, nonatomic) IBOutlet UITextFiled *inputField;
- (IBAction)finishEdit:(id)sender;
- (IBAction)send:(id)sender;
@end
ViewController.m
@implementation ViewController
NSString* myName;
AsyncSocket* socket;
BOOL iSOnline;
- (void)viewDidLoad
{
[super viewDidLoad];
// 创建一个UIAlertView提醒用户输入名字
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@”名字”
message:@”请输入您的名字” delegate:self
cancelButtonTitle:@”确定” otherButtonTitles: nil ];
// 设置该UIAlertView为UIAlertViewStylePlainTextInput风格
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
[alert show];
}
- (IBAction)finishEdit:(id)sender
{
[sender resignFirstResponder];
}
- (IBAction)send:(id)sender
{
if(isOnline)
{
// 定义要发送的字符串内容
NSString* stringTosend = [NSString stringWithFormat:@”%@说: %@”, myName, self.inputField.text];
self.inputField.text = nil;
NSData *data = [stringTosend dataUsingEncoding:NSUTF8StringEncoding];
// 调用writeData:withTimeout:tag:方法发送数据
[socket writeData:data withTimeout: - tag:];
}
else
{
NSLog(@”暂未连接服务器”);
}
}
// AsyncSocketDelegate中定义的方法,当成功连接到服务器时激发该方法
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
isOnline = YES;
// 调用readDataWithTimeout: tag: 方法读取数据
[sock readDataWithTimeout:- tag:];
}
// AsyncSocketDelegate中定义的方法,当读取数据完成时激发该方法
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
// 获取读到的内容
NSData* strData = [data subDataWithRange:NSMakeRange(, [data length])];
NSString* content = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding];
if(content)
{
// 使用showView控件显示从网络读取的内容
self.showView.text = [NSString stringWithFormat:@”%@\n%@”,
content , self.showView.text];
}
// 再次调用readDataWithTimeout:tag:方法读取数据
[sock readDataWithTimeout:- tag:];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// 获取UIAlertView控件上 文本框内的字符串,并将字符串赋值给myName变量
myName = [alertView textFieldAtIndex:].text;
socket = [[AsyncSocket alloc] initWithDelegate:self];
NSError* error = nil;
int port = ;
NSString* host = @”192.168.1.88”;
// 调用connectToHost:onPort:error:方法连接指定IP地址、指定端口的服务器
[socket connectToHost:host onPort:port withTimeout: error:&error];
if(error)
{
NSLog(@”连接出现错误:%@”, error);
}
}
@end
|
|
|
上面程序中的第1行红色字代码创建了一个AsyncSocket,并指定该视图控制器本身作为它的delegate对象,这意味着该视图控制器本身需要实现AsyncSocketDelegate协议,并实现该协议中特定的方法.程序中的第2行红色字代码调用了AsyncSocket的connectToHost:onPort:error: 方法来连接指定IP地址、指定端口的服务器程序。 处理AsyncSocket网络连接及网络通信过程中的事件,该视图控制器(作为AsyncSocket的delegate)实现了onSocket:didConnectToHost:port:方法------当AsyncSocket成功连接指定服务器时激发该方法,该方法中的程序调用了AsyncSocket的readDataWithTimeout: tag:方法读取网络数据。当AsyncSocket成功读取网络数据之后,系统会自动调用视图控制器(作为AsyncSocket的delegate)的onSocket:didReadData:方法------这就实现了通过AsyncSocket读取网络数据。 |
|
iOS-----使用CFNetwork实现TCP协议的通信的更多相关文章
- python中基于tcp协议的通信(数据传输)
tcp协议:流式协议(以数据流的形式通信传输).安全协议(收发信息都需收到确认信息才能完成收发,是一种双向通道的通信) tcp协议在OSI七层协议中属于传输层,它上承用户层的数据收发,下启网络层.数据 ...
- 事件驱动的TCP协议Socket通信
事件驱动的TCP协议Socket通信 介绍 常规的Socket通信案例一般都是在某个线程中建立连接,然后用一个while(true)循环判断是或否有数据传输,但是这种方法有局限性. 1.收到消息在处理 ...
- 网络编程——TCP协议和通信
第1章 TCP通信 TCP通信同UDP通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建socket对象. 区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地 ...
- 为何基于tcp协议的通信比基于udp协议的通信更可靠?
tcp协议一定是先建好双向链接,发一个数据包要得到确认才算发送完成,没有收到就一直给你重发:udp协议没有链接存在,udp直接丢数据,不管你有没有收到. TCP的可靠保证,是它的三次握手双向机制,这一 ...
- 基于TCP协议Socket通信
服务器线程处理类 package demo4; import java.io.*; import java.net.Socket; /** * 服务器线程处理类 * @ClassName Server ...
- linux 网络编程:客户端与服务器通过TCP协议相互通信 + UDP
1.TCP编程的客户端一般步骤: 1.创建一个socket,用函数socket(): 2.设置socket属性,用函数setsockopt():* 可选: 3.绑定IP地址.端口等信息到socket上 ...
- python 之网络编程(基于TCP协议Socket通信的粘包问题及解决)
8.4 粘包问题 粘包问题发生的原因: 1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包),这样接收端,就难于分辨出来了,必须提供科学的拆包机制. ...
- 阶段1 语言基础+高级_1-3-Java语言高级_07-网络编程_第2节 TCP协议_4_TCP通信的服务器端代码实现
表示服务器的类是ServerSocket 启动服务器端 再启动客户端 客户端代码修改获取服务端会写的数据 先启动服务器端,再启动客户端 客户端打印: 服务器端读取:
- TCP协议学习笔记(一)首部以及TCP的三次握手连接四次挥手断开
TCP协议是一种面向连接的.可靠的流协议. 流即不间断的数据结构.这样能够保证接收到数据顺序与发送相同.但是犹如数据间没有间隔,因此在TCP通信中,发送端应用可以在自己所要发送的消息中设置一个标示长度 ...
随机推荐
- JavaScript中对象数组 作业题目以及作业
var BaiduUsers = [], WechatUsers = []; var User = function(id, name, phone, gender, age, salary) { t ...
- Mysql tinyint长度为1时在java中被转化成boolean型
MySql 中的tinyint(1)的使用 在MySql中如何定义像Java中类型的Boolean类型数据..其实,mysql中 是没有直接定义成Boolean这种数据类型. 它只能定义成 tinyi ...
- 模版层Template layer
每一个Web框架都需要一种很便利的方法用于动态生成HTML页面. 最常见的做法是使用模板. 模板包含所需HTML页面的静态部分,以及一些特殊的模版语法,用于将动态内容插入静态部分. 说白了,模板层就是 ...
- JavaScript权威指南--脚本化HTTP
知识要点 超文本传输协议(HTTP)规定web浏览器如何从web服务器获取文档和向web服务器发送表单内容,以及web服务器如何响应这些请求和提交.web浏览器会处理大量的HTTP.通常,HTTP并不 ...
- [转]mysql-mmm集群(多实例)
一.需求说明 最近一直在学习mysql-mmm,想以后这个架构也能用在我们公司的业务上,我们公司的业务是单机多实例部署,所以也想把mysql-mmm部署成这样,功夫不负有心人,我成功了,和大家分享一下 ...
- 16S 基础知识、分析工具和分析流程详解
工作中有个真理:如果你连自己所做的工作的来龙去脉都讲不清楚,那你是绝对不可能把这份工作做好的. 这适用于任何行业.如果你支支吾吾,讲不清楚,那么说难听点,你在混日子,没有静下心来工作. 检验标准:随时 ...
- android--------微信 Tinker 热修复 (二)
前面简单介绍了一下Tinker热修复,今天就来分享一下如何在Android中使用,希望对各位有帮助. 1:Tinker 接入指南 在项目的build.gradle中,添加tinker-patch-gr ...
- 新项目中使用的linux命令
要通过跳板机进入内网之后,访问内网域名 mysql -h xxxxxxx -u u_caojiangjiang -p -P 3306 上传文件: scp -r /Users/qudian/Deskto ...
- hdu-3276-dp+二分+单调队列
Star Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- dp练习(9)——最大乘积
1017 乘积最大 2000年NOIP全国联赛普及组NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 题目描述 Desc ...