alin的学习之路(Linux网络编程:一)(网络模型、帧格式、socket套接字、服务器端实现)
alin的学习之路(Linux网络编程:一)(网络模型、帧格式、socket套接字、服务器端实现)
1. 协议
协议是一组规则,规定了如何发送数据。通信的双发都需要遵守该规则
2. 网络分层结构模型
1. OSI七层模型
物:物理层
数:数据链路层
网:网络层
传:传输层
会:会话层
表:表示曾
应:应用层
2. TCP/IP模型
链:数据链路层(网络接口层):以太网帧协议,ARP协议
网:网络层:IP协议,ICMP协议,IGMP协议
传:传输层:TCP协议,UDP协议
应:应用层:HTTP,ftp,nfs,telnet,ssh
3. 网络通信过程
数据在没有被封装之前,是不能传输到网络上去进行通信的。
数据产生后,经由数据链路层、网络层、传输层、应用层,四层每一层都会对数据进行不同的封装,封装后才能传送到网络中进行通信。
到达目的端时,经由应用层、传输层、网络层、数据链路层,四层每一层都会解封装,从而最终得到通信数据。
4. 网络协议格式
1. 以太网帧格式
-
以太网规定,数据包必须从一块网卡,传送到另一块网卡。
-
目的地址、源地址 —— mac地址(ifconfig命令可查看)。网卡编号。全球唯一。
-
ARP协议:
根据 IP地址, 获取 mac 地址。ARP 包括请求和应答,请求携带发送端的 mac 地址,通过应答的信息来获取目的端的 mac 地址
2. IP段格式
- 版本:IPv4、IPv6
- TTL: time to live:防止路由包拥塞,表示最大路由跳数 用来设置数据包,在路由节点中的跳转上限。每经过一个路由节点,该值 -1。
- 减为0的路由节点, 有义务将该数据包丢弃。 防止拥塞网络。
3. UDP协议格式
- 源端口号:16位。2^16 — 取值范围:0 ~ 65535
- 目的端口号:16位。2^16 — 取值范围:0 ~ 65535
- IP地址:在 网络环境中, 唯一标识一台主机。
- port:在 网络主机上,唯一标识一个进程。
- IP+port :在 网络环境中, 唯一标识一个 进程。 —— socket 套接字。
4. TCP协议格式
- 源端口号:16位。2^16 — 取值范围:0 ~ 65535
- 目的端口号:16位。2^16 — 取值范围:0 ~ 65535
- 32位序号。
- 32位确认序号。
- 6个标志位。
- 16位窗口大小。---- 取值范围:0 ~ 65535
TCP 优先建立好连接,后续消息全部在该路径上发送,UDP则是每次传输的路由路径不同
5. 网络应用程序设计模式
C/S 模式(client/server):
- 优点:提前缓存数据,提高通信效率。协议选择灵活。开发调试较方便。
- 缺点:对用户安全性构成威胁。开发工作量大。不能跨平台。
- 应用场景:对数据安全、稳定要求较高场合。需要提前缓冲数据。
B/S 模式(browser/server):
- 优点:不需要额外向用户主机安装应用。开发工作量相对小。跨平台使用。
- 缺点:不能缓存大量数据。协议选择不灵活。调试不方便。
- 应用场景:需要跨平台。
6. 网络套接字
在一次通信中,套接字socket必须成对出现
一个文件描述符指向一个套接字 ( 该套接字内部由内核借助两个缓冲区实现。)
1. 网络字节序
小端法:(pc本地存储,IA架构)高位存高地址,低位存低地址。
大端法:(网络存储)高位存低地址,低位存高地址。
需要在做网络传输时,将 “网络字节序” —— “本机字节序” 相互转换。
#include <arpa/inet.h> h表示host,n表示network,l表示32位长整数,s表示16位短整数。 // 本地字节序(IP) ---> 网络字节序(IP) uint32_t htonl(uint32_t hostlong); 参数:要求是一个 int, 但 “192.168.6.108” 是 string。——> atoi() --> int --> htonl() --> 网络字节序。 // 本地字节序(port) ---> 网络字节序(port) uint16_t htons(uint16_t hostshort); // 网络字节序(IP) --->本地字节序(IP) uint32_t ntohl(uint32_t netlong); // 网络字节序(port) --->本地字节序(port) uint16_t ntohs(uint16_t netshort);
2. IP地址转换函数
#include <arpa/inet.h> // 将 本机字节序 string 类型的 IP地址, 转换为 网络字节序 数值类型 int inet_pton(int af, const char *src, void *dst); af:AF_INET(IPv4)、AF_INET6(IPv6) src:传入。IP地址(string类型,点分十进制) dst:传出。转换后的数值类型的 IP地址。 返回值: 1:成功。 0:语法正确, IP无效。 -1:失败。 // 将 网络字节序 数值类型, 转换回程 本机字节序 string。 const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); af:AF_INET(IPv4)、AF_INET6(IPv6) src:传入。网络字节序 IP地址。 dst:传出。string 类型 IP size:dst 的大小。 返回值: 成功:dst 传出值。string 类型的 IP地址。 失败:NULL
3. sockaddr 数据结构
使用 man 7 ip 查看 struct sockaddr_in 结构体类型
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ }; // 举例: struct sockaddr_in addr; // 定义地址结构(IP+port)变量 addr.sin_family = AF_INET; addr.sin_port = htons(8000); // #define INADDR_ANY 0 --- 取系统中任意一个 有效的 IP地址。 addr.sin_addr.s_addr = htonl(INADDR_ANY); // 模拟,使用 bind 绑定地址结构。 bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
7. socket 通信流程图
8. socket 通信函数
1. socket 函数
#include <sys/types.h> 该头文件包含在 <unistd.h> #include <sys/socket.h> // 创建一个新的套接字。 int socket(int domain, int type, int protocol); domain: AF_INET、AF_INET6、AF_UNIX type:SOCK_STREAM、SOCK_DGRAM protocol:通常传 0 返回值: 成功:返回新套接字对应的 fd 失败:-1,errno
2. bind 函数
#include <sys/types.h> 该头文件包含在 <unistd.h> #include <sys/socket.h> // 给 socket 绑定IP+port int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockfd:socket函数的返回值。———— 监听套接字。 addr: 地址结构体。 ———— 类型应该是: struct sockaddr_in ,函数调用时需要强制类型转换 addrlen: sizeof(addr) 地址结构大小 返回值: 成功:0 失败:-1, errno
3. listen 函数
#include <sys/types.h> #include <sys/socket.h> // 设置同时与服务器建立连接的 上限数。———— 该函数不会阻塞程序。 int listen(int sockfd, int backlog); sockfd: socket 函数的返回值 backlog: 设置的监听最大数。 128 返回值: 成功:0 失败:-1, errno
4. accpet 函数
// 阻塞等待客户端连接请求。 成功的话,返回一个与客户端成功建立连接的,用于通信的 socket文件描述符 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); sockfd: socket 函数的返回值 addr: 传出参数。 成功与服务器建立连接的那个客户端地址结构。 addrlen:传入传出参数。 入:addr的大小。出:客户端 addr 的实际大小。 举例:socklen_t clt_addr_len = sizeof(addr); &clt_addr_len 返回值: 成功:能与客户端进行数据通信的 socket 对应的 文件描述符。 失败:-1, errno
5. connect 函数
// 使用现有的 socket 与服务器建立连接 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockfd: socket 函数的返回值 addr:传入参数。 服务器的地址结构。 addrlen: 服务器地址结构的大小。 返回值: 成功:0 失败:-1, errno ———— 客户端自己的地址结构,没有指定,由系统自动分配 --- "隐式绑定"
9. TCP-CS 模型通信
1. server端
通信流程:
- socket函数创建监听套接字
- bind函数绑定IP地址和端口号
- listen函数设置最大监听上限
- accept函数阻塞等待客户端接入服务器,返回值传出客户端的IP地址和端口号
- read函数从accept的返回值文件描述符中读数据
- 数据处理:小写转大写,使用toupper函数
- write函数写回到accept的返回值,即写回到客户端
- close函数关闭文件描述符
编码实现:
#include <stdio.h> #include <sys/types.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <sys/socket.h> #include <unistd.h> #include <ctype.h> #define SRV_PORT 9527 #define SIZE 4096 int main() { int lfd = 0; struct sockaddr_in srv_addr,clt_addr; srv_addr.sin_family = AF_INET; srv_addr.sin_port = htons(SRV_PORT); srv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //1.创建套接字 lfd = socket(AF_INET,SOCK_STREAM,0); if(-1 == lfd) { perror("socket"); return 1; } //绑定IP和port int ret = bind(lfd,(struct sockaddr*)&srv_addr,sizeof(srv_addr)); if(-1 == ret) { perror("bind"); return 1; } //listen 设置最大监听数 ret = listen(lfd,128); if(-1 == ret) { perror("listen"); return 1; } printf("服务器阻塞等待客户端接入\n"); //accept 阻塞等待客户端接入 socklen_t clt_addr_len = sizeof(clt_addr_len); int cfd = accept(lfd,(struct sockaddr*)&clt_addr,&clt_addr_len); if(-1 == cfd) { perror("accept"); return 1; } char ip[SIZE] = {0}; printf("客户端接入:ip:%s,port:%d\n", inet_ntop(AF_INET,&clt_addr.sin_addr.s_addr,ip,SIZE), ntohs(clt_addr.sin_port)); char buf[SIZE] = {0}; while((ret = read(cfd,buf,SIZE)) != 0) { if('\0' != buf[strlen(buf)-1]) buf[strlen(buf)-1] = '\0'; for(int i=0 ;i<ret ;++i) { buf[i] = toupper(buf[i]); } write(cfd,buf,ret); printf("client:%s\n",buf); memset(buf,0,SIZE); } close(cfd); close(lfd); return 0; }
2. client端
通信流程:
- socket函数创建通信套接字文件描述符
- connect函数与服务器建立连接,传入的参数中addr表示服务器的IP地址和端口号
- 从标准输入中读字符串,将该字符串通过write写到通信套接字中
- read函数从服务器读处理后的数据
- 结束后关闭文件描述符
编码实现:
#include <stdio.h> #include <sys/types.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <sys/socket.h> #include <unistd.h> #include <ctype.h> #define SRV_PORT 9527 #define SIZE 128 int main() { int cfd; cfd = socket(AF_INET,SOCK_STREAM,0); if(-1 == cfd) { perror("socket"); return 1; } struct sockaddr_in srv_addr; srv_addr.sin_family = AF_INET; srv_addr.sin_port = htons(SRV_PORT); inet_pton(AF_INET,"127.0.0.1",&srv_addr.sin_addr.s_addr); int ret = connect(cfd,(struct sockaddr*)&srv_addr,sizeof(srv_addr)); if(-1 == ret) { perror("connect"); return 1; } printf("客户端连接成功\n"); char buf[SIZE]; while(1) { fgets(buf,SIZE,stdin); ret = write(cfd,buf,SIZE); ret = read(cfd,buf,SIZE); if(0 == ret) { printf("客户端退出\n"); break; } } close(cfd); return 0; }
- alin的学习之路(Linux网络编程:五)(epoll ET\LT模式、epoll反应堆模型)
- linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时
- alin的学习之路(Linux网络编程:三)(高并发服务器-多线程、TCP通信时序状态、多路IO转接select概述)
- UNIX环境高级编程学习之第十六章网络IPC:套接字 - 非阻塞的Socket通信Select模型(多路复用), 实用Socket通信模板。
- Linux网络编程学习笔记(5)---实现点对点通信(回射客服端/服务器模型)
- alin的学习之路(Linux网络编程:二)(三次握手四次挥手、read函数返回值、错误函数封装、多进程高并发服务器)
- UNIX环境高级编程学习之第十六章网络IPC:套接字 - 非阻塞的Socket通信Poll模型(多路复用), 实用Socket通信模板
- linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时
- alin的学习之路(Linux网络编程:四)(多路IO转接:select、poll、epoll)
- linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时
- alin的学习之路(Linux网络编程:八)(libevent库)
- UNIX环境高级编程学习之第十六章网络IPC:套接字 - 非阻塞的Socket通信EPoll模型(多路复用), 实用Socket通信模板
- linux网络编程socket服务器端实现
- iTOP-4412开发板实现基于linux下网络通信-套接字 TCP 的 socket 编程
- linux socket网络编程:fcntl select(多个客户端连接服务器端情形)
- Linux socket网络编程之聊天室(三):select异步通讯实现
- Linux C语言编程-Linux网络通信--Linux上使用套接字(socket)来发送信息---知识点总结+实例
- 利用线程池实现多客户端和单服务器端Socket通讯(二):异步编程模型实现
- UNIX环境编程--------原始套接字学习笔记-----Linux原始套接字实现分析
- linux 网络编程:使用两线程实现socket同时收发数据