网络基础
协议的概念
什么是协议
协议就是规则,是数据传输和数组的解释的规则
两个之间传输信息必须要进过三次
第一次,传输文件名,接收方接收到文件名,应答OK给传输方;(知道要传什么)
第二次,发送文件的尺寸,接收方接收到该数据再次应答一个OK;(接受到了)
第三次,传输文件内容。同样,接收方接收数据完成后应答OK表示文件内容接收成功。(接受到了)
网络应用程序设计模式
C/S模式
传统的网络应用设计模式,客户机(client)/服务器(server)模式。需要在通讯两端各种部署客户机和服务器来完成数据通信
B/S模式
浏览器()/服务器(server)模式。只需在一端部署服务器,而另外一端使用各台pc都默认配置的浏览器即可完成数据的传输
分层模型
数据打包传输
要传输一个数据给另一台电脑,将我们的数据开始封装,传达到另一台电脑就会一层一层解除封装,显示我我们原本的信息
支持我们设定的网络协议就会使用什么特定的api读取,不然无法读取
数据开始通过网卡传输,然后通过一系列设备才到网络环境中
通信过程
封装好的数据为什么可以传递到我们想要的地方
路由寻址(利用ip和以太网帧)
一个路由器叫做一个路由节点,里面存放了一个表格,表格里面是这个路由可以到达的下一个路由的ip地址,我们最初的数据里面存放了目的地的ip地址,就这样一点点接近我们的目的地ip
TCP协议稳定,一次传输成功就会记住这个路线
以太网帧格式
目的地址
数据包下一个前往的网卡的硬件地址(网卡编号)
源地址
自己的硬件地址
根据类型这个几个数据来控制是传请求还是传数据
APR请求下一个进往的路由器的硬件地址
以太寻路
每一次进过路由器都要解包,更新数据打包
TTL;数据包传输的最长生命周期
ip段格式(对应以太网帧的数据)
传输层协议
1、UDP
每一个应用都有一个端口号(进程),这样消息就可以在我们指定的qq上传输
TCP数据报格式
NAT映射
数据先发送到交换机,然后交换机发送到路由器
NAT映射表
将私有ip转换成公网ip
一个路由器只有一个公网ip,但是我们一个路由器可以连接多个电脑,这些电脑的ip就是私有的,192.168这就是私有ip,要在网络中传输要转换成公网ip
打洞
在qq上传输信息,要经过腾讯的服务器
路由器会屏蔽陌生ip,但是我们两个都登入了腾讯的服务器,这样腾讯服务器对双方而言就不是陌生ip了
腾讯服务器给他们两个打洞,高效连接
socket套接字(插座)
成对出现,在内核中一个文件描述符对应两个缓冲区
ip地址:网络中辨识一个主机
端口号:在电脑中辨识一个进程
Ip + 端口 = 网络环境中唯一辨识一个进程(socket)
网络字节序
大端
地地址存高位
小端
低地址存低位
一般本机采用小端存储,网络数据流采用大端字节序,为此我们调用以下库函数做网络字节序和主机字节序的转换
直接的在点分十进制与网络字节序直接转换
socket
sockaddr j结构体用来描述IPV4协议
Socket 简单来说是ip地址与端口的结合协议
1、一种地址与端口的结合描述协议
2、Tcp/IP协议的相关API的总称:网络API的集合实现
3、涵盖了stream socket/Datagram Socket
socket 的组成与作用:
在网络传输中用于唯一标识两个端点的链接。
端点:包括(ip+port)
4个要素:客户端的地址、客户端的端口、服务器的地址、服务器端口。
Socke的传输原理
socket函数
创建一个套接字的结构体
bind函数
让socket结构体可以访问到我们本机的IP地址
将socket连接到我们主机的ip地址,这些主机所有的网卡都可使用socket
listen函数
规定可链接数量,与服务器要链接的客户端是多少个;
accept函数
开启链接口,等待客户端的链接;
这个会返回一个客户端的socket
connect函数
输入服务端的IP地址及服务端口,如果连接出错,返回socket.error错误;
项目各部分说明
TCP服务端
1、调用socket() 创建出一个套接子结构体
2、调用bind函数,使得socket结构体可以访问到本机ip和端口号
3、调用listen函数,设置同时可以与本机连接的客户端数量
4、调用accept函数,要是用户没有发起连接就是堵塞,要是发起了连接机会返回一个与客户端产生连接的套接字结构体
5、调用read函数读取
6、调用write函数将小写转换成大写,然后回写到accept那个结构体中
TCP客户端
1、调用socket创建一个套接字结构体
2、没有调用bind函数,会自动分配一个ip和端口号
3、调用connect,连接带服务器ip和端口号
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<ctype.h>
#include<arpa/inet.h>
#define SERV_IP "127.0.0.1"//本机ip
#define SERV_PORT 6666 //端口号
int main(void)
{
int lfd;// 文件描述符
int cfd;//与客户的产生关系的socket套接字
struct sockaddr_in serv_addr;//创建一个绑定的端口和具体地址,服务端
struct sockaddr_in clie_addr;//创建一个绑定的端口和具体地址,客户端
socklen_t clie_addr_len;//描述长度
char buf[BUFSIZ];//BUF是表示空间的大小
int n;//表示实际读到的字符
//创建一个socket
lfd = socket(AF_INET,SOCK_STREAM,0);//地址类型,套接字类型,协议类型为自动
// 要是成功就会返回可以用的socket变量失败就返回错误代码
serv_addr.sin_family = AF_INET;//初始化地址协议类型
serv_addr.sin_addr.s_addr =htonl(INADDR_ANY);//初始化IP地址
serv_addr.sin_port = htons(SERV_PORT);//初始化端口号
//给socket绑定端口号与地址
bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));//socket函数返回的描述符、指定想要绑定的IP和端口、serv_addr的长度
listen(lfd,128);//用于表述一个已捆绑未连接套接口的描述字、等待连接队列的最大长度
//成功返回0,失败返回-1
clie_addr_len = sizeof(clie_addr);//下面的长度
// 在服务器端上创建一个新的socket,将客户端的信息和新的socket绑定在一个,一次只能创建一个
cfd =accept(lfd,(struct sockaddr *)&clie_addr,&clie_addr_len);//服务器的socket、客户端端口地址信息结构体、返回参数2的数据类型大小
// 将读取到的小写转成大写
while(1)
{
n = read(cfd,buf,sizeof(buf));//接收端的socket、客户端消息的存储空间、存储空间的大小、数据读取方式
for ( int i = 0; i < n; i++)
{
buf[i] = toupper(buf[i]);
}
//写给客户端
write(cfd,buf,n);//从buf中写n个给客户端
} //关闭
close(lfd);
close(cfd);
return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#define SERV_IP "127.0.0.1"//本机ip
#define SERV_PORT 6666 //端口号
int main()
{
int cfd;// 文件描述符
int n ;
char buf[BUFSIZ];
cfd = socket(AF_INET,SOCK_STREAM,0);//地址类型,套接字类型,协议类型为自动
// 要是成功就会返回可以用的socket变量失败就返回错误代码
struct sockaddr_in serv_addr;//创建一个结构体,绑定的端口和具体地址,服务端
serv_addr.sin_family = AF_INET;//初始化地址协议类型
serv_addr.sin_addr.s_addr =htonl(INADDR_ANY);//初始化IP地址
serv_addr.sin_port = htons(SERV_PORT);//初始化端口号
socklen_t serv_addr_len;//定义一个长度
connect(cfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)); //标识一个未连接的socket,存放连接成功的socket对象、指向要连接套接字的sockaddr结构体的指针、sockaddr结构体的字节长度
while(1)
{
fgets(buf,sizeof(buf),stdin);//从键盘获取数据
write(cfd,buf,strlen(buf));//服务端去写
n = read(cfd,buf,sizeof(buf));//读到n个数据
write(STDOUT_FILENO,buf,n);//写到屏幕上
}
//关闭
close(cfd);
return 0;
}
错误处理函数
上面的函数都会在被调用之后,返回一个值,我们可以根据这个返回值来判断函数的运行情况。为了简单程序
我们在另一个warp.c文件中对每一个返回值都创建一个专门的函数去观察,我们只需要在观察函数中调用我们的socket函数,就可以得到返回值,然后判断是否错误