linux系统有丰富而稳定的网络协议栈,其范围是从协议无关层到各种网络协议的实现
计算机网络通信采用同步和异步两种方式,但传送效率最高的是同步方式。
网络模型
TCP/IP协议族体系结构
在tcp、ip模型中,在系统间提供可靠的数据传输的层次是网络层。
数据链路层
实现了网卡接口的网络驱动程序,以处理 数据在网络媒介上的传输
网络层
实现数据包的选路和转发。
两台主机是通过多个中间节点连接的,网络层就是选择这些节点,来确定两台主机间的通信路径
传输层
为两台主机上的应用程序提供端到端的通信
传输层只关心通信的起始端和目的端,不在乎数据包的中转过程
应用层
处理应用程序的逻辑
ping:应用程序,不是协议,调试网络环境
应用层协议:
telnet:远程登陆协议
DNS:机器域名到ip的转换
HTTP:超文本传输协议
规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议
DHCP:动态主机配置协议
IP协议
为数据输入/输出网络提供基本算法,为高层协议提供无连接的传输服务。(不向发送者或接收者报告包的状态,不处理所遇到的故障)
IPv4 & IPv6
IPv4协议是传统的IP协议,它的IP地址是由一个32 bits的二进制数表示的,常用点分十进制的形式表示出来(例如,172.21. 16. 115等)。但是随着互联网的飞速发展,计算机数量的激增,IP地址紧张的问题逐渐浮出水面。
目前有以下三种解决方案:
1、划分子网( Subneting):
利用子网掩码将IP地址的网络部分右移,也就是在同一网络中划分出几个不同的子网。
这样可以更加经济高效地使用IP地址,减少了主机地址的冗余。
2、保留IP地址( Reserved IP Address):
公共专网地址可在不同的单位重复使用。
缺点是保留的IP地址不能被连到Intemnet上,只能在企业内部使用,企业使用时需要有公网连接手段。
3、 IPv6:
前两种方案只能在某种条件下减缓IP地址耗竭的速度,并不能从根本上解决IP地址的紧张问题,因为它们仍然是基于IPv4协议的,地址空间的长度并没有发生改变。而只有IP6的出现才是IP地址紧张的终极解决方案。行了地址空间的范围,从而支持更多的IP
IPv6是IP协议的第6版,它在IP4的基础上扩大了地址空间范围,从根本上解决了IP地址紧张的问题。
IPv6 做了如下的改进:
1)在IPv6中,P地址空间由过去的32bits扩大到128bits,这样可表示的IP地址理论上增大至2的128次个
2)IPv6支持前缀的地址类型,不同类型的地址有不同的前级,这样地址类型可以更加方便地区分出来。
3) IPv6 支持接口的自动配置。
4) IPv6 改进了对组播的支持。
5) IPv6支持了内置的认证加密机制,提高了安全性。
6) IP6提供了IPv4到IPv6的升级方式,减少了从IPv4到IPv6升级的成本消耗。
IPv6 是IPv4的升级,其最显著的特点是增加了IP 地址的长度,从而在根本上解决了IP地址紧张的问题。
IPv6 更加适应现代复杂多变的互联网环境。
TCP(Transmission Control Protocol传输控制协议)
TCP三次握手四次挥手:
建立TCP连接时,需要服务器和客户端总共发送三个包。
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
TCP连接删除时,需要发送四个包
第一次挥手:Client ,设置Sequence Number和Acknowledgment Number,向 Server发送一个FIN报文段;此时,Client 进入FIN_WAIT_1状态;这表示 Client 没有数据要发送给 Server了。
第二次挥手:Server 收到了 Client 发送的FIN报文段,向 Client 回一个ACK报文段,Acknowledgment Number 为 Sequence Number 加 1;Client 进入 FIN_WAIT_2 状态;Server 告诉 Client ,我“同意”你的关闭请求
第三次挥手:Server 向 Client 发送 FIN 报文段,请求关闭连接,同时 Server 进入 CLOSE_WAIT 状态。
第四次挥手:Client 收到 Server 发送的 FIN 报文段,向 Server 发送 ACK 报文段,然后 Client 进入TIME_WAIT 状态;Server 收到 Client 的 ACK 报文段以后,就关闭连接;此时,Client等待2MSL后依然没有收到回复,则证明 Server 端已正常关闭,那好,Client 也可以关闭连接了。
TCP模型
chat.h
#ifndef CHAT_H
#define CHAT_H
struct ChatInfo
{
int tofd;
char text[1024];
};
typedef struct ChatInfo info;
#endif
服务器:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#nclude <arpa/inet.h>
#include <string. h>
#include <pthread. h>
#include " chat. h"
void *ClientHandler(void *arg)
{
int fd =*(int *)arg , ret ;
printf(" 线程启动, fd = %d\n", fd);
pthread_ detach(pthread_self()); //线程运行完自动释放资源
info buf;
while (1)
{
ret = recv(fd, &buf.,sizeof(buf),0); //从客户端接受数据
if (-1 == ret)
{
perror(" recv" );
exit(1 ) ;
}
if (0 == ret)
{
printf("client %d offline!\n", fd);
break;
}
if (!strcmp(buf.text, "bye"))
{
break;
}
ret = send(buf.tofd, &buf, sizeof(buf), 0);
if (ret == -1)
{
perror("send");
}
memset(&buf, 0, sizeof(buf));
}
close(fd);
}
int main()
{
int ret;
//创建socket(文件)
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
//IPV4地址族 流式套接字(TCP) 具体的协议类型(默认0)
if (-1 == sockfd)
{
perror("socket");
exit(1);
}
int opt = 1;
setsockopt(sockfd,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//设置地址可以被复用(方便调试)
struct sockaddr_in server_addr; //保存服务器本身的信息
struct sockaddr_in client_addr; //保存客户端的信息
memset(&server_addr, 0, sizeof(server_addr)); //清空结构体
server_addr.sin_family = PF_INET;
//地址族 同socket第一个参数
server_addr.sin_port = 7000;
//端口号(大于1024 小于65536) 客户端会向7000端口发起连接
//查man手册,包含头文件 man inet_addr
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
//ip地址 ifconfig获取 127.0.0.1回环ip(用于测试)
//绑定信息
ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (-1 == ret)
{
perror("bind");
exit(1);
}
//设置监听队列
ret = listen(sockfd, 10);
if (-1 == ret)
{
perror("listen");
exit(1);
}
printf("等待客户端连接...\n");
//接受客户端的连接,并且把客户端的信息保存在结构体中
int length = sizeof(struct sockaddr_in);
int fd;
while (1)
{
fd = accept(sockfd, (struct sockaddr *)&client_addr, &length);
//创建TCP连接 收发数据通过fd完成
if (-1 == fd)
{
perror("accept");
exit(1);
}
printf("接受客户端的连接 %d 客户端端口号 %d\n", fd, client_addr.sin_port);
pthread_t pid;
ret = pthread_create(&pid, NULL, ClientHandler, &fd);
if (ret != 0)
{
perror("pthread_create");
exit(1);
}
usleep(1000);
}
close(sockfd); //关闭socket
return 0;
}
客户端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "chat.h"
void *ClientRecv(void *arg)
{
int fd = *(int *)arg;
info buf;
int ret;
while (1)
{
memset(&buf, 0, sizeof(buf));
ret = recv(fd, &buf, sizeof(buf), 0);
if (-1 == ret)
{
perror("recv");
exit(1);
}
printf("\t\t%s\n", buf.text);
}
}
int main()
{
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("socket");
exit(1);
}
//客户端向服务器发起连接
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
//跟服务器保持一致
server_addr.sin_family = PF_INET;
server_addr.sin_port = 7000;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (-1 == ret)
{
perror("connect");
exit(1);
}
pthread_t tid;
ret = pthread_create(&tid, NULL, ClientRecv, &sockfd);
if (ret != 0)
{
perror("pthread_create");
exit(1);
}
info buf;
while (1)
{
scanf("%s%d", buf.text, &buf.tofd);
ret = send(sockfd, &buf, sizeof(buf), 0); //write
if (-1 == ret)
{
perror("send");
exit(1);
}
if (!strcmp(buf.text, "bye"))
{
break;
}
}
close(sockfd); //关闭TCP连接
return 0;
}
UDP(User Datagram Protocol用户数据报协议)
UDP特点:
1、沟通简单,相信网络通路默认就是很容易送达的,不容易被丢弃的
2、不会建立连接,虽然有端口号,但是监听在这个地方,谁都可以传给他数据,也可以传给任何人数据
3、不会根据网络的情况进行发包的拥塞控制,无论网络丢包丢成啥样了,它该怎么发还怎么发
UDP的实际应用:
1、网页或者APP的访问
2、实时游戏
3、移动通信领域
4、流媒体的协议(直播、视频传输)
UDP使用场景:
1、需要资源少,在网络情况比较好的内网,或者对于丢包不敏感的应用
2、不需要一对一沟通,建立连接,而是可以广播的应用
3、需要处理速度快,时延低,可以容忍少数丢包,即便网络堵塞,也毫不退缩,一往无前的时候
UDP模型
服务器
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int sockfd = socket(PF_INET, SOCK_DGRAM, 0); //第二个参数 数据报套接字
if (-1 == sockfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = PF_INET;
server_addr.sin_port = 7000;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (-1 == ret)
{
perror("bind");
exit(1);
}
char buf[32] = {0};
int length = sizeof(client_addr);
//接收文件名
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &length);
//创建并打开文件
int fd = open(buf, O_WRONLY | O_CREAT | O_EXCL, 00700);
while (1)
{
//接收客户端的消息,并且把客户端信息保存在client_addr中
ret = recvfrom(sockfd,buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &length);
if (-1 == ret)
{
perror("recvfrom");
exit(1);
}
if (!strcmp(buf, "bye"))
{
printf("文件接收完毕!\n");
break;
}
write(fd, buf, strlen(buf));
memset(buf, 0, sizeof(buf));
}
close(fd);
close(sockfd);
return 0;
}
客户端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("socket");
exit(1);
}
char buf[32] = {0};
int ret;
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = PF_INET;
server_addr.sin_port = 7000;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
//发送文件名
sendto(sockfd, argv[1], strlen(argv[1]), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
//打开文件
int fd = open(argv[1], O_RDONLY);
while (1)
{
//读文件
ret = read(fd, buf, sizeof(buf) - 1);
if (0 == ret)
{
printf("文件发送完毕!\n");
strcpy(buf, "bye");
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
break;
}
ret = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&server_addr, sizeof(server__addr));
if (-1 == ret)
{
perror("sendto");
exit(1);
}
memset(buf, 0, sizeof(buf));
}
close(fd);
close(sockfd);
return 0;
}
TCP | UDP |
---|---|
面向连接 | 面向无连接 |
面向字节流的,发送的时候发的是一个流,没头没尾的 | 继承了IP的特性,基于数据报的,一个个发,一个个收 |
可以有拥堵控制的,可以根据网络环境调整自己的行为 | 应用让我发,我就发,管它洪水滔天 |
(有状态的服务)可以精确的记着,自己发送了没有,接收到没有,发送到哪个了,应该接收到哪个了,错一点儿都不行 | (无状态服务) |