主页 > 手机  > 

【网络编程】网络编程基础:TCP/UDP协议

【网络编程】网络编程基础:TCP/UDP协议
一、什么是网络?

网络是信息传输,接收和共享的虚拟世界,通过把网络上的信息汇聚在一起,将这些资源进行共享。

初衷:知识共享。这里不得不提到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: 000 B: 100 C: 110 D: 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.0 192.168.30.100/24 前缀长度:24 3>网关 用来管理当前网段下的信息传输、网络的门户,默认取值为1,192.168.30.1,随机取值(1 - 254) 4>DNS域名解析器 DNS的作用类似于电话簿,将域名和IP地址相对应,使得用户可以通过域名来访问网站, 不要记忆复杂的IP地址 .baidu - 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/UDP TCP:微信视频电话 --- 保证对方接了之后才能通信 -- 文件传输、聊天 UDP:微信发消息 --- 只管发,不管对方有无接收 -- 视屏传输 网络层: IP:主机的唯一标识 ICMP:网络控制消息协议,用于ping命令的实现 IGMP:网络组管理协议,用于广播、组播的实现 **/ /** 网络接口和物理层: 网卡:让不同的计算机之间连接,从而实现数据的通信等功能(有线网卡和无线网卡) mac(物理地址):网卡的标识号(48位),类似于身份证号,理论上全球唯一 物理地址: 00:0c:29:14:84:69 前三组称为厂商ID,后三组为设备ID ARP 将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 ~ 65535 1 ~ 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协议s MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议 /********************************************************************** TCP的建立连接和断开连接 标识符: ACK:确认标识符 FIN:断开标识符 SYN:请求标识符 -- 请求服务器连接 URG:紧急标识符 PSH:推标识 TCP的三次握手: (如何打通电话) 1.客户端给服务器发送SYN连接请求 2.服务器回复ACK,并给客户端发送连接请求 3.客户端回复ACK TCP的四次挥手: (如何挂断电话) 1.客户端给服务器发送FIN请求 2.服务器回复ACK 3.服务器给客户端发送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/* -rn vim /usr/src/linux-headers-5.4.0-150-generic/include/linux/socket.h +31 ================================================================================= ctags查询struct sockaddr{}的具体实现 1》安装ctags sudo apt-get install ctags 2》去到头文件所在的目录 cd /usr/src/linux-headers-5.4.0-150-generic/include/ 3》生产ctags标签 sudo ctags -R 4》查询 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_xxx char 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 bytes u_short sin_port; // 端口,2 bytes struct 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 1024 int 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 1024 int 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() 断开连接

综上。希望该内容能对你有帮助,感谢!

以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。

我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。 感谢!

标签:

【网络编程】网络编程基础:TCP/UDP协议由讯客互联手机栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【网络编程】网络编程基础:TCP/UDP协议