博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
网络编程TCP总结及实践-C语言
阅读量:6656 次
发布时间:2019-06-25

本文共 14996 字,大约阅读时间需要 49 分钟。

网络变成首先要注意IP和port的转换,如今电脑基本上是主机字节序,存储依照小端方式,而在网络中传输统一使用大端方式,所以网络变成首先要注意字节序的转换。

一个经常使用的ip转换程序的实现:

#include 
#include
#include
#include
#define CHIP(ip) \(ip&0xff)<<24 |\(ip&(0xff<<8))<<8 |\(ip&(0xff<<16))>>8|\(ip&(0xff<<24))>>24int main(int argc,char *argv[]){ char buf[100]=""; int ip[4]={0}; int oldip,newip; scanf("%s",buf); sscanf(buf,"%d.%d.%d.%d",&ip[0],&ip[1],&ip[2],&ip[3]); //格式化输入,注意去地址符号 printf("%d %d %d %d\n",ip[0],ip[1],ip[2],ip[3]); oldip=(ip[3]<<24)|(ip[2]<<16)|(ip[1]<<8)|ip[0]; printf("%x\n",oldip);// newip=(ip[0]<<24)|(ip[1]<<16)|(ip[2]<<8)|ip[3]; newip=CHIP(oldip);//在计算机中依照十六进制存储的 printf("%x\n",newip); //printf("%d %d %d %d \n",a[0],a[1],a[2],a[3]); memset(buf,0,100); int i; for(i=0;i<4;i++)//将大端模式的IP转换为十进制 好像有BUG { buf[i]=((unsigned int)(newip&((unsigned int)0xff<<8*i))>>8*i); } fprintf(stdout,"%d.%d.%d.%d\n",buf[3],buf[2],buf[1],buf[0]); return 0;}

在网络传输 规定使用大端模式发送。小端模式转大端模式 能够使用这样的宏定义

比如小端模式下十六进制的IP为  64 01 a8 c0 //192.168.1.100

#define CHIP(ip)  \

(ip&0xff)<<24 | \

(ip&(0xff<<8))<<8 | \

(ip&(0xff<<16))>>8 | \

(ip&(0xff<<24))>>24

转化后的大端模式为:c0 a8 01 64

套接口的概念:

模式 IP:port

套接口,也叫“套接字”。是操作系统内核中的一个数据结构。它是网络中的节点进行相互通信的门户。它是网络进程的ID。

网络通信,归根究竟还是进程间的通信(不同计算机上的进程间通信)。在网络中。每个节点(计算机或路由)都有一个网络地址。也就是IP地址。

两个进程通信时,首先要确定各自所在的网络节点的网络地址。可是,网络地址仅仅能确定进程所在的计算机,而一台计算机上非常可能同一时候执行着多个进程,所以仅凭网络地址还不能确定究竟是和网络中的哪一个进程进行通信。因此套接口中还须要包含其它的信息。也就是port号(PORT)。在一台计算机中,一个port号一次仅仅能分配给一个进程,也就是说,在一台计算机中,port号和进程之间是一一相应关系。

所以,使用port号和网络地址的组合能够唯一的确定整个网络中的一个网络进程。

port号的概念:

在网络技术中,port大致有两种意思:一是物理意义上的port。如集线器、交换机、路由器等用于连接其它网络设备的接口。

二是指TCP/IP协议中的port,port号的范围从0~65535,一类是由互联网指派名字和号码公司ICANN负责分配给一些经常使用的应用程序固定使用的“周知的port”。其值一般为0~1023.比如http的port号是80,ftp为21。ssh为22。telnet为23等。另一类是用户自定义的,一般是大于1024的整型值。

 socket概念

Linux中的网络编程是通过socket接口来进行的。

socket是一种特殊的I/O接口。它也是一种文件描写叙述符

它是一种经常使用的进程之间通信机制。通过它不仅能实现本地机器上的进程之间的通信。并且通过网络可以在不同机器上的进程之间进行通信。

每个socket都用一个半相关描写叙述{

协议、本地地址、本地port}来表示一个完整的套接字则用一个相关描写叙述{
协议、本地地址、本地port、远程地址、远程port}来表示socket也有一个类似于打开文件的函数调用。该函数返回一个整型的socket描写叙述符,随后的连接建立、传输数据等操作都是通过socket来实现的

 socket类型 

(1)流式socketSOCK_STREAM用于TCP通信

流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了传输数据的正确性和顺序性

(2)数据报socketSOCK_DGRAM用于UDP通信

数据报套接字定义了一种无连接的服务数据通过相互独立的报文进行传输是无序的而且不保证是可靠、无差错的。使用数据报协议UDP

(3)原始socketSOCK_RAW用于新的网络协议实现的測试等

原始套接字同意对底层协议如IPICMP进行直接訪问,它功能强大但使用较为不便,主要用于一些协议的开发

头文件<netinet/in.h>

Struct sockddr _in

{

Sa_family_t Sin_family;//存储IPV4格式,见man socket

In_port_t sin_port; //存储端口号

Struct in_addr sin_addr;//IP结构体

};

Struct in_addr

{

In_addr_t s_addr;// 存储IP

};

Typedef unsigned short int sa_family_t;

#define __SOCKADDR_COMMON(sa_prefix) \

  sa_family_t   sa_prefix##family  //拼接成sin_family

sa_family:AF_INET  IPv4协议             AF_INET6  IPv6协议

经常使用的IP和port转换函数

假设称某个系统所採用的字节序为主机字节序,则它可能是小端模式的,也可能是大端模式的。

而port号和IP地址都是以网络字节序存储的,不是主机字节序,网络字节序都是大端模式。要把主机字节序和网络字节序相互相应起来,须要对这两个字节存储优先顺序进行相互转化。这里用到四个函数:主机转网络序

htons(),ntohs(),htonl()ntohl().

这四个地址分别实现网络字节序和主机字节序的转化。这里的h代表host,n代表network,s代表short,l代表long。通常16位的IPport号用s代表,而IP地址用l来代表。

#include <arpa\inet.h>
uint16_t htons(uint_16 hostshort) 一个短整形数
uint16_t ntohs(uint_16 hostshort) 

IPv4的函数原型:

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

int inet_aton(const char *straddr, struct in_addr *addrptr); //点十进制的IP转化为网络字节序。

并保存在在IP结构体

char *inet_ntoa(struct in_addr inaddr);//把网络字节序转化了点十进制IP sockaddr.sin_addr

in_addr_t inet_addr(const char *straddr);  //较为经常使用 将十进制数IP转化为sockaddr.sin_addr.s_addr

函数inet_aton():将点分十进制数的IP地址转换成为网络字节序的32位二进制数值。

參数straddr:存放输入的点分十进制数IP地址字符串。

參数addrptr:传出參数,保存网络字节序的32位二进制数值。

函数inet_ntoa():将网络字节序的32位二进制数值转换为点分十进制的IP地址。

函数inet_addr():功能与inet_aton同样,可是结果传递的方式不同。

inet_addr()若成功则返回32位二进制的网络字节序地址。

#include 
#include
#include
#include
int main() { char ip[] = "192.168.0.101"; struct in_addr myaddr; /* inet_aton */ int iRet = inet_aton(ip, &myaddr); printf("%x\n", myaddr.s_addr); /* inet_addr */ printf("%x\n", inet_addr(ip)); /* inet_pton */ iRet = inet_pton(AF_INET, ip, &myaddr); printf("%x\n", myaddr.s_addr); myaddr.s_addr = 0xac100ac4; /* inet_ntoa */ printf("%s\n", inet_ntoa(myaddr)); /* inet_ntop */ inet_ntop(AF_INET, &myaddr, ip, 16); puts(ip); return 0; }

 名字地址转化

通常。人们在使用过程中都不愿意记忆冗长的IP地址,尤其到Ipv6时,地址长度多达128位,那时就更加不可能一次性记忆那么长的IP地址了。

因此。使用主机名或域名将会是非常好的选择。

主机名与域名的差别:主机名通常在局域网里面使用,通过/etc/hosts文件,主机名能够解析到相应的ip;域名一般是再internet上使用。

域名比如:www.baidu.com

在linux中,有一些函数能够实现主机名和地址的转化。最常见的有gethostbyname()、gethostbyaddr()等。它们都能够实现IPv4和IPv6的地址和主机名之间的转化。

当中gethostbyname()是将主机名转化为IP地址。gethostbyaddr()则是逆操作,是将IP地址转化为主机名。

函数原型:

#include <netdb.h>

struct hostent* gethostbyname(const char* hostname);

struct hostent* gethostbyaddr(const char* addr, size_t len, int family);

结构体:

struct hostent		{			char *h_name;		/*正式主机名*/			char **h_aliases;	/*主机别名*/			int h_addrtype;		/*主机IP地址类型 IPv4为AF_INET*/			int h_length;		/*主机IP地址字节长度,对于IPv4是4字节。即32位*/			char **h_addr_list;	/*主机的IP地址列表*/		}

#define  h_addr  h_addr_list[0] /*保存的是ip地址*/

函数gethostbyname():用于将域名(www.baidu.com)或主机名转换为IP地址。參数hostname指向存放域名或主机名的字符串。

函数gethostbyaddr():用于将IP地址转换为域名或主机名。參数addr是一个IP地址,此时这个ip地址不是普通的字符串,而是要通过函数inet_aton()转换。

len为IP地址的长度。AF_INET为4。

family可用AF_INET:Ipv4或AF_INET6:Ipv6。

#include 
#include
#include
int main(int argc, char **argv){char *ptr, **pptr; struct hostent *hptr; char str[32] = {'\0'};/* 取得命令后第一个參数,即要解析的域名或主机名 */ptr = argv[1]; //如www.baidu.com/* 调用gethostbyname()。

结果存在hptr结构中 */ if((hptr = gethostbyname(ptr)) == NULL) { printf(" gethostbyname error for host:%s\n", ptr); return 0; } /* 将主机的规范名打出来 */ printf("official hostname:%s\n",hptr->h_name); /* 主机可能有多个别名,将全部别名分别打出来 */ for(pptr = hptr->h_aliases; *pptr != NULL; pptr++) printf(" alias:%s\n",*pptr); /* 依据地址类型。将地址打出来 */ switch(hptr->h_addrtype) { case AF_INET: case AF_INET6: pptr=hptr->h_addr_list; /* 将刚才得到的全部地址都打出来。

当中调用了inet_ntop()函数 */ for(; *pptr!=NULL; pptr++) printf(" address:%s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str))); printf(" first address: %s\n", inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str))); break; default: printf("unknown address type\n"); break; } return 0; }

socket编程

使用TCP协议的流程图

server端:

1. 头文件包括:

#include<sys/types.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

#include<unistd.h>

#include<string.h>

#include <stdio.h>

#include <stdlib.h>

 

2. socket函数:生成一个套接口描写叙述符(被看作一个文件)

原型:int socket(int domain,int type,int protocol);

參数:domain{ AF_INET:Ipv4 网络协议 AF_INET6:IPv6网络协议}

  type{tcp:SOCK_STREAM   udp:SOCK_DGRAM}

  protocol 指定socket所使用的传输协议编号。通常为0.

返回值:成功则返回套接口描写叙述符。失败返回-1。

经常使用实例:int sfd = socket(AF_INET, SOCK_STREAM, 0);

      if(sfd == -1){perror("socket");exit(-1);}

 

3. bind函数:用来绑定一个port号和IP地址。使套接口与指定的port号和IP地址相关联。

原型:int bind(int sockfd,struct sockaddr * my_addr,int addrlen);

參数:sockfd为前面socket的返回值。

  my_addrà为结构体指针变量

对于不同的socket domain定义了一个通用的数据结构

struct sockaddr  //此结构体不经常使用
{
unsigned short int sa_family;  //调用socket()时的domain參数,即AF_INET值。
char sa_data[14];  //最多使用14个字符长度
};
此sockaddr结构会因使用不同的socket domain而有不同结构定义,
比如使用AF_INET domain。其socketaddr结构定义便为

struct sockaddr_in  //经常使用的结构体{unsigned short int sin_family;  //即为sa_family èAF_INETuint16_t sin_port;  //为使用的port编号struct in_addr sin_addr;  //为IP 地址unsigned char sin_zero[8];  //未使用};struct in_addr{uint32_t s_addr;};
addrlen  sockaddr的结构体长度。一般是计算sizeof(struct sockaddr);

返回值:成功则返回0,失败返回-1

经常使用实例:struct sockaddr_in my_addr;  //定义结构体变量

memset(&my_addr, 0, sizeof(struct sockaddr)); //将结构体清空  //或bzero(&my_addr, sizeof(struct sockaddr));  my_addr.sin_family = AF_INET;  //表示採用Ipv4网络协议  my_addr.sin_port = htons(8888);  //表示端口号为8888。一般是大于1024的一个值。//htons()用来将參数指定的16位hostshort转换成网络字符顺序my_addr.sin_addr.s_addr = inet_addr("192.168.0.101"); //inet_addr()用来将IP地址字符串转换成网络所使用的二进制数字。假设为INADDR_ANY。这表示server自己主动填充本机IP地址。

if(bind(sfd, (struct sockaddr*)&my_str, sizeof(struct socketaddr)) == -1) {perror("bind");close(sfd);exit(-1);}

(注:通过将my_addr.sin_port置为0。函数会自己主动为你选择一个未占用的端口来使用。相同。通过将my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自己主动填入本机IP地址。)

 

4. listen函数:使server的这个port和IP处于监听状态,等待网络中某一客户机的连接请求。假设client有连接请求,port就会接受这个连接。

原型:int listen(int sockfd,int backlog);

參数:sockfdà为前面socket的返回值.即sfd

backlogà指定同一时候能处理的最大连接要求。通常为10或者5。最大值可设至128

返回值:成功则返回0,失败返回-1

经常使用实例:

if(listen(sfd, 10) == -1)  {perror("listen");close(sfd);exit(-1);}

 

5. accept函数:接受远程计算机的连接请求,建立起与客户机之间的通信连接。

server处于监听状态时,假设某时刻获得客户机的连接请求,此时并非马上处理这个请求,而是将这个请求放在等待队列中。当系统空暇时再处理客户机的连接请求。当accept函数接受一个连接时,会返回一个新的socket标识符,以后的传输数据和读取就要通过这个新的socket编号来处理。原来參数中的socket也能够继续使用。继续监听其他客户机的连接请求。

(也就是说,类似于移动营业厅,假设有客户打电话给10086,此时server就会请求连接,处理一些事务之后,就通知一个话务员接听客户的电话,也就是说,后面的全部操作,此时已经于server没有关系。而是话务员跟客户的交流。相应过来,客户请求连接我们的server。我们server先做了一些绑定和监听等等操作之后,假设同意连接,则调用accept函数产生一个新的套接字。然后用这个新的套接字跟我们的客户进行收发数据。

也就是说。server跟一个client连接成功。会有两个套接字。

原型:int accept(int s,struct sockaddr * addr,int * addrlen);

參数:sà为前面socket的返回值.即sfd

  addrà为结构体指针变量,和bind的结构体是同种类型的,系统会把远程主机的信息(远程主机的地址和port号信息)保存到这个指针所指的结构体中。

  addrlenà表示结构体的长度。为整型指针

返回值:成功则返回新的socket处理代码new_fd。失败返回-1

经常使用实例:

struct sockaddr_in clientaddr;  memset(&clientaddr, 0, sizeof(struct sockaddr));  int addrlen = sizeof(struct sockaddr);  int new_fd = accept(sfd, (struct sockaddr*)&clientaddr, &addrlen);  if(new_fd == -1)  {perror("accept");close(sfd);exit(-1);}  printf("%s %d success connect\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));

 

6. recv函数:用新的套接字来接收远端主机传来的数据,并把数据存到由參数buf 指向的内存空间

原型:int recv(int sockfd,void *buf,int len,unsigned int flags);

參数:sockfdà为前面accept的返回值.即new_fd。也就是新的套接字。

  buf 表示缓冲区

  len 表示缓冲区的长度

  flags 通常为0

返回值:成功则返回实际接收到的字符数。可能会少于你所指定的接收长度。失败返回-1

经常使用实例:

char buf[512] = {0}; if(recv(new_fd, buf, sizeof(buf), 0) == -1)  最好传长度 来防止堵塞  {perror("recv");close(new_fd);close(sfd);exit(-1);}  puts(buf);

 

7. send函数:用新的套接字发送数据给指定的远端主机

原型:int send(int s,const void * msg,int len,unsigned int flags);

參数:sà为前面accept的返回值.即new_fd

  msgà一般为常量字符串

  lenà表示长度

  flagsà通常为0

返回值:成功则返回实际传送出去的字符数,可能会少于你所指定的发送长度。失败返回-1

经常使用实例:

if(send(new_fd, "hello", 6, 0) == -1){perror("send");close(new_fd);close(sfd);exit(-1);}

 

8. close函数:当使用完文件后若已不再须要则可使用close()关闭该文件,而且close()会让数据写回磁盘,并释放该文件所占用的资源

原型:int close(int fd);

參数:fdà为前面的sfd,new_fd

返回值:若文件顺利关闭则返回0。错误发生时返回-1

经常使用实例:

close(new_fd);  close(sfd);
//通过TCP实现的服务器文件下载功能
#ifndef __MY_SOCKET_H__#define __MY_SOCKET_H__#include 
#include "msg.h"#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MSG_SIZE 8188 #define MSG_LEN (8192 - MSG_SIZE)typedef struct tag{ int s_len ; char s_buf[MSG_SIZE];} MSG;int listenfd_init(char* ip, char* port);void handle_request(int fd_client);#endif
#include "my_socket.h"int listenfd_init(char* ip, char* port){	int fd_server ;	struct sockaddr_in server_addr ;	int reuse = 1 ;	if((fd_server = socket(AF_INET,SOCK_STREAM, 0) ) == -1)	{		perror("socket");		exit(-1);	}	if(0 != setsockopt(fd_server,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(int)))	{		perror("setsockopt");		close(fd_server);		exit(-1);	}	memset(&server_addr, 0 , sizeof(server_addr)) ;	server_addr.sin_family = AF_INET ;	server_addr.sin_port = htons(atoi(port));	server_addr.sin_addr.s_addr = inet_addr(ip);	if(-1 == bind(fd_server,(struct sockaddr*)&server_addr,sizeof(server_addr)))	{		perror("bind");		close(fd_server);		exit(-1);	}	if(-1 == listen(fd_server,5))	{		perror("listen");		close(fd_server);		exit(-1);	}	return fd_server ;}void handle_request(int fd_client){	int fd_file ;	int read_n ;	MSG msg,snd_msg ;	memset(&msg,0, sizeof(msg));	recv(fd_client,&msg, MSG_LEN , 0) ;	recv(fd_client,&msg.s_buf,msg.s_len,0) ;	printf("recv msg :%s \n", msg.s_buf);	fd_file = open(msg.s_buf,O_RDONLY);	while(memset(&snd_msg,0,sizeof(msg)), (read_n = read(fd_file,snd_msg.s_buf,MSG_SIZE)) > 0)	{		snd_msg.s_len = read_n ;		send(fd_client, &snd_msg, snd_msg.s_len + MSG_LEN ,0);	}	snd_msg.s_len = 0 ;	send(fd_client, &snd_msg, snd_msg.s_len + MSG_LEN ,0);	close(fd_file);	close(fd_client);}

#include "mysocket.h"int main(int argc,char *argv[])//ip port{	if(argc!=3)	{		printf("too few argument!\n");		exit(-1);	}	int fd_sever,fd_client,fd_file;	int reuse=1;//使IP能够重用	struct sockaddr_in sever_addr,client_addr;	int addrlen=sizeof(client_addr);	int read_n;//	char file_name[128]="";	MSG msg,snd_msg;//8K	if((fd_sever=socket(AF_INET,SOCK_STREAM,0))==-1)	{		perror("open");		exit(-1);	}	if(0!=setsockopt(fd_sever,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(int)))	{		perror("setopt");		close(fd_sever);		exit(-1);	}	memset(&sever_addr,0,sizeof(sever_addr));	sever_addr.sin_family=AF_INET;//ipv4	sever_addr.sin_port=htons(atoi(argv[2]));	sever_addr.sin_addr.s_addr=inet_addr(argv[1]);	//绑定	if(-1==bind(fd_sever,(struct sockaddr*)&sever_addr,sizeof(sever_addr)))	{		perror("bind");		close(fd_sever);		exit(-1);	}	printf("bind success!\n");	if(-1==listen(fd_sever,0))	{		perror("listen");		close(fd_sever);		exit(-1);	}	printf("listen success\n");	while(fd_client=accept(fd_sever,(struct sockaddr *)&client_addr,&addrlen))//不想接受信息能够不填写结构体 NULL	{		printf("client connect:%s:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));//网络端口和IP转为主机模式//	memset(file_name,0,128);	memset(&msg,0,sizeof(msg));	//msg用于保存文件名称极其大小	recv(fd_client,&msg.s_len,MSG_LEN,0);//先收长度,再收文件名称	recv(fd_client,&msg.s_buf,msg.s_len,0);	printf("recvmsg:%s\n",msg.s_buf);	fd_file=open(msg.s_buf,O_RDONLY);//snd_msg	while(memset(&snd_msg,0,sizeof(snd_msg)),(read_n=read(fd_file,snd_msg.s_buf,MSG_SIZE))>0)	{		snd_msg.s_len=read_n;//保存每次读取的长度		send(fd_client,&snd_msg,snd_msg.s_len+MSG_LEN,0);	}	snd_msg.s_len=0;	send(fd_client,&snd_msg,snd_msg.s_len+MSG_LEN,0);	close(fd_file);	close(fd_client);	}		return 0;}
 
#include "mysocket.h"int main(int argc,char *argv[]){	if(argc!=3)	{		printf("too few argument!\n");		exit(-1);	}	MSG msg,rcv_msg;//一个用于发送文件名称。一个用于接受文件	int fd_client,fd_file;	struct sockaddr_in sever_addr;//保存server端信息	if((fd_client=socket(AF_INET,SOCK_STREAM,0))==-1)	{		perror("socket");		exit(-1);	}	memset(&sever_addr,0,sizeof(sever_addr));	sever_addr.sin_family=AF_INET;	sever_addr.sin_port=htons(atoi(argv[2]));	sever_addr.sin_addr.s_addr=inet_addr(argv[1]);	//建立与server的连接	if(-1==connect(fd_client,(struct sockaddr *)&sever_addr,sizeof(sever_addr)))	{		perror("connect");		close(fd_client);		exit(-1);	}	//输入要下载的文件名称	memset(&msg,0,sizeof(msg));	printf("input:");	scanf("%s",msg.s_buf);       	fd_file=open(msg.s_buf,O_WRONLY|O_CREAT,0666);//创建相应的文件	msg.s_len=strlen(msg.s_buf);//发送文件名称的长度	send(fd_client,&msg,msg.s_len+MSG_LEN,0);	//MSG_LEN表示int s_len的长度 msg.s_len存储的是文件名称字符串的长度	int total;	while(1)	{		memset(&rcv_msg,0,sizeof(rcv_msg));//接受文件的结构体		recv(fd_client,&rcv_msg,MSG_LEN,0);//先接收字符串长度。存储在rcv_msg.s_len		total+=rcv_msg.s_len;		system("clear");		printf("downloading...%.2f kb\n",(double)total/1024);		if(rcv_msg.s_len>0)		{				recv(fd_client,rcv_msg.s_buf,rcv_msg.s_len,0);		write(fd_file,rcv_msg.s_buf,strlen(rcv_msg.s_buf));//写入文件里		}		else		{			break;		}	}	close(fd_file);	close(fd_client);	return 0;}

你可能感兴趣的文章
Base62x算法改进并增加Base62x in Python
查看>>
小项目创意大集合
查看>>
php汉字繁简体互转扩展:openccpp
查看>>
Bootstrap 进度条
查看>>
Mysql 学习记录(一)
查看>>
some Linux command
查看>>
js表情图实现
查看>>
MySql中的排序规则utf8_unicode_ci、utf8_general_ci的区别总结
查看>>
sqoop常见错误
查看>>
Android与JS之JsBridge使用与源码分析
查看>>
js中eval详解
查看>>
Maven编译期管理插件——maven-compiler-plugin
查看>>
yii2中ajax页面中a标签js跳转
查看>>
Laravel 5.3之 Query Builder 源码解析(中)
查看>>
动态样式语言Scss&Less介绍与区别
查看>>
aptana--python开发工具使用技巧
查看>>
纯CSS画的基本图形(矩形、圆形、三角形、多边形、爱心、八卦等)
查看>>
使用PM2来部署nodejs项目
查看>>
Postgres 小技巧
查看>>
保证分布式系统数据一致性的6种方案
查看>>