前言
本文记录OSI7层模型、TCP\IP模型、socket在UDP、TCP使用。
网络
网络:多个计算机之间相互通信
网络协议:多个计算机之间通信用的语言(是有一定规范的)
OSI 7层模型
应用层 表示层 会话层 传输层 网络层 链路层 物理层
面向应用 | 应用层 | 应用程序:FTP、E-mail、Telnet |
面向服务 | 表示层 | 数据格式定义、数据转换/加密 |
会话层 | 建立通信进程的逻辑名字与物理名字之间的联系 | |
面向通信 | 传输层 | 差错处理/恢复,流量控制,提供可靠的数据传输 |
通信子网 | 网络层 | 数据分组、路由选择 |
链路层 | 数据组成可发送、接收的帧 | |
物理层 | 传输物理信号、接口、信号形式、速率 |
TCP/IP协议
四层(IBM公司推出的)
应用层 | http:超文本传输协议 ftp:文件传输协议 smtp:简易邮件传输协议 dhcp:动态ip分配协议 dns:域名解析协议(www.baidu.com->192.168.11.111) |
传输层 | TCP:流式套接字 UDP:数据报套接字,可靠,但是慢 |
网络层 | ip:网络层最重要的协议(IPV4、IPV6) |
网络接口层 | ether(以太网):常用的所有物理介质(网线、WIFI) |
基于TCP协议:http、ftp、smtp 保证数据不能丢失,允许慢一些。
基于UDP协议:dhcp、dns 数据可以丢,但是快
查看ip地址和物理地址的记录
arp -a
物理地址(mac):网卡出厂时,自带编号,不可更改
IP地址:由路由器分配的唯一标识,连接上不同的网络,IP地址也会发生改变
交换机和路由器的区别(重点)
交换机:根据mac地址(物理地址)进行数据的交换,不能分配ip地址,仅限于局域网使用。
路由器:根据IP地址进行数据的交互,能分配ip地址,作用于互联网和局域网。
网络管理相关命令(重点)
(1) 查看ip地址
ifconfig
(2)nc 模拟服务器
TCP通信
启动服务器
nc -l 8888
启动客户端
nc 127.0.0.1 8888 (整数1-65535之间)UDP通信
启动接收端
nc -ul 9999
启动发送端
nc -u 127.0.0.1 9999
Linux下的网络编程
IP地址的存储方式
(1)点分制 192.168.1.111 每个数的取值范围是0-255
(2)整数形式 0xc0a8016f
sizeof("192.168.1.111"); //14个字节
sizeof(0xc0a8016f); //4个字节
区别:点分制可读性强,占用空间大;整数形式可读性差,占用空间小。
inet_addr函数
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
功能: 将点分制(字符串)的IP地址转成整数的IP地址
参数:点分制(字符串)的IP地址
返回值:整数的IP地址
大端序和小端序
大端序(网络字节序):低地址存高字节,高地址存低字节;
小端序:低地址存低字节,高地址存高字节。
验证操作系统采用的是小端序还是大端序
指针方式验证
int main(int argc, char const *argv[])
{int a = 0xc0a8016f;char *p = (char *)&a;printf("%02x %p\n", *p, p);printf("%02x %p\n", *(p+1), p+1);printf("%02x %p\n", *(p+2), p+2);printf("%02x %p\n", *(p+3), p+3);return 0;
}6f 0x7ffcbc0fde7c
01 0x7ffcbc0fde7d
a8 0x7ffcbc0fde7e
c0 0x7ffcbc0fde7f
验证之后我的主机是小端序
共用体方式验证
union addr
{int a;char b[4];
};
int main(int argc, char const *argv[])
{union addr num;num.a = 0xc0a8016f;printf("%x\n",num.b[0]);return 0;
}
网络字节序和主机字节序
h:host 主机 to:转 n:network 网络 l:long 四字节 s:short 二字节
htonl | 主机转网络,转四字节变量 |
htons | 主机转网络,转二字节变量 |
ntohl | 网络转主机,转四字节 |
ntons | 网络转主机,转二字节 |
端口号
区分一台电脑上的应用程序的,也就是网络数据包应该给哪个应用。同一台电脑上,同一个端口号在同一时刻只能被一个应用程序绑定。
一些有固定端口号的应用
ftp 21
http 80
dns 53
dhcp 67
端口号(1-65535),1-1024分配给一些固定的服务
Socket 编程
套接字编程,也叫网络编程,进程间通信中唯一一个可以跨主机的通信
UDP通信
传输速度快,但是不可靠
应用:局域网,对数据的实时性要求较高的互联网
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数1:族协议 通常为AF_INET(ipv4)
参数2:SOCK_DGRAM 使用UDP Socket
SOCK_STREAM 使用TCP Socket
参数3:网络层协议,为0即可
返回值:≥0 Socket的文件描述符(open的返回值类似),<0 出错
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{//int socket(int domain, int type, int protocol);int sock_fd = socket(AF_INET,SOCK_DGRAM,0);printf("%d\n",sock_fd);close(sock_fd);return 0;
}
接收端
bind函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
recvfrom函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{// 1.创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);// 2.绑定自己的IP地址和端口号struct sockaddr_in myaddr;myaddr.sin_family = AF_INET;myaddr.sin_port = htons(8888);myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");int ret = bind(sockfd, (struct sockaddr *)&myaddr, sizeof(struct sockaddr));// 3.接收数据char buf[128];struct sockaddr_in your_addr;int len = sizeof(your_addr);recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&your_addr, &len);printf("buf is %s\n", buf);// 4.关闭close(sockfd);return 0;
}
发送端
sendto函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{// 1.创建Socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);// 2.指定对方的ip地址和端口号struct sockaddr_in your_addr;your_addr.sin_family = AF_INET;your_addr.sin_port = htons(8888);your_addr.sin_addr.s_addr = inet_addr("127.0.0.1");// 3.发送数据char buf[128] = "hello";sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&your_addr, sizeof(your_addr));// 4.关闭close(sockfd);return 0;
}
TCP通信
可靠,但是慢
完成可靠的方法:
(1)发送之前,先建立连接;
(2)发送时,有应答机制,如果中间数据丢失,会重新发送。
TCP通信流程
服务器端
socket函数(略)
bind函数(略)
listen函数
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
设置最大监听数(设置最大同时连接数):指正在连接的客户端,已经建立连接的客户端(在接收数据的客户端)不算在内。
参数1:socket的返回值
参数2:设置的最大监听数
accept函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
阻塞等待客户端的连接
参数1:socket的返回值;
参数2:连接的客户端的ip地址和端口号的结构体的地址(一般为NULL,代表不关心,和recvfrom的最后两个参数一样);
参数3:结构体长度的地址(一般为NULL)。
返回值:newfd代表这个链接成功客户端的连接通道的编号;后续与这个客户端进行通信时,使用newfd。
send函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
发送数据,如果无人发数据,则阻塞等待数据的发送
参数1:accept函数的返回值
参数2:发送数据的首地址
参数3:缓存区长度
参数4:0
返回值:大于0 实际接收数据的长度;≤0 连接断开
实例代码
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{// 1.创建socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);// 2.绑定IP地址和端口号struct sockaddr_in myaddr;myaddr.sin_family = AF_INET;myaddr.sin_port = htons(8888);myaddr.sin_addr.s_addr = htonl(INADDR_ANY);int ret = bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr));// 3.设置最大监听数listen(sockfd, 5);// 4.阻塞等待客户端的连接int newfd = accept(sockfd, NULL, NULL);// 5.接收char buf[128] = {0};ret = recv(newfd, buf, sizeof(buf), 0);printf("ret %d ,buf is %s\n", ret, buf);// 6.关闭close(newfd);close(sockfd);return 0;
}
客户端
1.创建socket
2.指定对方的ip地址和端口号
struct sockaddr_in client_addr;
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(atoi(argv[2]));
client_addr.sin_addr.s_addr = inet_addr(argv[1]);
3.连接客户端
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数1:socket返回值
参数2:结构体地址
参数3:结构体长度
返回值:0 成功,-1 失败
4.收发数据
客户端只能连接一个服务器,所以没有newfd,所以recv/send的第一个参数用socket返回值
5.关闭close
实例代码
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[])
{// 1.创建socket套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);// 2.指定对方的ip地址和端口号struct sockaddr_in your_addr;your_addr.sin_family = AF_INET;your_addr.sin_port = htons(8888);your_addr.sin_addr.s_addr = inet_addr("127.0.0.1");// 3.连接int ret = connect(sockfd, (struct sockaddr *)&your_addr, sizeof(your_addr));// 4.收发数据char buf[128] = {0};scanf("%s", buf);send(sockfd, buf, sizeof(buf), 0);// 5.关闭close(sockfd);return 0;
}
循环服务器
缺点:
(1)newfd会被覆盖
(2)accept和recv 会互相阻塞
A客户端连接成功后,accept解除阻塞,代码运行到recv,等待A客户端发送的数据;若A一直不发送数据,会recv处一直阻塞,无法运行到accept,其它客户端无法进行连接。