网络协议编程 网络基础 协议 一组规则
分层模型结构 OSI七层模型 :物理层 -> 数据链路层 -> 网络层 -> 传输层 -> 会话层 -> 表示层 -> 应用层TCP/IP 网络模型: 网络接口层 -> 网络层 -> 传输层 -> 应用层
网络传输流程 端对端通信: 逐层封装,逐层解封
网络应用程序设计模式 2种常见的模型
C/S
B/S
优点
缓存大量数据、协议选择灵活
安全性好、跨平台、开发工作量小
速度快
缺点
安全性差、不跨平台、开发工作量大
不能缓存大量数据、严格遵循http
socket编程 套接字概念 在TCP/IP协议中,IP地址+TCP或 UDP端口号
标识网络通讯中的一个进程。IP address + PORT
就可以对应一个soket. 欲建立的连接的2个进程各自有一个socket来标识,那么这个两个socket组成的socket pair (IP PORT IP PORT)
就唯一标识 一个连接. 因此可以用Socket来描述网络连接一对一的关系。
tips:
在网络通信中,套接字一定是成对出现的,且具备唯一性 ,其中一端的发送缓冲区对应另一端的接收缓冲区。
一个描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现)
关于socket缓存区 [^1] :
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。 write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由 TCP 协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是 TCP 协议负责的事情。
网络字节序 由于历史遗留问题:intel架构的cpu采用小端存储,而IBM采用大端存储
TCP/IP协议规定: 网络数据流应采用大端字节序
字节序
描述
例子
内存(低->高)
小端
高位存放高地址,低位存放低地址
int a = 0x12345678
0x78563412
大端
低位存在高地址,高位存放低地址
int a = 0x12345678
0x12345678
又大多数PC本地存储都采用小端法,网络存储需要大端,故需要进行大小端转化。 在C语言中,为了解决这个问题使用网络程序具备可移植性,提供了以下库函数实现网络字节序到主机字节序的转换 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #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) ;
IP地址转换函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <arpa/inet.h> int inet_pton (int af, const char *src, void *dst) ; const char *inet_ntop (int af, const void *src, char *dst, socklen_t size) ;
套接字地址结构 socketaddr结构与socketaddr_in的关系
sockaddr地址结构:
1 2 3 4 5 6 #include <arpa/inet.h> struct sockaddr_in { sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr ; };
使用方法
1 2 3 4 5 6 7 8 9 10 11 12 struct sockaddr_in addr ;addr.sin_family = AF_INET/AF_INET6; addr.sin_port = htons(8080 ); int dst;inet_pton(AF_INET, "127.0.0.1" , (void *)&dst); addr.sin_addr = dst; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_addr.s_addr = inet_addr("127.0.0.1" ); bind(fd, (struct sockaddr *)&addr, size);
socket模型创建流程
socket函数 创建一个socket套接字
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <sys/types.h> #include <sys/socket.h> int socket (int domain, int type, int protocol) ;
bind函数 将一个套接字地址结构与现有的socket文件描述符绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <sys/types.h> #include <sys/socket.h> int bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen) ;
listen函数 设置同时与服务器建立连接的上限数,即同时进行3次握手的客户端数量
1 2 3 4 5 6 7 8 9 10 11 12 #include <sys/types.h> #include <sys/socket.h> int listen (int sockfd, int backlog) ;
accept函数 阻塞等待客户端建立连接,成功时返回一个与客户端成功建立连接的socket文件描述符
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <sys/types.h> #include <sys/socket.h> int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen) ;
connect函数 使用现有的socket与服务器建立连接
tips: 如果不使用bind绑定客户端地址结构,将采用“隐式绑定”.
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <sys/types.h> #include <sys/socket.h> int connect (int sockfd, const struct sockaddr *addr, socklen_t addrlen) ;
简单的数据双向传输(C/S) 实现利用socket使通信双方都能收发数据[^2] 服务端
接受请求部分被放在 while 循环中,服务端接受客户端数据,并原样返回.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <arpa/inet.h> #include <errno.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <unistd.h> int main () { int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); struct sockaddr_in serv_addr ; memset (&serv_addr, 0 , sizeof (serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1" ); serv_addr.sin_port = htons(1234 ); int reuse = 1 ; if (setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (int )) == -1 ) { printf ("error!%s" , strerror(errno)); return -1 ; } if (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof (serv_addr)) == -1 ) { printf ("error!%s" , strerror(errno)); return -1 ; } listen(serv_sock, 20 ); struct sockaddr_in clnt_addr ; socklen_t clnt_addr_size = sizeof (clnt_addr); char buffer[BUFSIZ] = {0 }; while (1 ) { int clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size); int strLen = read(clnt_sock, buffer, BUFSIZ); write(clnt_sock, buffer, strLen); close(clnt_sock); memset (buffer, 0 , BUFSIZ); } close(serv_sock); return 0 ; }
客户端
客户端中需要将 socket 创建与连接等操作放在 while 循环内部。因为服务器中调用 close(clnt_sock);不仅会关闭服务器端的 clnt_sock,还会通知客户端连接已断开,客户端也会清理 socket 相关资源,所以每次请求完毕都会清理 socket,下次发起请求时都需要重新创建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <arpa/inet.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <unistd.h> int main () { struct sockaddr_in serv_addr ; memset (&serv_addr, 0 , sizeof (serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1" ); serv_addr.sin_port = htons(1234 ); char bufSend[BUFSIZ] = {0 }; char bufRecv[BUFSIZ] = {0 }; while (1 ) { int sock = socket(AF_INET, SOCK_STREAM, 0 ); if (connect(sock, (struct sockaddr *)&serv_addr, sizeof (serv_addr)) == -1 ) return -1 ; printf ("Input a string: " ); fgets(bufSend, BUFSIZ, stdin ); send(sock, bufSend, strlen (bufSend), 0 ); recv(sock, bufRecv, BUFSIZ, 0 ); printf ("Message form server: %s\n" , bufRecv); memset (bufSend, 0 , BUFSIZ); memset (bufRecv, 0 , BUFSIZ); close(sock); } return 0 ; }
多进程并发服务器 创建步骤
创建监听套接字 lfd
绑定地址结构
监听客户端的请求
处理客户端的请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 while (1 ) { cfd = accept(); pid = fork(); if (pid == 0 ) { close(lfd); read(); 小 -> 大 write(); } else if (pid > 0 ){ close(cfd); continue ; } }
父子进程
父进程:
1 2 3 4 5 close(cfd); 注册信号捕捉函数: SIGCHLD 在回调函数中,完成子进程回收 while (waitpid());
子进程:
1 2 3 4 close(lfd); read(); 小 -> 大 write();
多线程并发服务器 创建步骤
创建监听套接字 lfd
绑定地址结构
监听客户端的请求
处理客户端的请求1 2 3 4 5 while (1 ) { cfd = accept(lfd,); pthread_create(&tid, NULL , tfn, NULL ); pthread_detach(tid); }
子线程1 2 3 4 5 6 7 void *tfn (void *arg) { close(lfd); read(cfd); 小 -> 大 write(cfd); }
RERFERENCE [^1]: socket缓冲区. https://archlinuxstudio.github.io/LinuxNetworkProgrammingAndEncryption/#/network/socket?id=socket-%e7%bc%93%e5%86%b2%e5%8c%ba [^2]: 简单的数据双向传输. https://archlinuxstudio.github.io/LinuxNetworkProgrammingAndEncryption/#/network/socket?id=%e5%8f%8c%e5%90%91%e6%95%b0%e6%8d%ae%e4%bc%a0%e8%be%93