文章目录
网络编程学习(1)
- 程序间通信的协议是什么?
- 通信什么是否发起?由谁发起?有谁响应?
QA:
- 网络通信整体框架是什么?
- 开放系统互联模型OSI ? 如何理解?
- 发现某些网络细节的两个基本命令:netstat 和ifconfig //存储在 或/usr/bin目录、
整体框架:
1. 同一网络通讯模型:
- 一端从上而下,另外一端从下而上
2. 不同网路通讯模型:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oyeT7OVl-1644211103388)(C:\Users\xinge.hu\Documents\网络编程\不同网络间的通信框架.bmp)]
3. OSI 模型及网际层:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1EYq74X5-1644211103389)(C:\Users\xinge.hu\Documents\网络编程\OSI 模型及网际层.bmp)]
- 套接字位于应用层和传输层间,因为应用层通常为应用程序,而下面四层为内核处理。
常用协议介绍:
协议整体框架:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JnLZZU16-1644211103390)(C:\Users\xinge.hu\Documents\网络编程\TCP_IP 协议_NO4.bmp)]
- BPF : 分组过滤器
- ICMP: 网际控制管理协议,主要处理路由与主机之间的控制或者错误。
- IGMP: 网际组管理协议,该协议用于多播。
TCP 协议介绍:
TCP 是面向连接的可靠的的传输协议。
有包发送确认机制:发送方发1数据包后,对端会回ACK确认;
有超时重传机制: 通过RTT算法获取发包后收到确认的大致时间,超时就会重发数据包。
有分节系列号标识:一段数据可能会被分隔成多个数据包,接收方通过序列号标识重新组合,对于误重传的包会丢弃。
有流量控制机制:会告知1次所能接受的最大数据量,称为通话窗口。 通话窗口动态变化,往缓冲区写数据,通话窗口会变小,从缓冲区读数据,通话窗口会变大。
TCP 三次握手:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yxFDdn2T-1644211103390)(C:\Users\xinge.hu\Documents\网络编程\TCP 三次握手_N5.bmp)]
SYN 可以包含以下信息:
- MSS: 最大分节大小, 通常设置为MTU 减去IP首部和TCP 首部固定大小。 MTU 是链路层硬件决定的最大传输单元。两个主机之间路径中最小的MTU称为路径MTU. 如1500字节的MTU 是当今常见的路径MTU.
- 窗口规模选项: 对端所能接收的最大窗口大小
- 时间戳选项:放置失而复现的分组破坏数据包
注: ACK 确认是SYN 序列号+1
TCP 4次挥手
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NgNUQM7U-1644211103390)(C:\Users\xinge.hu\Documents\网络编程\TCP 四次挥手_N6.bmp)]
四次挥手有时候可能三次就完了, 但通常一端收到另一端的FIN 后,等待一会才会close. 两端都可以主动close.
TCP 分节及TIME_WAIT 状态理解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2MjQzKeK-1644211103391)(C:\Users\xinge.hu\Documents\网络编程\TCP 分组_TIME_WAIT_n8.bmp)]
TIME_WAIT 状态:
- 作用1,第一实现全双工,如果服务器没有收到最后的ACK ,会重发ACK,此时客户端要维持好状态可以确保发送ACK。
- 作用2,允许老分节在网络中消失。 比如套接字对断开后又重新连接相同套接字端,之前迷失的分节可能又被发送,因此需要TIME_WAIT 确保所有分节在网络中消失。 时间为2MSL.(1min-4min).
TCP 输出理解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-snOrG3ls-1644211103391)(C:\Users\xinge.hu\Documents\网络编程\TCP 输出n9.bmp)]
- 套接字发送缓冲区:TCP 有重传机制,再未收到ACK前,发送缓冲区会保存发送的数据
- MSS: 最大分节大小, 在握手时通过SYN 携带该信息。 表示重组缓存大小即一个最大分节大小。为避免分节,通常设置为MTU-TCP 首部-IP 首部。
- MTU: 链路层决定的最大传输单元。主机与主机之间相反方向的MTU 可能不同,因此会是不同链路。
- 路径MTU: 主机与主机之间最小的MTU 称为路径MTU.
TCP 编程常用函数:
1. socket 函数:
#include <sys/socket.h>
int socket(int family, int type, int protocol)
- 调用socket 创建套接字。
- family:地址族,常用如下:
Family | 说明 |
---|---|
AF_INET | IPv4 协议族 |
AF_INET6 | IPv6协议族 |
AF_LOCAL | Unix 域协议 |
AF_ROUTE | 路由套接字 |
AF_KEY | 秘钥套接字 |
AF_XXX 是地址族,RF_XXX 是协议族,通常两者相等。原本预想一个协议族对应多个地址族,但目前实际上一个协议族对应一个地址族。
- type 是协议类型,对于TCP 而言仅仅支持字节流套接字。
type | 说明 |
---|---|
SOCK_STREAM | 字节流套接字 |
SOCK_DGRAM | 数据包套接字 |
SOCK_SEQPACKET | 有序分组套接字 |
SOCK_RAW | 原始套接字 |
- protocol 协议值:
protocol | 说明 |
---|---|
IPPROTO_CP | TCP传输协议 |
IPPROTO_UDP | UDP 传输协议 |
IPPROTO_SCTP | SCTP 传输协议 |
- 组合结果:
AF_INET | AF_INET6 | AF_LOCOL | AF_ROUTE | AF_KEY | |
---|---|---|---|---|---|
SOCK_STREAM | TCP/SCTP | TCP/SCTP | Y | N | N |
SOCK_DGRAM | UDP | UDP | Y | N | N |
SOCK_SEQPACKET | SCTP | SCTP | Y | N | N |
SOCK_RAW | ipv4 | ipv6 | N | Y | Y |
2. connect 函数:
#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
-
成功返回0, 错误返回-1
-
第一个参数是套接字fd, 第二个参数指向套接字地址结构的指针;第三个参数是地址长度。
-
connect 函数由客户端发起,该函数调用前不必非得调用bind函数,因为内核会确定源IP地址,并选择临时端口作为源端口。
-
如果是TCP套接字,则connect函数触发TCP三次握手,且在连接成功或错误时返回。 常见错误如下:
- ETIMEDOUT 错误: 没有收到SYN 分节响应,重发也无收到响应时返回该错误。
- ECONNREFUSED错误:服务器主机在指定的端口上没有进程在等待与之连接,会发送RST 分节,客户端收到该分节就会立马返回该错误。 (该错误为硬错误)
- EHOSTUNREACH/ENETUNREACH 错误:客户发出的SYN在中间的某个路由器上引发“destination unreachable”的ICMP错误,重复发送SYN 规定的75s内未收到响应,则将消息保存作为此错误返回给进程。
-
connect 失败之后,必须关闭套接字重现建立套接字才可以再次调用connect。
3. bind 函数:
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
- 返回值:成功返回0, 失败返回-1;
- 作用:把一个协议地址绑定到一个套接字上,可以同时指定IP地址+端口,也可以指定一个,也可以都不指定
- 参数:第一个参数套接字fd,第二个参数指定地址结构的指针,第三个参数地址结构的长度
- 必要性:不是必须要指定端口或IP地址。如果一个TCP客户端/服务端未曾绑定一个端口,则调用connect/listen 时,内核为相应的套接字选择一个临时端口。
- 进程可以把一个特定的IP地址帮当到它的套接字,且该IP地址必须属于所述主机的网络接口之一。如果客户端绑定IP地址,则该套接字上发送的IP数据指定源IP地址; 如果服务端未绑定IP地址,则连接时,目标地址就是对应的IP地址。
4. listen 函数:
#include <sys/socket.h>
int listen(int sockfd, int backlog)
-
返回值:成功是返回0.出错时返回-1.
-
作用:将套接字转换为被动套接字,指示内核应该受指向该套接字的连接请求。
-
参数:第一个参数为套接字fd, 第二个参数为监听套接字维护两个队列的和的最大值。(往往是该值*1.5)
- 未完成连接队列:服务器正等待完成TCP三路握手的队列
- 已完成队列:每个完成TCP三路握手对应的一项,从未完成对列赋值到已完成对列
- 当进程调用accept时,已完成的连接队列的对头项将返回给进程。如果队列为空,则进程将进入睡眠。
-
注意:如果不要客户连接时,直接关闭该套接字,而不要将backlog设置为0
-
该函数由TCP服务器端调用,通常在socket,bind 之后,accept 之前。
5. accept 函数:
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen *addrlen)
- 成功返回非负描述符,出错则返回-1
- 第一个参数为监听套接字fd, 第二个参数为客户进程的协议地址,第三个参数为客户进程协议地址的长度(值-结果,结果为内核实际写进地址结构的长度)
- 返回值:新分配套接字,表示已连接的套接字。
- 注意:监听套接字在服务器的整个有效期内都保持开放,而连接套接字在连接断开后停止。
6.close函数:
#include <unistd.h>
int close(int sockfd);
- 默认行为将套接字标记为已关闭,然后立即返回调用进程,且该套接字不能再被调用进程使用,即不能read/write.
- 默认TCP尝试发送已排队等到发送到对端的任何数据,发送完毕后进入正常的TCP连接终止序列。
- 当close 调用,引用数值为0时,才引发TCP 4次挥手的终止程序。(比如fork 多个子进程,同一个sockfd 有多个拷贝即多个引用,子进程中close(sockfd)并不会触发TCP4次挥手终止程序。)
7.getsockname 和getpeername 函数:
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr,socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
-
获取sockfd 关联的本地协议地址和外地协议地址,
-
什么时候需要用到本地协议地址呢?
-
- 当客户端未bind 本地IP地址和端口,直接connect 后,由内核分配IP地址和端口号,此时就可以通过该接口获取内核分配的IP地址和端口信息。
- 当客户端使用bind, 但是端口号设置为0(告诉内核去选择本地端口号),可以使用该接口获取信息
-
-
什么时候用到获取外地协议地址呢?
-
- 当fork 子进程后,调用exec 用新的程序覆盖赋值的子进程环境时,可以使用该接口,获取连接该套接字的对端IP地址和端口(客户端的IP地址和端口)
-
8. select 函数:
函数说明:
- 调用select 告知内核对哪些描述符(就读, 写, 异常条件)感兴趣以及等待多长时间。
- slect 函数是I/O 复用的典型例子。
函数格式:
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdpl, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
函数参数说明:
-
maxfdpl: fdset 中最大描述符+1; 因为描述符是从0 开始。系统最大支持描述符FD_SETSIZE 定义,如果修改该值,则需要重新编译内核。
-
readset:读描述符集
-
writeset: 写描述符集合
-
exceptset: 异常描述符集合
-
描述符集使用以下接口:
void FD_ZERO(fd_set *fdset); //清空所有fd 的fdset的所有bit
void FD_SET(int fd, fd_set *fdset); //如第一个fd 0-31bit , fd2 32-63bit
void FD_CLR(int fd, fd_set *fdset); // 清空某个fd 的fdset.
int FD_ISSET(int fd, fd_set *fdset); //fdset 的哪个bit被设置,说明已就绪。 返回>0 表示fd 已就绪。
-
-
timeval *timeout:select 函数等待时间;
-
结构:
struct timeval{
long tv_sec; //单位;s
long tv_usec; //单位:us
};
-
分类:
- 永远等待下去:将参数*timeout设置为NULL
- 等待一段时间: 设定时间值
- 不等待:上述tv_sec 和tv_usec 设置为0
-
举例说明:
//使用select 哪个I/O (file fd和sockfd)可读操作哪个
Test_Func(FILE *fp, int sockfd)
{
fd_set rset;
FD_ZERO(&rset);
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdpl = max(fileno(fp),sockfd)+1;
slelect(maxfdpl, &rset, NULL, NULL ,NULL); //可写,异常,timeout 设置为null
if(FD_ISSET(sockfd, &rset))
{
//sockfd 可读
......
}
if(FD_ISSET(fileno(fp), &rset))
{
//fd 可读
......
}
}
描述符就绪条件:**
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SKSHKxLV-1644211103392)(C:\Users\xinge.hu\Documents\网络编程\n17_slect 描述符就绪条件.bmp)]
- 可读条件:对端TCP 发送数据,该套接字变为可读。
- 对端TCP 发送FIN, 该套接字变为可读。
- 对端TCP 发送一个RST(对端主机崩溃并重新启动),该套接字可读。
9. shutdown函数:
函数格式:
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
函数参数说明:
- sockfd: 操作的sockfd 套接字描述符
- howto 参数决定shutdown 具体作用,主要参数如下:
- SHUT_RD: 关闭连接的读这一半,套接字中不再有数据可接受,且套接字接受缓冲区现有数据被丢弃,进程不能再对这样的套接字调用任何读函数。 注:调用shutdown 函数后,该套接字接收的来自对端的任何数据都被确认,然后悄然丢弃。
- SHUT_WR: 关闭连接的写这一半;称为半关闭。发送缓冲区的数据将发送掉,后跟TCP正常连接终止,进程不能再对这样的套接字调用任何写函数。
- SHUT_RDWR: 相当于两次调用shutdown,读写都关闭。
shutdown 函数和close 函数比较:
- close 把描述符引用计数减1, 当计数变为0时,关闭套接字。而shutdown 直接激发TCP 正常终止序列。
- close 终止读写两个方向的数据传输,但有时我们需要告诉对端我们完成了数据发送,但对端仍有数据要发送给我们,此时我们就可以使用shutdown 函数。
10. pselect 函数:
#incldue <sys/select.h>
#include<signal.h>
#include<time.h>
int pselect (int maxfdpl. fd_set *readset, fd_set *writeset, fd_set *exceptset,const struct timespec *timeout, const sigset_t *sogmask);
11. poll 函数:
#include <poll.h>
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
- 第一个参数指向pollfd 结构数组第一个元素的指针, nfds 为pollfd 数组中fd个数,timout 为等待时间。
- timeout : INFTIM: 永远等待; 0 立即返回,不阻塞进程; >0 等待指定数目的毫秒数。
- pollfd 结构:
struct pollfd{
int fd;
short events; //表示要关注的事件
short revents; //根据该值确定哪个事件到达
};
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-daqsTV7y-1644211103392)(C:\Users\xinge.hu\Documents\网络编程\poll_event.bmp)]
- 返回值小于0表示poll出错,返回值==0表示timeout但没有捕获到事件;返回值>0表示捕获到事件。
#include <iostream>
int main()
{
int going=1;
int retValue;
int32 fd1, fd2;
fd1=intsocket(); //use slefdefine intsocket to get socket fd
fd2=intsocketGeneric(); //use selfdefine intsocketGeneric to get socket fd
struct pollfd fds[2];
fds[0].fd=fd1;
fds[0].events=POLLIN;
fds[0].revents=0;
fds[1].fd=fd2;
fds[1].events=POLLING;
fds[1].revents=0;
while(going==1){
retValue=poll(fds,2,1000);
if(retValue<0){
cout<<"error\n";
break;
}else if(retValue==0)
{
continue;
}else
{
if(fds[0].revents==POLLIN)
{
//从fds[0].fd 读取数据
}
if(fds[1].revents==POLLIN)
{
// 从fds[1].fd 读取数据
}
}
}
return 0;
}
12. getsockopt 和setsockopt 函数:
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen );
int setsockopt(int sockfd, int level, int iptname, const void *optval, socklen_t optlen);
// 参考网络编程page164
设置和获取套接字属性;
通用套接字选项:
- SO_BROADCAST :仅数据报套接字支持广播,该选项开启或禁止进程发送广播消息的能力
- SO_DEBUG:给TCP 套接字开启该选项,内核将为TCP 在该套接字发送和接收所有分组保留详细跟踪信息。
- SO_DONTROUTE:外出分组将绕过底层协议的正常路由机制。路由守护进程经常使用本选项绕过路由表以强制将分组从特定接口送出。
13 . fcntl 介绍:
设置sockfd 套接字类型,常用设置为O_NONBLOCK 非阻塞方式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3qUGXMvz-1644211103392)(C:\Users\xinge.hu\AppData\Roaming\Typora\typora-user-images\1634544925250.png)]
#include<fcntl.h>
int fcntl (int fd, int cmd,…/* int arg */)
比如设置sockfd 为非阻塞型:fcntl(sockfd, F_SETTL, O_NONBLOCK);
UDP 协议介绍:
UDP 是数据报协议,不面向连接。应用程序发送数据,然后通过UDP 封包传给IP层,再进行IP封包到目的地。 无论是否传输成功,发送端不进行重传和检查,因此会有丢包的情况。
UDP 输出理解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQ39JDbd-1644211103393)(C:\Users\xinge.hu\Documents\网络编程\n10_UDP 输出.bmp)]
- 套接字发送缓冲区:因为UDP 不可靠不需要确认ACK, 这个只是说将数据写进socket, 但是实际并无发送缓冲区
UDP 整体套接字编程整体框架:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XzgPgmwP-1644211103393)(C:\Users\xinge.hu\Documents\网络编程\udp_客户服务器套接字函数框架.bmp)]
- 无需连接,
UDP 编程常用函数:
1. recvfrom 和sendto 函数:
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void*buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
- recvfrom 的from 和addrlen 是发送者的地址信息
- *buf 和nbytes 是接受的数据的最大值和接收数据存储的buffer.
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t *addrlen);
- sento 的sockaddr 和addrlen 表示要写的目标地址及地址信息的长度。
- *buf 和nbytes 是发送数据的buffer 及发送字节数。
2. UDP 服务器获取相关地址
recvfrom : 获取源IP地址
recvfrom: 获取源端口号
recvmsg: 获取目标IP地址
getsockname: 获取目标端口
3. UDP 的connect 函数:
UDP 也可以使用connect ,表示已连接的UDP 套接字,已连接的UDP 套接字有以下特点:
- 不能指定目的IP和端口,只能与connect 的sockfd 通信。
- 不使用sendto 和recvfrom ,而使用read 和write/
- 异步错误会返回给他们的进程。
套接字介绍:
套接字对与并发服务器:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wM7GWTaP-1644211103394)(C:\Users\xinge.hu\AppData\Roaming\Typora\typora-user-images\1632733007314.png)]
- 通常一个套接字绑定一个IP+一个端口。 而套接字对是一组套接字(本地IP+本地端口;对端IP+对端端口)。
- 服务器是多宿的(即包含多个IP地址),上述1通过bind 绑定本地IP地址和端口时,将IP设置为任意时,此时创建的套接字就是监听套接字。
- 当客户端connect 连接时,服务器会fork()出新的子进程进行管理,此时accept()返回的套接字即为已连接套接字。
- 上述分节信息中IP 信息是2 的就会使用右端fork的第一个子进程的已连接套接字。并发服务器中fork 新的子进程后,套接字描述符会进行一次新的赋值(引用计数会加1),此时父进程关闭连接套接字继续监听套接字,而子进程关闭监听套接字,使用连接套接字进行处理连接上的客户。
基础概念了解:
1. 基本套接字地址结构:
IPV4 地址结构:
#include <netinet/in.h>
struct in_addr{
in_addr_t s_addr; //ipv4 地址,一般为uint32_t
};
struct sockaddr_in{
uint8_t sin_len; //地址总长度
sa_family_t sin_family; //地址族 为AF_INET通常为uint8_t *posix 标准要求*
in_port_t sin_port; //端口号 一般为uint16_t *posix 标准要求*
struct in_addr sin_addr; //二进制的IP地址,一般为uint32_t *posix 标准要求*
char sin_zero[8];//可选
};
IPV6 地址结构:
struct in6_addr{
uint8_t s6_addr[16]; //128bit IPV6 地址
};
struct sockaddr_in6{
uint8_t sin6_len; //总地址结构长度
sa_family_t sin6_family; //协议族为AF_INET6
in_port_t sin6_port; //端口号
uint32_t sin6_flowinfo; //几乎不用
struct in6_addr sin6_addr; //ipv6 地址
uint32_t sin6_scope_id; //标识地址的范围,如链路局部地址
};
通用套接字地址:
#include <sys/socket.h>
struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family[14];
char sa_data[14];
};
- 不同的协议地址族可以定义自己的地址结构,为避免协议相关性,定义了通用套接字结构。
- sa_family_t 表示所用的协议族
2. 值和结果参数的理解:
- 以指针形式作为参数,表示地址结构大小; 内核处理之后将结果信息写入该值进行返回。因此传入的仅仅是值而是指针结构。
3. 主机字节序和网络字节序的理解:
-
数据存储分为大端模式和小端模式;比如0x0102 在内存中低地址存储0x01, 高地址存储为0x02 即为大端模式,否则为小端模式。
-
主机字节序:不同的CPU 有不同的字节序类型,我们把某个系统给定的字节序称为主机字节序。
-
网络字节序:网络字节序采用big endian 大端排序。
-
主机字节序与网络字节序转换的函数如下:
uint16_t htons(uint16_t host16bitvalue); 主机字节序转换为网络字节序 uint32_t htonl(uint32_t host32bitvalue); 主机字节序转换为网络字节序 uint16_t ntohs(uint16_t net16bitvalue); 网络字节序转换为主机字节序 uint32_t ntohl(uint32_t net32bitvalue); 网络字节序转换为主机字节序
4. 字节常用操作函数:
字节操作函数有两类,一类起源4.2BSD, 一类来自ANSI C标准:
#include<string.h> | |
---|---|
void bzero(void *dest, size_t nbytes); | 将dest 指向地址数据设置为0 |
void bcopy(const void *src, void *dest, size_t nbytes ); | |
int bcmp(const void *ptrl, cosnt void *ptr2, size_t nbytes); | 相等为0, 否则非0 |
void *memset(void *dest, int c ,size_t len); | |
void *memcpy(void *dest, const void *src, size_t nbytes); | |
int memcmp(const void *ptrl, const void *ptrs, size_t nbytes); |
5. 地址处理函数:
记录在sockaddr 中的地址结构是网络字节序的二进制数,但人们偏爱的格式是点分十进制数串,因此需要转换函数,在这两种之间进行转换。
-
仅仅适用于IPV4 类型地址转换:
#include <arpa/inet.h> int inet_aton(const char *strptr,struct in_addr *addrptr) 将strptr 中的点分十进制数串转换成网络字节序二进制存储在addrptr 中。 char* inet_ntoa(struct in_addr inaddr); 将一个32位网络字节序二进制IPV4地址转换成相应的点分十进制数串。 -
随IPV6产生,但同样适用IPV4 和IPV6 两种:
#include <arpa/inet.h> | |
---|---|
int inet_pton(int family, const char *strptr,void *addrptr) | family 为地址族如AF_INET. 将点分十进制字串转换为二进制地址 |
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len); | 将二进制地址转换为点分十进制字串 |
比如: inet_pton(AF_INET, cp, &foo.sin_addr);
inet_ntop(AF_INET, &foo.sin_addr, str, sizeof(str));
6. 字节流套接字上的read/write/readline 函数:
- read函数实际读取的数据可能会比想要读取的数据小。 因为读取数据的buffer 中可能不足,需要用户重新调用read函数。
- write 函数在非阻塞时,受缓冲区剩余空间限制,可能实际写入的数据没有预期的多,因此也需要用户重新调用write函数。
- 但是可以改写read 和write 函数,重复调用read/write 以读取/写入预期的数据。
7. I/O 复用
1). I/O 复用概念:
-
说明:比如在客户端和服务器TCP连接实例中,客户端既要通过fgets()获取输入子串,又要关注sockfd 套接字,当服务器端终止发出FIN时,客户端因阻塞在fgets() 而无法看到EOF信息,因此需要使用I/O 复用。进程需要预先告知内核的能力,使内核一旦发现进程指定的一个或多个I/O 条件就绪时,就通知进程,这个能力称为I/O复用。
-
I/O 复用使用场合:
- 客户端处理多个描述符时。
- 一个客户同事处理多个套接字时(少见)
- 一个TCP服务器既要处理监听套接字,又要处理已连接套接字时
- 一个服务器既要处理TCP又要处理UDP时
- 一个服务器要处理多个服务或多个协议时,一般要使用I/O 复用。
2). I/O 模型:
-
阻塞式 I/O模型:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j78BymO0-1644211103394)(C:\Users\xinge.hu\Documents\网络编程\n11_IO阻塞.bmp)]
-
非阻塞式I/O 模型:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QatYFnwN-1644211103400)(C:\Users\xinge.hu\Documents\网络编程\n12_非阻塞IO复用.bmp)]
-
I/O 复用模型:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rameUk1x-1644211103401)(C:\Users\xinge.hu\Documents\网络编程\n13_IO复用模型.bmp)]
-
信号驱动式I/O模型:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nAeWJBLy-1644211103401)(C:\Users\xinge.hu\Documents\网络编程\n14 信号驱动式IO模型.bmp)]
-
异步I/O 模型:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qMUqWZII-1644211103401)(C:\Users\xinge.hu\Documents\网络编程\n14 异步IO 模型.bmp)]
-
各种I/O模型比较:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lxLWzI7F-1644211103402)(C:\Users\xinge.hu\Documents\网络编程\n16 IO模型比较.bmp)]
代码运行框架:
1. 客户端代码:
int main()
{
/*定义结构 fd 和地址族结构*/
int socketfd; //定义sockfd
int n;
struct socketaddr_in servaddr; //定义地址族结构
/*创建套接字获取socktfd*/
sockfd=Socket(AF_INET,SOCK_STREA,0); //通过socket 获取套接字fd
/*设置servaddr 结构*/
servaddr.sin_family=AF_INET; //设置地址族
servaddr.sin_port = htons(13); //设置端口
servaddr.sin_addr =xxxx; //设置IP地址
/*将创建的套接字与指定主机和端口进行绑定*/
connect(sockfd, (SA*)&servaddr,sizeof(servaddr)); //将套接字与指定IP 和端口关联。
/*通过套接字进行读取数据*/
while((n=read(socketfd,buffer,BUFFERLEN))>0)
{
//从套接字读取数据
......
}
return 0;
}
2. 服务端代码:
int main()
{
/*定义fd和socketaddr_in 结构*/
int listenfd, connfd;
struct socketaddr_in servaddr;
...
/*创建套接字获取socketfd*/
listenfd = Socket(AF_INET,SOCK_STREAM,0);
/*设置socketaddr*/
servaddr.sin_family = AF_INET;
servaddr.sin_addr = htonl(INADDR_ANY); // 表示所有连上的主机
servaddr.sin_port = htons(13);
/*将套接字与主机和端口绑定*/
Bind(listenfd, (SA*)servaddr,sizeof(servaddr));
/*监听socketfd */
Listen(listenfd, LISTENQ);
/*从向连接上的socketfd 中写数据*/
while (1){
connfd = Accept(listenfd, (SA*)NULL, NULL); // 当有主机连接上时,accept 就会返回被连上的socketfd.
Write(connfd, buff, strlen(buff))
}
return 0;
}
网络发现常用指令:
指令通常放置/sbin 或者/usr/sbin下。
netstat 指令:
-
netstat -i:
提供网络接口的信息。
-
netstat -r:
展示路由表信息。 用-n 标志以输出数值地址。 默认路由器和IP地址。
-
netstat -a:
查看当前所有sock的状态
ifconfig 指令:
-
ifconfig wlan0
查看网络接口的具体信息
ping 指令:
- ping -b 广播IP
可以找到本地网络中众多主机的IP地址。
网络编程注意事项:
网络编程常见三种情况:
- 当fork子进程时,必须捕获SIGCHLD 信号 (// 如果父进程不捕获SIGCHLD信号不执行waitpid 等待子进程退出,子进程将会变成僵尸进程。)
- 当捕获信号时,必须处理被中断的系统调用;(**//如父进程阻塞于accept时–》子进程结束–》父进程在accept 阻塞时处理SIGCHLD 信号,结束时,慢系统调用accept 将返回EINTR,如果系统调用没有处理中断的代码,accept 将会结束,不再继续监听新的连接)
- SIGCHLD 的信号处理函数必须正确编写,应使用waitpid 函数以免留下僵尸进程。
客户服务器之间传递结构:
传递文本结构:
if(sscanf(line, “%ld%ld”,&arg1, &arg2)==2)
snprintf(line,sizeof(line),“%ld\n”,arg1+arg2);
其它linux 基础知识:
POSIX 信号处理:
- 信号: 告知某个进程发生了某个事件的通知,有时称为软件中断。
- 信号发起: 1. 由一个进程发给另一个进程(或自身);2.由内核发给某个进程(如SIGCHLD 信号)
-
信号处置: 每个信号都有与之关联的处置,也称为行为。 可以使用sigaction函数设定信号的处置,其中信号的处置称为信号处理函数,信号处理函数在特定信号发生时被调用这一行为称为捕获信号。
- 信号处置可以设定为SIG_IGN 来忽略它,可以设置SIG_DFT 来启用默认处置。
- 特点: 1. 一旦安装信号处理函数,它便一直安装着。2.在信号处理函数运行期间,被递交的信号是阻塞的。3.如果一个信号在阻塞期间产生1次或多次,信号被解阻塞之后通常只递交一次。
SIGPIPE 信号:
当一个进程向某个已收到RST 的套接字(比如服务端sockfd已关闭,但客户端还向sockfd 中写数据,服务端就会立马回应RST)执行写操作时,内核向该进程发送一个SIGPIPE 信号,该信号的默认行为是终止程序。
- 如果不处理该信号,则直接设置为SIG_IGN
- 如果处理该信号,则捕获该信号,且在信号处理函数返回后再处理来自write的EPIPE 。
僵尸进程:
-
当子进程结束时,会给父进程发送SIGCHLD 信号,如果父进程没有响应该信号,则子进程会变成僵尸进程。 僵尸进程存在原因是为了某个时间,父进程可以知道子进程的ID 以及CPU占用量等。
-
如果僵尸进程的父进程终止时,该进程会交给init 进程。(即该子进程的父进程ID设置为1)
慢系统调用:
- 如网络编程中的accept 函数,当没有网络连接上时,accept 永远阻塞,这类系统调用称为慢系统调用。
- 适用于慢系统调用的基本规则是:当阻塞与某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个ETNTR错误。 有些内核会自动重启某些被中断的系统调用,有些不会。为便于移植,在编写捕获信号程序时,必须对慢系统调用返回EINTR有所处理。
- 在对慢系统调用发生EINTR 错误时,accept/read/write/select/open 可以自己重启被中断的系统调用。。但connect 被一个捕获的信号中断而不自动重启时,我们必须调用select来等待连接完成。
wait 和waitpid 函数:
#include <sys/wait.h>
pid_t wait(int *statloc);
- 返回子进程的ID
- statloc 为子进程的一个终止状态
pid_t waitpid(pid_t pid,int *statloc, int options);
- 返回子进程的进程ID
- pid 指等待某个特定的子进程关闭
- statloc 为子进程终止状态
- options 为附加选项。
两者比较:
-
wait 进程没有已终止的子进程,wait 将阻塞到现有子进程第一个终止为止;
-
waitpid: 等待特定进程终止,第一个参数为-1表示等待第一个终止的子进程。options 附加选项为WNOHANG 时,告知内核没有已终止子进程时不要阻塞。