计算机网络基础理论
分层结构:
微信app-->OS-->驱动-->网卡--> 网络 -->网卡-->驱动-->os->微信现今有两种主流的分层方式:
OSI模型: 7层 理论丰满,复杂,但是没人使用 。
TCP/IP模型: 4层 简单,实用,普遍在使用。
TCP/IP模型:
应用层 application
我们自己写的网络应用程序 比如说:邮件 浏览器 淘宝 微信 .....
传输层 TCP: transport control 层
负责传输的安全性的问题. 数据包:丢失 破坏 乱序
如何保证安全问题.后面讲.
端口号: 16bit,short类型. 0--65535 0-----1023端口号,有一些知名的大厂才能用 1024------5000,预留给大厂;5001----65535我们自己用
用于区分同一台设备的内部 app. 每个程序都有一个端口号 port
平时有默认的.
TCP:
面向连接的, 字节流的,安全可靠协议,资源占用大,采用一对一通信,常用于文本,文件,重要信息。
常用tcp的编写流程以及代码案列
下面是一个常用tcp的编写流程以及代码案列:
tcp编程实现服务器与客户端单线接受流程图如下:
![]()
创建服务器过程如下(server)
#include <stdio.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include <stdlib.h> int main(int argc , char **argv) { if(argc != 3) { printf("请输入一个端口号,和一个IP地址\n"); return -1; } if(atoi(argv[1]) > 65535) { printf("请输入一个5001----65535之间的端口号\n"); return -1; } //创建套接字 int sock_fd = socket(AF_INET,SOCK_STREAM,0); // 创建一个套接字,使用IPv4地址族、流式套接字 if(-1 == sock_fd) { perror("创建套接字失败"); return -1; } //绑定IP地址+端口号,结构体 struct sockaddr_in seraddr; seraddr.sin_family = AF_INET; // 地址族 seraddr.sin_port = htons(atoi(argv[1])); // 将端口号由字符串转换为网络字节序(大字节序) seraddr.sin_addr.s_addr = inet_addr(argv[2]); // 将d点分十进制的IP地址转换为无符号的32位网络字节序 socklen_t addrlen = sizeof(seraddr); int ret = bind(sock_fd, (struct sockaddr *)&seraddr, addrlen); // 将套接字绑定到指定的IP地址和端口号 //设置监听 listen(sock_fd, 4); // 设置套接字为监听状态,最大连接数为4 printf("等待连接....\n"); //等待客户端发起连接 int con_fd = accept(sock_fd, NULL, NULL); // 阻塞等待客户端发起连接请求,返回一个新的套接字描述符用于与客户端进行通信 if(-1 == con_fd) { perror("accept失败\n"); return -1; } printf("连接成功!!!!\n"); char buf[128]; while(1) { memset(buf,0,128); recv(con_fd, buf, 128, 0); // 接收来自客户端的数据 printf("[来自客户端]%s\n",buf); if(strcmp(buf,"quit\n") == 0) { break; } } close(sock_fd); // 关闭套接字 close(con_fd); // 关闭与客户端的套接字连接 return 0; }
创建客户端cilent
#include <stdio.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> int main(int argc , char **argv) { //创建套接字 int sock_fd = socket(AF_INET,SOCK_STREAM,0); if(-1 == sock_fd) { perror("creat socket failed"); return -1; } //绑定IP地址+端口号,结构体 struct sockaddr_in seraddr; seraddr.sin_family = AF_INET;//地址族 seraddr.sin_port = htons(atoi(argv[1]));//字符串->网络序 seraddr.sin_addr.s_addr = inet_addr(argv[2]);//点分式IP地址需要转换成无符号的32位网络地址 socklen_t addrlen = sizeof(seraddr); int ret = bind(sock_fd, (struct sockaddr *)&seraddr, addrlen); //发起连接 int con_ret = connect(sock_fd, (struct sockaddr *)&seraddr,addrlen); if(-1 == con_ret) { perror("connect failed"); return -1; } char buf[128];//buf[0] while(1) { memset(buf,0,128); fgets(buf,sizeof(buf),stdin); send(sock_fd, buf, strlen(buf), 0);//发送消息 if(strcmp(buf,"quit\n") == 0) { break; } } close(sock_fd); //close(con_fd); }
关于上述代码所用的函数解析以及声明
1. 创建套接字:
接口声明:int socket(int domain, int type, int protocol);
参数:
domain:域。
AF_INET/PF_INET: 网际协议
AF_UNIX/PF_UNIX:本地协议,可写成 AF_LOCAL/PF_LOCAL
type:类型。
SOCK_STREAM:流式套接字 TCP
SOCK_DGRAM:数据包套接字 UDP
protocol:协议。
一般为 0
返回值:
成功:待连接套接字
失败:-1
备注:在网际协议中,选择流式套接字就代表 TCP 协议,选择数据包套接字就代表 UDP 协议,
第三个参数 protocol 一般都不用。
2、绑定套接字与网络地址
接口声明:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd:待连接套接字
addr:包含本地地址(IP+PORT)的通用地址结构体的指针
addrlen:地址结构体大小
返回值:
成功:0
失败:-1
备注:
通用地址结构体的定义:
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];};
特殊地址结构体 —— IPv4 地址结构体:
struct sockaddr_in
{
u_short sin_family; // 地址族
u_short sin_port; // 端口,0-----1023, 1024-----5000,5000------65535
struct in_addr sin_addr; // IPV4 地址
char sin_zero[8];
};
struct in_addr
{
in_addr_t s_addr; // 无符号 32 位网络地址sockaddr. sin_addr. s_addr = htons(192.168.124.195)
};
特殊地址结构体 —— IPv6 地址结构体:
struct sockaddr_in6
{
u_short sin6_family; // 地址族
__be16 sin6_port; // 端口
__be32 sin6_flowinfo; // 流信息
struct in6_addr sin6_addr; // IPv6 地址
__u32 sin6_scope_id;
};
特殊地址结构体 ——UNIX 域地址结构体:
struct sockaddr_un
{
u_short sun_family; // 地址族
char sun_path[108]; // 套接字文件路径
};
======================================================
1. 将待连接套接字设置为监听套接字,并设置最大同时接收连接请求个数
接口声明:int listen(int sockfd, int backlog);
参数:
sockfd:待连接套接字
backlog:最大同时接收连接请求个数
返回值:成功:0,并将 sockfd 设置为监听套接字
失败:-1
备注:
由于历史原因,各种系统对 backlog 的理解并不一致,以 LINUX 为例,监听端能同时接收的
最大连接请求个数为 0+4
4、等待对端连接请求
接口声明:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd:监听套接字
addr:通用地址结构体,用以存储对端地址(IP+PORT)
addrlen:参数 addr 的存储区域大小
返回值:
成功:已连接套接字(非负整数)
失败:-1
======================================================
5、连接对端监听套接字
接口声明:int connect(int sockfd, const struct sockaddr *addr, socklen_t
addrlen);
参数:
sockfd:待连接套接字
addr:包含对端地址(IP+PORT)的通用地址结构体的指针
addrlen:地址结构体大小
返回值:
成功:0
失败:-1
======================================================
1. 断开本端连接套接字
接口声明:int close(int fd);参数:
fd:已连接套接字
返回值:
成功:0
失败:-1
备注:
同时断开读端和写端
======================================================
1. 断开本端连接套接字
接口声明:int shutdown(int sockfd, int how);
参数:
sockfd:已连接套接字
how:断开方式。
SHUT_RD:关闭读端
SHUT_WR:关闭写端
SHUT_RDWR:同时关闭读写端
返回值:
成功:0
失败:-1
备注:
在只关闭一端的时候,另一端可以继续使用。
======================================================
7.1、将文本地址转化为二进制地址
Char *src=”192.168.124.115”
接口声明:int inet_pton(int af, const char *src, void *dst);
参数:
af:地址族。
AF_INET:IPv4 地址
AF_INET6:IPv6 地址
src:指向“点分式”IPv4 或 IPv6 地址的指针,例如“192.168.1.100”
dst:类型为 struct in_addr *或者 struct in6_addr *的指针返回值:
成功:1
失败:0 代表地址与地址族不匹配,-1 代表地址不合法
7.2、将二进制地址转化为文本地址
接口声明:const char *inet_ntop(int af, const void *src, char *dst, socklen_t
size);
参数:
af:地址族。
AF_INET:IPv4 地址
AF_INET6:IPv6 地址
src:类型为 struct in_addr *或者 struct in6_addr *的指针
dst:地址缓冲区指针,缓冲区至少
size:地址缓冲区大小,至少要 INET_ADDRSTRLEN 或者 INET6_ADDRSTRLEN 个字节
返回值:
成功:dst
失败:NULL
======================================================
8.1、向 TCP 套接字发送数据
接口声明:ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
sockfd:已连接套接字
buf:即将被发送的数据
len:数据长度
flags:发送标志。
MSG_NOSIGNAL:当对端已关闭时,不产生 SIGPIPE 信号
MSG_OOB:发送紧急(带外)数据,只针对 TCP 连接
返回值:
成功:已发送字节数
失败:-1
备注:
当 flags 为 0 时,send 与 write 作用相同。
从 TCP 套接字接收数据
接口声明: ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
sockfd:已连接套接字
buf:存储数据缓冲区
len:缓冲区大小
flags:接收标志
MSG_OOB:接收紧急(带外)数据
返回值:
成功:已接收字节数
失败:-1
备注:
当 flags 为 0 时,recv 与 read 作用相同。阻塞等待