*文件传输的例子 -FTP
[]“物数网传会表应”
物理层 数据层 网络层 传输层 会话层 表示层 应用层
数据通信过程是发送方层层打包的过程,接收方层层解包的过程。
C/S模式:客户端和服务器端
B/S模式:浏览器和服务器模式
:优缺点:
对于C/S模式,可以保证性能,将数据缓存到客户端本地,提高数据传输效率,所用协议相对灵活。(传统的大型网络程序)
缺点:安全性,工作量。
B/S:浏览器不用开发,开发量小,移植性好,不受平台限制。
协议智能选择http协议,不能缓存数据,效率受影响。
其中有:8位生存时间ttl,(64)下一跳:为了防止网络堵塞。
路由和交换的区别:
路由与交换之间的主要区别是发生在OSI参考模型第二层(数据链路层),而路由发生在第三层,即网络层。这一区别决定了路由交换在移动信息的过程汇总需要使用不同的控制信息。
1.6.1 TCP如何建立连接. 另外:更详细的问题.
1.6.2 [TCP如何通信]
1.6.3 TCP如何关闭连接
1.6.4 什么是滑动窗口
差错控制和流量控制。
TCP中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。
1.6.5 什么是半关闭
由于在全关闭的状态下,当服务器端的数据发送完毕要关闭连接的时候,这时客户端会接收到服务器端的请求,但由于使用全关闭状态,则客户端向服务器端发送的断开连接确认请求将无法再次返回给服务器端。此时就需要用到半关闭状态。为了让数据之间完全交换,也为了让客户端和服务器端之间的数据得到可控,而防止阻塞的情况发生,因此应该用shutdown函数在服务器端发送完数据之后,应该附带一个EOF,让客户端知道服务器端的数据已经发送完成。
为了保证数据的完全交换,应该留出足够长的连接时间,但是应该留出多长的时间呢?
比如客户端连接到服务器,服务器将一个文件传输给客户端,客户端收到后发送确认数据给服务器端
这时服务器端只需要连续的传输文件数据,而客户端却无法知道需要接收数据到何时,客户端也不可能无休止的调用输入函数,因为这有可能导致程序阻塞(调用的函数未返回)
服务器端应该在数据发送完毕后传递EOF表示文件结束,客户端接收到EOF即停止接收数据并向服务器端发送确认数据。close函数与shutdown都可以向客户端发送EOF数据,但使用close发送EOF后也无法接收对方传输的数据了,所以使用shutdown
1.6.6 局域网上的两台主机如何进行通信
通信方法???.
1.6.7 internet上的两台主机如何进行通信
1.6.8 如何在internet上唯一识别一个进程
1.6.9 为什么说TCP是可靠的连接,UDP不可靠
10.路由器和交换机的区别
11.点到点,端到端
一个标注很全的网络服务器编程的例子:python:https://www.cnblogs.com/python58/p/10426188.html.
要让不同机器通信,不仅需要内核IPC,也需要sockt。
两个连接,如下为socket pair,一个文件名包括两个缓冲区,通信是双方的。(发对收),可以半关闭,例如只发不收。
<font color=>站在TCP/UDP协议开发,SOCKET编程,建立文件描述符,对文件描述符进行操作。
socket编程的主要的API函数:
int blind()
int listen()
int accept()
int connect() 客户端主动调用服务器
ssize_t read()
ssize_t write()
ssize_t recv()
ssize_t send()
对应recv和send这两个函数的标志位直接填0就可以了。
注意:如果写缓冲器已满,write也会阻塞,read读操作的时候,若读缓冲区没有数据会引起阻塞。’
2.1 socket编程预备知识
端口号的转换:
大端和小端的概念
大端:也叫高端字节序,也叫网络字节序,是低位地址存放高位数据,高位地址存放低位数据
小端:也叫低端字节序,是低地址存放低位数据,高地址存放高位数据
大端和小端的使用场合???
(1)超过一个字节的,例如int等。
(2)网络传输使用大端法,如果机器使用的是小端,则需要进行大小端的转换。下面4个函数是进行大小端转换的函数:
函数名h表示主机host, n表示网络network, s表示 short, l表示long。
#include<arga/inet> uint32_t htonl(uint32_t hostlong); uint32_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint32_t netshort);
手工计算转换结果:
ip地址转换的函数:
点分十进制ip转换为16进制字节序
int inet_pton(int af,const char *src,vodi *dst) //将点分的十进制IP转换为大端模式的网络IP(整型4字节数) af : AF_INET或 AF_INET6,传入的src最后保存到dst中 例如inrt_pton(AF_INET,"127.0.0.1",&serv.sinaddr.s)
strcut sockaddr 很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。
struct sockaddr { sa_family_t sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ }; //------------------------------------- struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ 地址结构类型 __be16 sin_port; /* Port number */ 端口号 struct in_addr sin_addr; /* Internet address */ IP地址 /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; }; struct in_addr { /* Internet address. */ __be32 s_addr; }; //------------------------------------------------------ struct sockaddr_in6 { unsigned short int sin6_family; /* AF_INET6 */ __be16 sin6_port; /* Transport layer port # */ __be32 sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ __u32 sin6_scope_id; /* scope id (new in RFC2553) */ }; //------------------------------------------------------ struct in6_addr { union { __u8 u6_addr8[16]; __be16 u6_addr16[8]; __be32 u6_addr32[4]; } in6_u; #define s6_addr in6_u.u6_addr8 #define s6_addr16 in6_u.u6_addr16 #define s6_addr32 in6_u.u6_addr32 }; #define UNIX_PATH_MAX 108 struct sockaddr_un { __kernel_sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };
TCP的通信编码的过程。
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int socket(int domain, int type, int protocol); domain: AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址 AF_INET6 与上面类似,不过是来用IPv6的地址 AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用 type: SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。 SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。 SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。 SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议) SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序 protocol: 传0 表示使用默认协议。 返回值: 成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno
socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。对于IPv4,domain参数指定为AF_INET。对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。如果是UDP协议,则type参数指定为SOCK_DGRAM,表示面向数据报的传输协议。protocol参数的介绍从略,指定为0即可.
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockfd: socket文件描述符 addr: 构造出IP地址加端口号 addrlen: sizeof(addr)长度 返回值: 成功返回0,失败返回-1, 设置errno
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind绑定一个固定的网络地址和端口号。
bind()的作用是将参数sockfd和addr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。前面讲过,struct sockaddr *是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。如:
bind()的作用是将参数sockfd和addr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。前面讲过,struct sockaddr *是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。如:
struct sockaddr_in servaddr; bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(6666);
首先将整个结构体清零,然后设置地址类型为AF_INET,网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址,端口号为6666。
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog); sockfd: socket文件描述符 backlog: 排队建立3次握手队列和刚刚建立3次握手队列的链接数
典型的服务器程序可以同时服务于多个客户端,当有客户端发起连接时,服务器调用的accept()返回并接受这个连接,如果有大量的客户端发起连接而服务器来不及处理,尚未accept的客户端就处于连接等待状态,listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接待状态,如果接收到更多的连接请求就忽略。listen()成功返回0,失败返回-1。
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); sockdf: socket文件描述符 addr: 传出参数,返回链接客户端地址信息,含IP地址和端口号 addrlen: 传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小 返回值: 成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno
三方握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。addr是一个传出参数,accept()返回时传出客户端的地址和端口号。addrlen参数是一个传入传出参数(value-result argument),传入的是调用者提供的缓冲区addr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给addr参数传NULL,表示不关心客户端的地址。
我们服务器的程序结构如下:
while (1) { cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); n = read(connfd, buf, MAXLINE); ...... close(connfd); }
整个是一个while死循环,每次循环处理一个客户端连接。由于cliaddr_len是传入传出参数,每次调用accept()之前应该重新赋初值。accept()的参数listenfd是先前的监听文件描述符,而accept()的返回值是另外一个文件描述符connfd(通信文件描述符),之后与客户端之间就通过这个connfd通讯,最后关闭connfd断开连接,而不关闭listenfd,再次回到循环开头listenfd仍然用作accept的参数。accept()成功返回一个文件描述符,出错返回-1。
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockdf: socket文件描述符 addr: 传入参数,指定服务器端地址信息,含IP地址和端口号 addrlen: 传入参数,传入sizeof(addr)大小 返回值: 成功返回0,失败返回-1,设置errno
客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。connect()成功返回0,出错返回-1。
在Linux系统下编译并执行C++程序.
vim设置的指令.
#include<stdio.h> #include<stdlib.h> #include<sys/types.h> #include<string.h> #include<unistd.h> #include<arpa/inet.h> #include<netinet/in.h> #include<sys/socket.h> #include<ctype.h> int main() { //1.创建socket,返回一个文件描述符 lfd----socket int lfd = socket(AF_INET,SOCK_STREAM,0); if(lfd<0) { perror("socket error"); return -1; } //2.将lfd和Ip PORT进行绑定 -----bind() //int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen); struct sockaddr_in serv; //复制方便 bzero(&serv,sizeof(serv)); serv.sin_family = AF_INET; serv.sin_port = htons(8888); //网络字节序转换,不使用1024以前的端口 serv.sin_addr.s_addr = htonl(INADDR_ANY); //表示使用本地任意可用ip inet_pton int ret = bind(lfd,(struct sockaddr *)&serv,sizeof(serv)); if(ret<0) { perror("bind error"); return -1; } //3.监听 // int listen(int socketfd,int backlog); listen(lfd,128); //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //3.接收时不关注客户端的ip地址 //int cfd = accept(lfd,NULL,NULL); //如果不关心,先填NULL //printf("lfd==[%d],cfd==[%d]\n",lfd,cfd); //3。接收时关注ip地址-----------------接收得到的IP地址 struct sockaddr_in client; socklen_t len = sizeof(client); int cfd = accept(lfd,(struct sockaddr *)&client, &len); //打印出端口号和ip地址 char sIP[16]; memset(sIP,0x00,sizeof(sIP)); printf("client-->IP:[%s],PORT:[%d]\n",inet_ntop(AF_INET,&client.sin_addr.s_addr,sIP,sizeof(sIP)),ntohs(client.sin_port)); //----------------------------------------------------- int n = 0; int i = 0; char buf[128]; while(1) { //4.读数据 memset(buf,0x00,sizeof(buf)); n = read(cfd,buf,sizeof(buf)); if(n<=0) { printf("read error or client close"); break; } printf("n==[%d],buf==[%s]\n",n,buf); for(i=0; i<n; i++) { buf[i] = toupper(buf[i]); //大小写转换的功能 } //5.发送数据: write(cfd,buf,n); } //6.关闭监听文件描述符和通信文件描述符 close(lfd); close(cfd); }
//查看连接状态: netstat -anp | grep 8888
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/types.h> #include<unistd.h> #include<netinet/in.h> #include<arpa/inet.h> int main() { //1.创建socket --通信文件描述符 int cfd = socket(AF_INET,SOCK_STREAM, 0); if(cfd<0) { perror("socket error"); return -1; } //2.连接网络端 //int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); struct sockaddr_in serv; serv.sin_family = AF_INET; serv.sin_port = htons(8888); inet_pton(AF_INET,"127.0.0.1",&serv.sin_addr.s_addr); //将ip地址也转化为网络字节序,也要高速对应的通信为iPv4还是ipv6 printf("[%x]",serv.sin_addr.s_addr); int ret = connect(cfd,(struct sockaddr *)&serv,sizeof(serv));//建立连接后可以进行读数据 if(ret<0) { perror("connect error"); return -1; } int n = 0; char buf[256]; while(1) { //3. 发送数据 发送数据的设备一定为网卡,驱动底层发出信息 memset(buf, 0x00, sizeof(buf)); n = read(STDIN_FILENO,buf, sizeof(buf)); //从标准输入读入信息 write(cfd, buf, n); //4.读服务端发来的数据 memset(buf, 0x00,sizeof(buf)); n = read(cfd, buf, sizeof(buf)); if(n<=0) { printf("read error or server closed,n==[%d]",n); break; } printf("n==[%d], buf==[%s]\n",n, buf); } //关闭套接字 close(cfd); return 0; }