asource insight.f7搜函数
一、网络介绍
协议:规则
tcp/ip模型4层:
应用层{http超文本传输协议 ftp文件传输协议 telnet远程登录ssh安全外壳协议stmp简单邮件发送 pop3收邮件}
传输层{tcp传输控制协议,udp用户数据包协议}
网络层{ip网际互联协议 icmp网络控制消息协议 igmp网络组管理协议}
网络接口层{arp地址转换协议,rarp反向地址转换协议,mpls多协议标签交换}
IP协议:网络层。IP报文协议。包地址,校验。
ping:ICMP协议。
传输层:你想以什么方式把数据给对方。
应用层:浏览器走的http协议。
ssh协议。
XMPP协议 即时通信协议 腾讯没有用
QICQ:
国外的聊天用的开源的。
能够做协议
每一个计算机有一个ip。端口的背后是进程。
用户自己定义的端口通常大于1024.
牛逼的留给1024以下。
必须要学会TCP、UDP。用来实现上层协议。
需要进行三次握手的协议。tcp
每次我都把数据丢给你。upd
积极确认重传:积极的概念。
TCP:网络稳定不会重复建立连接
什么时候用UDP:优酷视频,不需要建立连接的情况。一般是视频,摄像头。
二、网络相关概念
套接口,套接字(socket)
在一台计算机中,一个端口号一次只能分配给一个进程,也就是说,在一台计算机中,端口号和进程之间是一一对应关系。
short 短整形,2个字节;long 长整形,4个字节;unsigned short,无符号短整型,2个字节;unsigned long,无符号长整形,4个字节。
struct sockaddr_in { //旧的版本是struct sockaddr,这个版本可以兼容旧版本。
short int sa_family; //地址族。如果是IPv4协议,就写:AF_INET;如果是IPv6协议,就写:AF_INET6。
unsigned short int sin_port; //端口号。如果端口是2000,不能直接赋2000的,因为网络字节序的问题。
struct in_addr sin_addr; //IP地址
unsigned char sin_zero[8]; //填充0 以保持与struct sockaddr同样大小
};
struct in_addr {
unsigned long int s_addr; //32位IPv4地址,网络字节序的。无符号长整形,4个字节。
};
数据存储优先顺序的转换(大小端的问题):
网络字节序:在网络上是大端存储的,但是在自己的计算机上大小端都有可能,如Intel是小端存储的。
如数据实际是0x12345678,则在网络中存储的是0x12345678,Intel内存中是:0x78563412。
要把主机字节序和网络字节序相互对应起来,需要对这两个字节存储优先顺序进行相互转化。这里用到四个数:
htons() host to net short //如果主机是大端存储的,就不变,如果主机是小端存储的,就反过来
ntohs() net to host short
htonl() host to net long
ntohl() net to host long
例:
int main(){
unsigned short i=0x1234; //定义i=0x1234,在主机上存的是:0x3412
unsigned short j;
unsigned short k;
j=htons(i); //host to net short 把主机转成网络格式。
printf("j=%x\n",j); //
k=ntohs(j); //net to host short 把网络转成主机格式。
printf("k=%x\n",k);
return 0;
}
地址格式转换:
a:点分十进制
n:net
int inet_aton(const char *straddr, struct in_addr *addrptr); //将点分十进制的straddr 转化为:32位的IPV4的网络字节序,保存到addrptr中
char *inet_ntoa(struct in_addr inaddr); //将32位的网络字节序,转化为点分十进制字符串。
in_addr_t inet_addr(const char *straddr); //in_addr_t 就是unsigned long int ,代表s_addr,即struct in_addr结构体中的参 数。所以该句直接把点分十进制转换为32位网络字节序的IP地址。
const char *inet_ntop(int family, const void *src, char *dst, socklen_t len); //family参数为AF_INET,表示是IPv4协议;为AF_INET6,表示 IPv6协议。把网络字节序转换为点分十进制字符串。与inet_ntoa类似。但是 inet_ntoa()只能转换成32位的网络字节序(即IPV4地址),而这个函数可以转换 IPV4或IPV6.
int inet_pton(int family, const char *src, void *dst);
例:
int main(int argc,char* argv[]){
if(argc!=2){
printf("error args\n");
return -1;
}
struct in_addr addr;
int ret;
ret=inet_aton(argv[1],&addr); //将argv[1]转化为网络字节序,存到结构体addr中。如:192.168.4.155
if(ret!=1){ //成功返回1
perror("inet_aton");
return -1;
}
printf("%x\n",addr.s_addr); //打印出:9b04a8c0. c0就是192,a8就是168,9b就是155。因为是网络是大端 存储的,所以这么存。
printf("%s\n",inet_ntoa(addr));
printf("inet_addr=%x\n",inet_addr(argv[1])); //点分十进制转换为32位网络字节序的IP地址
return 0;
}
点分十进制转换为IPV6的
域名地址转换
域名,如:www.baidu.com。域名解析服务器。买域名,需要花钱。一个域名可以对应多个IP地址。
struct hostent* gethostbyname(const char* hostname); //通过域名得到地址
struct hostent{
char *h_name; //正式主机名。 域名也有别名,www.a.shifen.com是百度的真实域名。
char **h_aliases; //主机别名。二级指针,常用于char * buf[],是一个指针数组
int h_addrtype; //主机IP地址类型,IPv4为AF_INET。
int h_length; //主机IP地址字节长度,对于IPv4是4字节,即32位。
char **h_addr_list; //主机的IP地址列表,因为一个域名可以有多个IP地址。二级指针,常用语字符指针数组。
}
例:通过域名得到地址
int main(int argc,char* argv[]){
if(argc !=2){
printf("error args\n");
return -1;
}
struct hostent *p;
p=gethostbyname(argv[1]); //输入域名,通过域名对应域名的IP地址等信息,返回给指针p。
if(p==NULL){
perror("gethostbyname");
return -1;
}
printf("h_name=%s\n",p->h_name); //打印出正式主机名。
char str[32]={0};
char *ptr;
for(ptr=*(p->h_aliases);ptr!=NULL;){ //p->h_aliases是二级指针。*(p->h_aliases)等于*((p->h_aliases)+0)等于(p->h_aliases)[0]。
printf("ptr=%s\n",ptr); //打印出主机别名。
(p->h_aliases)++; //(p->h_aliases)+n 指向一维数组(p->h_aliases)[n]
ptr=*(p->h_aliases);
}
ptr=*(p->h_addr_list);
while(ptr!=NULL){
memset(str,0,sizeof(str));
inet_ntop(p->h_addrtype,ptr,str,sizeof(str)); //p->h_addrtype为IP地址的类型。将网络字节序转换为点分十进制。ptr是要转换的 字符串,str存储转化后的点分十进制。
printf("str=%s\n",str);
(p->h_addr_list)++;
ptr=*(p->h_addr_list);
}
return 0;
}
标准C:是一个标准,需要代码实现。
glibc:linux下,开源的实现了标准C的代码。libc.so是glibc使用的库。
操作系统提供系统调用。
中间层使用系统调用。
SOCKET编程:TCP通信
通过socket接口TCP及UDP通信。TCP/UDP是协议。
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.
sockaddr结构体:
struct sockaddr_in{ //常用的结构体。sockaddr结构会因使用不同的socket domain而有不同结构定义
unsigned short int sin_family; //地址族。IPV4为:AF_INET
uint16_t sin_port; //为使用的port编号。一般1024以上自己使用。
struct in_addr sin_addr; //为IP地址
unsigned char sin_zero[8]; //未使用
};
struct in_addr {
uint32_t s_addr; //32位IPv4地址,网络字节序的。无符号长整形,4个字节。
};
int bind(int sockfd,struct sockaddr * my_addr,int addrlen); //my_addr为结构体指针变量。addrlen是sockaddr的结构体长度。通常是计 算sizeof(struct sockaddr) 。
int listen(int sockfd,int backlog); //backlog为同时能处理的最大连接要求。一个端口可以同时有多个客户端来连接。
int accept(int s,struct sockaddr * addr,int * addrlen); //s即sfd。addr为结构体指针变量,和bind的结构体是同种类型的,系统会把远程 主机的信息(远程主机的地址和端口号信息)保存到这个指针所指的结构体中。成 功则返回新的socket处理代码new_fd,失败返回-1回值。之后客户端通过new_fd来通 信。
int recv(int sockfd,void *buf,int len,unsigned int flags); //sockfd为前面accept的返回值.即new_fd,也就是新的套接字。buf表示缓冲区, len表示缓冲区的长度.
int send(int s,const void * msg,int len,unsigned int flags); //用新的套接字发送数据给指定的远端主机.s为前面accept的返回值.即new_fd. msg一般为常量字符串,len表示长度。
int close(int fd);
int connect(int sockfd,struct sockaddr * serv_addr,int addrlen); //sockfd为前面socket的返回值,即sfd。serv_addr为结构体指针变量,存储 着远程服务器的IP与端口号信息。addrlen表示结构体变量的长度。
例一:使用socket接口实现TCP通信
//使用socket接口实现tcp通信的服务器端 int main(int argc,char** argv){ if(argc!=3){ printf("error args\n"); return -1; } int sfd=socket(AF_INET,SOCK_STREAM,0); //生成套接口描述符sfd。 if(-1==sfd){ perror("socket"); return -1; } printf("sfd=%d\n",sfd); //新建的从3开始 struct sockaddr_in ser; //socket信息数据结构 memset(&ser,0,sizeof(ser)); ser.sin_family=AF_INET; //IPV4. ser.sin_port=htons(atoi(argv[2])); //主机转网络,端口。先转成数字。 ser.sin_addr.s_addr=inet_addr(argv[1]); //IP地址。 int ret; ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr)); //给sfd绑定IP地址和端口 号,&ser为结构体指针。 if(-1==ret){ perror("bind"); return -1; } ret=listen(sfd,10); //监听。最大连接数为10. if(-1==ret){ perror("listen"); return -1; } printf("listen success\n"); struct sockaddr_in client; memset(&client,0,sizeof(client)); int addrlen=sizeof(struct sockaddr); int new_fd=accept(sfd,(struct sockaddr*)&client,&addrlen); //接受连接请求 if(-1==new_fd){ perror("accept"); return -1; } printf("new_fd=%d\n",new_fd); printf("client IP=%s,client port=%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); char buf[128]={0}; ret=recv(new_fd,buf,sizeof(buf),0); //接收 if(-1==ret){ perror("recv"); return -1; } printf("%s\n",buf); ret=send(new_fd,"I am server",11,0); //发送 if(-1==ret){ perror("send"); return -1; } return 0; } | //使用socket接口实现tcp通信的客户端 int main(int argc,char** argv){ //argv[1,2]存放服务器的地址和端口 if(argc !=3){ printf("error args\n"); return -1; } int sfd=socket(AF_INET,SOCK_STREAM,0); if(-1==sfd){ perror("socket"); return -1; } struct sockaddr_in ser; //保存服务器端的信息 memset(&ser,0,sizeof(ser)); ser.sin_family=AF_INET; ser.sin_port=htons(atoi(argv[2])); ser.sin_addr.s_addr=inet_addr(argv[1]); int ret; ret=connect(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr)); if(-1==ret){ perror("connect"); return -1; } char buf[128]={0}; ret=send(sfd,"I am client",11,0); //客户端通信的时候用一个 if(-1==ret){ perror("send"); return -1; } ret=recv(sfd,buf,sizeof(buf),0); if(-1==ret){ perror("recv"); return -1; } printf("%s\n",buf); return 0; } |
注意:1.客户端只有一个sfd.clint的端口是系统随机打开的一个端口。
2.自己的机子上的两个进程通信还是用之前学的共享内存,管道什么的。
3.客户端的端口是系统自动分配的。
例子二:如何实现多个客户端连接服务器
要求:服务器只有一个线程:
1.主线程能够监听客户端的连接请求;
2.能够接受已经连接的客户端发送来的数据。
3.已经连接的客户端发送某个数据,回复相同数据给它。
客户端:
1.不断开
2.可以说话给服务端
//服务器端:一个服务器能够同时监听多个客户端请求 #define NUM 10 int main(int argc,char** argv){ if(argc!=3){ printf("error args\n"); return -1; } int sfd=socket(AF_INET,SOCK_STREAM,0); if(-1==sfd){ perror("socket"); return -1; } struct sockaddr_in ser; memset(&ser,0,sizeof(ser)); ser.sin_family=AF_INET; ser.sin_port=htons(atoi(argv[2])); ser.sin_addr.s_addr=inet_addr(argv[1]); int ret; ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr)); //绑定端口和IP地址 if(-1==ret){ perror("bind"); return -1; } ret=listen(sfd,NUM); if(-1==ret){ perror("listen"); return -1; } printf("listen success\n"); int addrlen=sizeof(struct sockaddr); fd_set rdset; int new_fd[NUM]={0}; int i=0; int j; fd_set tmpset; FD_SET(sfd,&tmpset); //在tmpset中增加一个sfd char buf[128]={0}; while(1){ FD_ZERO(&rdset); memcpy(&rdset,&tmpset,sizeof(rdset)); ret=select(NUM+4,&rdset,NULL,NULL,NULL); //监听rdset中的元素,因为新 建的第一个为sfd为3,listen最大的链接数为NUM. if(ret >0){ if(FD_ISSET(sfd,&rdset)){ //如果有新连接申请 new_fd[i]=accept(sfd,(struct sockaddr*)&client,&addrlen); if(-1==new_fd[i]){ perror("accept"); return -1; } printf("client IP=%s,client port=%d\n",inet_ntoa(client.sin_addr), ntohs(client.sin_port)); FD_SET(new_fd[i],&tmpset); //放到tmpset集合中 i++; } j=0; while(new_fd[j]!=0){ if(FD_ISSET(new_fd[j],&rdset)){ //如果有客户端说话 memset(buf,0,sizeof(buf)); ret=recv(new_fd[j],buf,sizeof(buf),0); if(ret >0){ printf("%d=%s\n",new_fd[j],buf); int ret1=send(new_fd[j],buf,strlen(buf),0); if(-1==ret1){ perror("send"); return -1; } }else if(ret ==0){ //如果客户端断开连接 close(new_fd[j]); FD_CLR(new_fd[j],&tmpset); //把该客户移除集合中 }else{ exit(-1); } } j++; } } } return 0; } | //客户端 int main(int argc,char** argv){ if(argc !=3){ printf("error args\n"); return -1; } int sfd=socket(AF_INET,SOCK_STREAM,0); if(-1==sfd){ perror("socket"); return -1; } struct sockaddr_in ser; memset(&ser,0,sizeof(ser)); ser.sin_family=AF_INET; ser.sin_port=htons(atoi(argv[2]));//一定要用htons ser.sin_addr.s_addr=inet_addr(argv[1]); int ret; ret=connect(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr)); if(-1==ret){ perror("connect"); return -1; } char buf[128]={0}; while(1){ memset(buf,0,sizeof(buf)); ret=read(0,buf,sizeof(buf)); //读标准输入 if(ret <=0){ break; } ret=send(sfd,buf,strlen(buf)-1,0); if(-1==ret){ perror("send"); return -1; } memset(buf,0,sizeof(buf)); ret=recv(sfd,buf,sizeof(buf),0); if(-1==ret){ perror("recv"); return -1; } printf("%s\n",buf); } return 0; } |
注意:这是用select实现的,在epoll一节中,会用epoll实现。
SOCKET编程:UDP通信
udp都是一个sfd。tcp中的可以写NULL。upd必须要拿出来。
必须先客户端往服务器端发数据,这样服务器端才能拿到客户端的ip.
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen); //读到buf中,读len个字节。flags=0.form保存 发送过来一方的信息。fromlen=sizeof(struct sockaddr)
int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);//发送msg,发送的长度为len,flags=0,to是 发送给谁。tolen=sizeof(struct sockaddr)
例一:使用socket接口实现UDP通信
//使用socket接口实现udp通信的服务器端 int main(int argc,char** argv){ if(argc!=3){ printf("error args\n"); return -1; } int sfd=socket(AF_INET,SOCK_DGRAM,0); //UDP if(-1==sfd){ perror("socket"); return -1; } printf("sfd=%d\n",sfd); struct sockaddr_in ser; memset(&ser,0,sizeof(ser)); ser.sin_family=AF_INET; ser.sin_port=htons(atoi(argv[2])); ser.sin_addr.s_addr=inet_addr(argv[1]); int ret; ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr)); //绑定IP和端口 if(-1==ret){ perror("bind"); return -1; } struct sockaddr_in client; //定义client用于保存客户端的信息 memset(&client,0,sizeof(client)); int addrlen=sizeof(client); char buf[128]={0}; ret=recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&client,&addrlen); //服务器端必须先接收,放到buf中,读sizeof(buf)个字节,对方的信息放到client中 if(-1==ret){ perror("recvfrom"); return -1; } printf("IP=%s,PORT=%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); printf("%s\n",buf); ret=sendto(sfd,"Server",6,0,(struct sockaddr*)&client,sizeof(struct sockaddr)); //往对方写"Server",6个字节,0,发送给client。 if(-1==ret){ perror("sendto"); return -1; } close(sfd); return 0; } | //使用socket接口实现udp通信的客户端 int main(int argc,char** argv){ if(argc!=3){ printf("error args\n"); return -1; } int sfd=socket(AF_INET,SOCK_DGRAM,0); if(-1==sfd){ perror("socket"); return -1; } printf("sfd=%d\n",sfd); struct sockaddr_in ser; memset(&ser,0,sizeof(ser)); ser.sin_family=AF_INET; ser.sin_port=htons(atoi(argv[2]));//一定要用htons ser.sin_addr.s_addr=inet_addr(argv[1]); int ret; ret=sendto(sfd,"I am client",11,0,(struct sockaddr*)&ser,sizeof(struct sockaddr)); //先发送 if(-1==ret){ perror("sendto"); return -1; } struct sockaddr_in client; memset(&client,0,sizeof(client)); int addrlen=sizeof(client); char buf[128]={0}; ret=recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&client,&addrlen); if(-1==ret){ perror("recvfrom"); return -1; } printf("SERVER IP=%s,PORT=%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); printf("%s\n",buf); close(sfd); return 0; } |
注意:1.必须客户端先向服务器端发送数据,这样服务器端才能知道是谁发来的,以及向谁发。
2.
单播、广播、多播(组播).
组播:分组。如192.168.4.10-192.168.4.20是一个组