协议:一组规则
如:ftp,tcp/udp,http…
OSI 七层模型:物数网传会表应
TCP/IP 四层模型:网网传应
应用层:http,ftp,nfs,ssh,telnet…
传输层:TCP,UDP
网络层:IP,ICMP,IGMP
链路层:以太网帧协议,ARP
c/s模型(client-server):
优点:缓存大量的数据(比如游戏需要提前加载大量的特效等数据),可以自定义协议灵活速度快
缺点:因为需要安装软件所以安全性会低一些,开发工作量大
b/s模型(browser-server):
优点:安全性高协议公开不能藏私货,跨平台(比如在linux和windows你都可以玩4399但是需要安装的游戏linux很多不支持),开发工作量小
缺点:不能缓存大量数据,严格遵守http协议
数据没有封装之前是不能再网络传输的
数据先封装应用层,然后进行传输层协议,之后再进行网络层协议,最后进行链路层协议。封装号后才能在网络传输。
其中应用层的需要我们自己编程选择需要的协议进行封装,其他三个层是计算机内部来进行封装。
链路层:以太网帧协议
以太网帧中包括:目的地址,源地址,类型,数据,CRC
其中里面的地址是mac地址(是一种网卡的编号且全球唯一),其中源地址是自己的地址是知道的,但是不知道目的地址,所以需要通过ARP请求来获取目的地址。
ARP协议:通过IP地址来获取mac地址。
ARP数据格式:目的以太网地址,源以太网地址,帧类型,8字节相关内容,发送端以太网地址,发送端IP地址,目的以太网地址,目的IP地址。
类型:默认情况下0800,进行ARP请求是0806
网络层:
IP协议:
4位版本号:IPV4,IPV6
8位的生存时间(TTL :Time To Leave):指定数据包可以跳转路由器的次数上限,当TTL减为零路由器有义务将数据包丢弃,防止无限跳转拥塞网络。
源IP:32位—4字节 192.168.1.108----这个叫点分十进制IP地址(string)本质是一个字符串
目的IP:32位–4字节
IP地址可以在网络环境中唯一标识一台主机,但是发到主机中的那个程序是靠Port(端口号,类似进程ID)
ip地址+port:可以唯一标识一个进程,注意端口号最大不能超过65536,一般用5000以上基本不会有问题
传输层:
UDP:
16位源端口号: (最大就是65536)
16位目的端口号:
TCP主要格式:
16位源端口号: (最大就是65536)
16位目的端口号:
32位序号:
32位确认序号:
6个标志位:
16位窗口大小:
网络套接字(socket):在通信过程中套接字一定是成对出现的,一个文件描述符指向一个套接字(该套接字内部由两个缓冲区实现)。
网络字节序:
小端法: 高位存高地址,低位存低地址(计算机存储)
大端法: 高位存低地址,低位存高地址(网络存储)
所以需要进行网络字节序转换
#include<arpa/inet.h>
htonl -->本地转网络(IP)//h表示host表示本地,n表示net
uint32_t htonl(uint32_t hostlong);//转IP,但需要将字符串筒骨atoi函数变为整数。
htons–>本地转网络(Port)
uint32_t htons(uint32_t hostshort);//转端口号
ntohl,ntohs用法一样,网络转本地
IP地址转换函数:
int inet_pton(int af, const char *src, void *dst);
af:AF_INET,AF_INET6(只能取这两个)
src:传本地点分十进制的IP地址
dst:传出,转换后的网络字节序,IP地址
返回值:
成功返回1
异常返回0,说明src指向的不是一个有效的IP地址
失败返回-1
const char*inet_ntop(int af, const void *src, char *dst, socklen_t size);//网络字节序转换为本地字节序
af:AF_INET,AF_INET6(只能取这两个)
src:传网络字节序IP地址
dst:本地字节序
size:dst的大小
返回值:成功返回dst,失败返回NULL
bind();
sockaddr地址结构:
struct sockaddr_in addr;
bind(fd ,(struct sockaddr *)&addr, size);
//服务器
1 #include<stdio.h>
2 #include <sys/types.h> /* See NOTES */
3 #include <sys/socket.h>
4 #include<errno.h>
5 #include<unistd.h>
6 #include<stdlib.h>
7 #include<arpa/inet.h>
8
9 void err_pri(const char* str)
10 {
11 perror(str);
12 exit(1);
13 }
14
15 int main()
16 {
17 int fd = 0, cfd = 0;
18 struct sockaddr_in serv_addr, clin_addr;
19 socklen_t len;
20 char buf[BUFSIZ];//4096
21 int ret, i;
22
23 serv_addr.sin_family = AF_INET;
24 serv_addr.sin_port = htons(9527);
25 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
26
27 fd = socket(AF_INET, SOCK_STREAM, 0);//流式协议的代表就是TCP
28 if(fd == -1)
29 {
30 err_pri("socket error");
31 }
32 bind(fd, (struct sockaddr*)&serv_addr,sizeof(serv_addr));
33
34 listen(fd, 128);
35
36 len = sizeof(len);
37 cfd = accept(fd, (struct sockaddr*)&clin_addr, &len);
38 if(cfd == -1)
39 {
40 err_pri("accept error");
41 }
42 while(1)
43 {
44 ret = read(cfd, buf, sizeof(buf));
45 write(STDOUT_FILENO, buf, ret);
46 for(i = 0; i < ret; i++)
47 {
48 buf[i] = toupper(buf[i]);
49 }
50 write(cfd, buf, ret);
51 }
52
53 close(cfd);
54 close(fd);
55 return 0;
56 }
//客户端
1 #include<stdio.h>
2 #include <sys/types.h> /* See NOTES */
3 #include <sys/socket.h>
4 #include<errno.h>
5 #include<unistd.h>
6 #include<stdlib.h>
7 #include<arpa/inet.h>
8
9 void err_pri(const char* str)
10 {
11 perror(str);
12 exit(1);
13 }
14
15 int main()
16 {
17 int fd,ret;
18 struct sockaddr_in ser_addr;
19 char buf[4096];
20
21 ser_addr.sin_family = AF_INET;
22 ser_addr.sin_port = htons(9527);
23 inet_pton(AF_INET, "127.0.0.1", &ser_addr.sin_addr.s_addr);
24
25 fd = socket(AF_INET, SOCK_STREAM, 0);
26 if(fd == -1)
27 err_pri("socket error");
28 connect(fd, (struct sockaddr*)&ser_addr, sizeof(ser_addr));
29 if(ret != 0)
30 err_pri("connect error");
31
32 while(1)
33 {
34 write(fd, "hello\n", 6);
35 ret = read(fd, buf, sizeof(buf));
36 write(STDOUT_FILENO, buf, ret);
37 sleep(1);
38 }
39 close(fd);
40 return 0;
41 }
三次握手发生在内核
tcp之所以面向连接通信是因为客户端发送一个数据包相应的接收方会来一个回值
四次挥手主要是因为半关闭
对于三次握手,如果客户端发起连接,也就是对应connect函数调用开始,客户端先给服务器发送SYN以及包号且数据量为零,还会发我的滑动窗口有多大,还有mss最大数据携带量会发过去,这个时候服务器会回消息,返回一个ACK客户端的包号加一,并且也会发一个SYN和服务器自己的包号且携带数据量是零,当然也有滑动窗口大小和mss(最大报文长度),最后客户端发回一个服务器包号加一的ACK完成三次握手
对于四次挥手,它的主要原因就是半关闭,因为TCP通信是一个全双工的,所以两条数据路需要分两次关闭,每次关闭的都是相应的套接字管理的数据缓冲区,读缓冲和写缓冲,具体就是FIN和数据包号这个只和最开始发的包号相关以及通信了多少的数据,然后还有ACK之后服务器ACK一个包号加一,然后交换再来一次。
本地套接字与网络套接字的区别
1.在使用socket函数的时候,domain和type的参数有所改变
AF_INET变成AF_UNIX/AF_LOCAL,不过后面的用报式协议和流式协议选哪个都没有所谓的
2.地址结构
声明时变成了struct sockaddr_un
之后对这个结构体进行初始化,domain 有变化,bind函数调用最后一个长度参数需要额外加上一个结构体的偏移位置,也就是两字节,需要用到offsetof函数
3.bind调用成功,会创建一个socket,一般使用unlink将socket删除,
不然再次运行会因为已经存在文件而报错
可以通过man 7 unix 查看
本地套接字客户端写法是需要用到bind函数进行手动绑定的,并且需要初始化两个地址结构,然后使用connect