一、什么是网络?
网络是信息传输,接收和共享的虚拟世界,通过把网络上的信息汇聚在一起,将这些资源进行共享。
初衷:知识共享。这里不得不提到Internet 的历史-它其实是“冷战”的产物:
- 1957年10月和11月,前苏联先后有两颗“Sputnik”卫星上天。
- 1958年美国总统艾森豪威尔向美国国会提出建立DARPA (Defense Advanced Research Project Agency),即国防部高级研究计划署,简称ARPA。
- 1968年6月DARPA提出“资源共享计算机网络” (Resource Sharing Computer Networks),目的在于让DARPA的所有电脑互连起来,这个网络就叫做ARPAnet,即“阿帕网”,是 Interne 的最早雏形。
二、计算机中的软件层面,网络是由什么组成的?
1>IP//定义IP地址是一种因特网上的主机编址方式,也称为网际协议地址IP,是任意一台主机在网络中的唯一标识。//格式点分十进制:用户编写,给人看的 192.168.30.100网络二进制:给系统看的 0 1//分类IPv4:(主要集中在电脑)点分十进制:4个字节网络二进制:32个位(42亿个IP地址)由网络地址(子网ID)和主机地址(主机ID)构成IPv6:(主要用在手机WiFi,目前电脑上用得不多)点分十进制:16个字节网络二进制:128个位IPv4地址分类:A/B/C/D/E分别应用于大型网、中型网、中小型网、组播型、待用型//A类地址:政府机关或者学校等大型网络#规则:以0开头,8位的网络地址(子网ID),24位的主机地址(主机ID)网络二进制:0000 0000 0000 0000 0000 0000 0000 0001 - 0111 1111 1111 1111 1111 1111 1111 1110点分十进制: 0.0.0.1 - 127.255.255.254//B类地址:中等规模的企业使用#规则:以10开头,16位的网络地址(子网ID),16位的主机地址(主机ID)网络二进制:1000 0000 0000 0000 0000 0000 0000 0001 - 1011 1111 1111 1111 1111 1111 1111 1110点分十进制: 128.0.0.1 - 191.255.255.254//C类地址:任意个人使用 *#规则:以110开头,24位的网络地址(子网ID),8位的主机地址(主机ID)网络二进制:1100 0000 0000 0000 0000 0000 0000 0001 - 1101 1111 1111 1111 1111 1111 1111 1110点分十进制: 192.0.0.1 - 223.255.255.254//D类地址(组播地址):4个字节都是网络地址 *#规则:以1110开头,32位的网络地址(子网ID),0位的主机地址(主机ID)网络二进制:1110 0000 0000 0000 0000 0000 0000 0001 - 1110 1111 1111 1111 1111 1111 1111 1110点分十进制: 224.0.0.1 - 239.255.255.254//E类地址:未使用的地址 -- 测试地址#规则:以11110开头,留着待用前三位:判断类别A: 000B: 100C: 110D: 111注意:有两个地址不能使用/**主机地址(主机ID)全为0,它是网段号、网络号主机地址(主机ID)全为1,它是广播地址**/端口号:标识计算机上的进程取值范围: 0~2^16 -1//特点网络地址不同的网络不能直接通信,如果要通信须通过路由器进行转发2>子网掩码//定义子网掩码又叫网络掩码、地址掩码,是一个32位由1和0组成的数值,并且1和0都是连续//作用指明IP地址中哪些位表示为子网ID,哪些位表示为主机ID//特点必须结合IP地址一起使用,不能单独存在IP地址中由子网掩码中1覆盖的连续位为子网ID,其余为主机ID//以C类地址为例192.168.30.100/255.255.255.0192.168.30.100/24前缀长度:243>网关用来管理当前网段下的信息传输、网络的门户,默认取值为1,192.168.30.1,随机取值(1 - 254)4>DNS域名解析器DNS的作用类似于电话簿,将域名和IP地址相对应,使得用户可以通过域名来访问网站,不要记忆复杂的IP地址www.baidu.com - 183.2.172.42
三、网络体系结构
网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起。每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,同时使用下层提供的服务。网络体系结构即指 网络的层次结构和每层所使用协议的集合。
两类非常重要的体系结构:OSI 与 TCP/IP
OSI开放系统互联模型
OSI模型相关的协议已经很少使用,但模型本身非常通用;OSI模型是一个理想化的模型,尚未有完整的实现,OSI模型共有七层:
1>OSI七层模型应用层 应用程序:FTP、E-mail、Telnet----------------------表示层 数据格式定义、数据转换/加密----------------------会话层 建立通信进程的逻辑名字与物理名字之间的联系 ----------------------传输层 差错处理/恢复,流量控制,提供可靠的数据传输----------------------网络层 数据分组、路由选择----------------------链路层 数据组成可发送、接收的帧----------------------物理层 传输物理信号、接口、信号形式、速率目的:将数据封装,形成一个约定好的通信协议协议:双方约定好的通信规则缺点:太复杂,太过理想化,有些层的功能重复
双方通信需要保证协议一致
2>TCP/IP协议族的体系结构重点学习的体系结构,网络协议中的世界语、标准语应用层 应用程序:FTP、E-mail、Telnet -- http(超文本传输协议) DNS POP3(邮件接收协议) STMP(邮件发送协议)----------------------传输层 TCP/UDP -- 确定数据包交给主机上的那个进程----------------------网络层 IP/ICMP/IGMP----------------------网络接口和物理层 网卡驱动和物理接口/**传输层:TCP/UDPTCP:微信视频电话 --- 保证对方接了之后才能通信 -- 文件传输、聊天UDP:微信发消息 --- 只管发,不管对方有无接收 -- 视屏传输网络层:IP:主机的唯一标识ICMP:网络控制消息协议,用于ping命令的实现IGMP:网络组管理协议,用于广播、组播的实现 **//**网络接口和物理层:网卡:让不同的计算机之间连接,从而实现数据的通信等功能(有线网卡和无线网卡)mac(物理地址):网卡的标识号(48位),类似于身份证号,理论上全球唯一物理地址: 00:0c:29:14:84:69 前三组称为厂商ID,后三组为设备IDARP 将IP地址转换为MAC地址 RARP 将MAC地址转换为IP地址 **/3>数据的封装与传递过程见 数据的封装与传递过程(如下图所示)封装的目的:保证数据稳定可靠地传输
发送端 数据打包
接收端 数据解包
数据传输过程:
层层封装 ---> 层层解封["疯狂星期四Vme50"] ---> 应用层[TCP/UDP 头]["疯狂星期四Vme50"] ---> 传输层[IP 头][TCP/UDP 头]["疯狂星期四Vme50"] ---> 网络层[ARP 头][IP 头][TCP/UDP 头]["疯狂星期四Vme50"] ---> 物理接口层|将打包好的数据发送出去数据在网络中都是以帧的形式发送 [ARP 头][IP 头][TCP/UDP 头]["疯狂星期四Vme50"] -- 1帧数据[IP 头][TCP/UDP 头]["疯狂星期四Vme50"] -- 最大 1500个 字节
TCP/IP 协议下的数据包:
4>端口号类似于PID表示一个进程,区分不同的网络应用程序例如:QQ 4399 微信 17173无符号短整型 unsigned short 2个字节 0 ~ 655351 ~ 1023 系统进程使用1024 ~ 5000 系统分配的端口5001 ~ 65535 用户自己分配使用的问:为什么有进程PID了,还要搞一个端口号来区分不同的应用程序?答:根据进程运行被分配的进程号不固定,但想以固定的序号去访问指定的应用程序,便有了端口号5>大小端序不同类型CPU主机中,内存存储的整数字节序分为以下两种:小端序(little-endian)低位字节存储在低位地址 linux,x86平台大端序(big-endian)低位字节存储在高位地址 网络字节序//用于大小端序转换的工具函数#include <arpa/inet.h>//主机字节序转换为网络字节序(小端序转换为大端序)uint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostshort);//网络字节序转换为主机字节序(大端序转换为小端序)uint32_t ntohl(uint32_t netlong);uint16_t ntohs(uint16_t netshort);6>网络通信模型:C/S : 服务器 + 客户端B/S : 网页端 http https
四、TCP/UDP的特点和区别
都采用经典的C/S模型,即客户端(client)服务器(server)模型。
不同点:TCP:有连接,可靠;UDP:无连接,不保证可靠。
1》TCP: 面向链接的可靠协议 -- 1对1私聊//定义是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)借助于C/S模型搭建TCP通信:服务器(while(1)) 客户端1>建立socket连接 -- 买手机 1> 建立socket连接 -- 买手机2>绑定IP和端口号 -- 办电话卡 2> 连接服务器 3>监听 -- 开机 3> 收/发 4>等待连接 -- 等别人打电话 5>收/发 //功能提供不同主机上的进程通信//特点1.建立连接->使用连接->释放连接2.TCP数据包中包含序号和确认序号3.对包进行排序并排错,而损坏的包可以被重传//适用情况:适合于对传输质量要求较高,以及传输大量数据的通信。在需要可靠数据传输的场合,通常使用TCP协议sMSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议/**********************************************************************TCP的建立连接和断开连接标识符:ACK:确认标识符FIN:断开标识符SYN:请求标识符 -- 请求服务器连接URG:紧急标识符PSH:推标识TCP的三次握手: (如何打通电话)1.客户端给服务器发送SYN连接请求2.服务器回复ACK,并给客户端发送连接请求3.客户端回复ACK TCP的四次挥手: (如何挂断电话)1.客户端给服务器发送FIN请求2.服务器回复ACK3.服务器给客户端发送FIN请求4.客户端回复请求ACK **********************************************************************/
2》UDP//定义是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。//功能提供不同主机上的进程通信//特点1.发送数据之前不需要建立连接2.不对数据包的顺序进行检查3.没有错误检测和重传机制//适用情况发送小尺寸数据(如对DNS服务器进行IP地址查询时)在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)适合于广播/组播式通信中。MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输
TCP传输
五、基于TCP协议的网络通信模型
1》框架
/**框架**/服务器(server) 客户端(client)1.创建流式套接字:socket();|
2.绑定本地地址:bind();|
3.设置监听套接字:listen(); 1.创建流式套接字:socket();| |
4.等待客户端连接:accept(); 2.发送连接请求:connect();| |
5.开始和客户端通信:read()/recv(); 3.与服务器通信:write()/send();| |
6.断开连接:close(); 4.发送断开请求:close();
2》TCP编程相关的API函数1>socket创建套接字#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int socket(int domain, int type, int protocol);/**********************************************************************@brief: 创建一个特殊的文件描述符(具有网络属性)@domain: 地址族AF_UNIX, AF_LOCAL 用于本地进程间通信,域通信AF_INET IPv4 Internet protocols AF_INET6 IPv6 Internet protocols @type: SOCK_STREAM // 流式套接字SOCK_DGRAM // 数据报套接字SOCK_RAW // 原始套接字@protocol: 一般默认设置为0,表示前面两个参数有效@retval: 成功:返回具有网络属性的文件描述符失败:返回-1,并且设置全局错误码**********************************************************************/2>connect主动连接服务器(绑定IP和端口号)bind()#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);/**********************************************************************@brief: 主动连接服务器@sockfd: 客户端的唯一文件描述符@addr: 声明服务器的结构体指针@addrlen: 结构体的长度@retval: 成功:返回0失败:返回-1,并且设置全局错误码**********************************************************************//**********************************************************************************grep查询struct sockaddr{}的具体实现grep struct\ sockaddr\ { /usr/src/linux-headers-5.4.0-150-generic/include/* -rnvim /usr/src/linux-headers-5.4.0-150-generic/include/linux/socket.h +31=================================================================================ctags查询struct sockaddr{}的具体实现1》安装ctagssudo apt-get install ctags2》去到头文件所在的目录cd /usr/src/linux-headers-5.4.0-150-generic/include/3》生产ctags标签sudo ctags -R4》查询vim -t 类(例如:sockaddr)=================================================================================locate 文件名 用于查询该文件所在路径**********************************************************************/#通用地址结构struct sockaddr {sa_family_t sa_family; /* address family, AF_xxx */ //地址族 --> ip地址char sa_data[14]; /* 14 bytes of protocol address */ //IP + 端口号}; struct sockaddr{ u_short sa_family; // 地址族, AF_xxxchar sa_data[14]; // 14字节协议地址};// 该结构体不适用: 1>IP + Prot = 6 个字节,sa_data有14个字节,多的字节没法填充// 2>无法判断IP在前面还是Prot在前面/*问题:1.IP地址和端口号谁在前谁在后?2.IP地址和端口号总共占6个字节,多出来的8个字节怎么处理?*/#Internet协议地址结构struct sockaddr_in {__kernel_sa_family_t sin_family; /* Address family */__be16 sin_port; /* Port number */struct in_addr sin_addr; /* Internet address *//* Pad to size of `struct sockaddr'. */unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -sizeof(unsigned short int) - sizeof(struct in_addr)];}; struct sockaddr_in { u_short sin_family; // 地址族, AF_INET,2 bytesu_short sin_port; // 端口,2 bytesstruct in_addr sin_addr; // IPV4地址,4 bytes char sin_zero[8]; // 8 bytes unused,作为填充}; // internet address struct in_addr{in_addr_t s_addr; // u32 network address };/**点分十进制和网络二进制的相互转换函数**/ in_addr_t inet_addr(const char *cp);/**********************************************************************@brief: 点分十进制转换为网络二进制@cp: 字符串点分十进制@retval: 成功:返回网络二进制数失败:返回-1,并且设置全局错误码**********************************************************************/char *inet_ntoa(struct in_addr in);/**********************************************************************@brief: 网络二进制转换为点分十进制@cp: 网络二进制的结构体变量@retval: 成功:返回字符串点分十进制失败:返回-1,并且设置全局错误码**********************************************************************/3>send发送#include <sys/types.h>#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);/**********************************************************************@brief: 发送数据@sockfd: 套接字文件描述符@buf: 发送数据的容器@len: buf的长度(用strlen)@flags: 一般为0@retval: 成功:返回实际发送的字节数失败:返回-1,并且设置全局错误码**********************************************************************/4>close关闭#include <unistd.h>int close(int fd);5>recv接收#include <sys/types.h>#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);/**********************************************************************@brief: 接收数据@sockfd: 套接字文件描述符,注意如果是服务器应使用accept对应的套接字@buf: 接收数据的容器@len: buf的长度(用sizeof)@flags: 一般为0@retval: 成功:返回实际接收的字节数0:异常退出失败:返回-1,并且设置全局错误码**********************************************************************/6>bind绑定 #include <sys/types.h> /* See NOTES */#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);/**********************************************************************@brief: 将IP地址和端口号与套接字绑定在一起@sockfd: 套接字文件描述符@addr: 服务器的地址信息的结构体指针@addrlen: 结构体的长度@retval: 成功:返回0失败:返回-1,并且设置全局错误码**********************************************************************/7>listen监听 #include <sys/types.h> /* See NOTES */#include <sys/socket.h>int listen(int sockfd, int backlog);/**********************************************************************@brief: 保护服务器,限制同一时间客户端最大的连接数量@sockfd: 套接字文件描述符@backlog: 同一时间最大连接数量@retval: 成功:返回实际发送的字节数失败:返回-1,并且设置全局错误码**********************************************************************/8>accept等待客户端的连接#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);/**********************************************************************@brief: 等待客户端连接(阻塞)@sockfd: 套接字文件描述符@addr: 客户端的结构体指针@addrlen: 结构体的长度的指针@retval: 成功:返回与客户端进行连接通信的文件描述符失败:返回-1,并且设置全局错误码**********************************************************************/
基于 TCP协议 的服务器-客户端通信模型的 C 语言代码示例
该代码实现了基本的网络通信框架,其中服务器监听客户端连接,接受连接后进行数据收发,客户端与服务器建立连接并进行数据交换。
TCP 服务器端代码(server.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 8080 // 服务器监听端口
#define BUFFER_SIZE 1024int main() {int server_fd, new_socket;struct sockaddr_in address;int addrlen = sizeof(address);char buffer[BUFFER_SIZE] = {0};// 1. 创建流式套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("Socket failed");exit(EXIT_FAILURE);}// 2. 绑定本地地址address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("Bind failed");close(server_fd);exit(EXIT_FAILURE);}// 3. 设置监听套接字if (listen(server_fd, 3) < 0) {perror("Listen failed");close(server_fd);exit(EXIT_FAILURE);}printf("Server listening on port %d...\n", PORT);// 4. 等待客户端连接if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("Accept failed");close(server_fd);exit(EXIT_FAILURE);}printf("Client connected!\n");// 5. 开始和客户端通信while (1) {memset(buffer, 0, BUFFER_SIZE);int bytes_read = read(new_socket, buffer, BUFFER_SIZE);if (bytes_read <= 0) {printf("Client disconnected.\n");break;}printf("Received: %s\n", buffer);// 发送回复char *message = "Message received.";send(new_socket, message, strlen(message), 0);}// 6. 断开连接close(new_socket);close(server_fd);return 0;
}
TCP 客户端代码(client.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>#define SERVER_IP "127.0.0.1" // 服务器地址
#define PORT 8080 // 服务器端口
#define BUFFER_SIZE 1024int main() {int sock;struct sockaddr_in serv_addr;char buffer[BUFFER_SIZE] = {0};// 1. 创建流式套接字if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("Socket creation error");exit(EXIT_FAILURE);}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// 2. 设置服务器地址if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {perror("Invalid address / Address not supported");close(sock);exit(EXIT_FAILURE);}// 3. 发送连接请求if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("Connection failed");close(sock);exit(EXIT_FAILURE);}printf("Connected to server.\n");// 4. 与服务器通信while (1) {printf("Enter message: ");fgets(buffer, BUFFER_SIZE, stdin);buffer[strcspn(buffer, "\n")] = 0; // 去掉换行符send(sock, buffer, strlen(buffer), 0);memset(buffer, 0, BUFFER_SIZE);int bytes_read = read(sock, buffer, BUFFER_SIZE);if (bytes_read <= 0) {printf("Server disconnected.\n");break;}printf("Server: %s\n", buffer);}// 5. 发送断开请求close(sock);return 0;
}
运行步骤
- 编译代码
gcc server.c -o server
gcc client.c -o client
- 启动服务器
./server
- 启动客户端
./client
- 在客户端发送消息
客户端输入消息后,服务器会接收到并回复 Message received.
该代码实现了基于 TCP 协议的网络通信模型:
服务器:socket() 创建套接字bind() 绑定 IP 和端口listen() 监听连接accept() 接受客户端连接read()/send() 进行数据通信close() 断开连接
客户端:socket() 创建套接字connect() 连接服务器send()/read() 进行数据通信close() 断开连接
综上。希望该内容能对你有帮助,感谢!
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!