QA:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oyeT7OVl-1644211103388)(C:\Users\xinge.hu\Documents\网络编程\不同网络间的通信框架.bmp)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1EYq74X5-1644211103389)(C:\Users\xinge.hu\Documents\网络编程\OSI 模型及网际层.bmp)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JnLZZU16-1644211103390)(C:\Users\xinge.hu\Documents\网络编程\TCP_IP 协议_NO4.bmp)]
TCP 是面向连接的可靠的的传输协议。
有包发送确认机制:发送方发1数据包后,对端会回ACK确认;
有超时重传机制: 通过RTT算法获取发包后收到确认的大致时间,超时就会重发数据包。
有分节系列号标识:一段数据可能会被分隔成多个数据包,接收方通过序列号标识重新组合,对于误重传的包会丢弃。
有流量控制机制:会告知1次所能接受的最大数据量,称为通话窗口。 通话窗口动态变化,往缓冲区写数据,通话窗口会变小,从缓冲区读数据,通话窗口会变大。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yxFDdn2T-1644211103390)(C:\Users\xinge.hu\Documents\网络编程\TCP 三次握手_N5.bmp)]
SYN 可以包含以下信息:
注: ACK 确认是SYN 序列号+1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NgNUQM7U-1644211103390)(C:\Users\xinge.hu\Documents\网络编程\TCP 四次挥手_N6.bmp)]
四次挥手有时候可能三次就完了, 但通常一端收到另一端的FIN 后,等待一会才会close. 两端都可以主动close.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2MjQzKeK-1644211103391)(C:\Users\xinge.hu\Documents\网络编程\TCP 分组_TIME_WAIT_n8.bmp)]
TIME_WAIT 状态:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-snOrG3ls-1644211103391)(C:\Users\xinge.hu\Documents\网络编程\TCP 输出n9.bmp)]
#include <sys/socket.h>
int socket(int family, int type, int protocol)
Family | 说明 |
---|---|
AF_INET | IPv4 协议族 |
AF_INET6 | IPv6协议族 |
AF_LOCAL | Unix 域协议 |
AF_ROUTE | 路由套接字 |
AF_KEY | 秘钥套接字 |
AF_XXX 是地址族,RF_XXX 是协议族,通常两者相等。原本预想一个协议族对应多个地址族,但目前实际上一个协议族对应一个地址族。
type | 说明 |
---|---|
SOCK_STREAM | 字节流套接字 |
SOCK_DGRAM | 数据包套接字 |
SOCK_SEQPACKET | 有序分组套接字 |
SOCK_RAW | 原始套接字 |
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 |
#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
成功返回0, 错误返回-1
第一个参数是套接字fd, 第二个参数指向套接字地址结构的指针;第三个参数是地址长度。
connect 函数由客户端发起,该函数调用前不必非得调用bind函数,因为内核会确定源IP地址,并选择临时端口作为源端口。
如果是TCP套接字,则connect函数触发TCP三次握手,且在连接成功或错误时返回。 常见错误如下:
connect 失败之后,必须关闭套接字重现建立套接字才可以再次调用connect。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
#include <sys/socket.h>
int listen(int sockfd, int backlog)
返回值:成功是返回0.出错时返回-1.
作用:将套接字转换为被动套接字,指示内核应该受指向该套接字的连接请求。
参数:第一个参数为套接字fd, 第二个参数为监听套接字维护两个队列的和的最大值。(往往是该值*1.5)
注意:如果不要客户连接时,直接关闭该套接字,而不要将backlog设置为0
该函数由TCP服务器端调用,通常在socket,bind 之后,accept 之前。
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen *addrlen)
#include <unistd.h>
int close(int sockfd);
#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 关联的本地协议地址和外地协议地址,
什么时候需要用到本地协议地址呢?
什么时候用到获取外地协议地址呢?
函数说明:
函数格式:
#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
};
分类:
举例说明:
//使用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)]
函数格式:
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
函数参数说明:
shutdown 函数和close 函数比较:
#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);
#include <poll.h> int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
struct pollfd{ int fd; short events; //表示要关注的事件 short revents; //根据该值确定哪个事件到达 };
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-daqsTV7y-1644211103392)(C:\Users\xinge.hu\Documents\网络编程\poll_event.bmp)]
#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; }
#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
设置和获取套接字属性;
通用套接字选项:
设置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 封包传给IP层,再进行IP封包到目的地。 无论是否传输成功,发送端不进行重传和检查,因此会有丢包的情况。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQ39JDbd-1644211103393)(C:\Users\xinge.hu\Documents\网络编程\n10_UDP 输出.bmp)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XzgPgmwP-1644211103393)(C:\Users\xinge.hu\Documents\网络编程\udp_客户服务器套接字函数框架.bmp)]
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void*buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t *addrlen);
recvfrom : 获取源IP地址
recvfrom: 获取源端口号
recvmsg: 获取目标IP地址
getsockname: 获取目标端口
UDP 也可以使用connect ,表示已连接的UDP 套接字,已连接的UDP 套接字有以下特点:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wM7GWTaP-1644211103394)(C:\Users\xinge.hu\AppData\Roaming\Typora\typora-user-images\1632733007314.png)]
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]; };
数据存储分为大端模式和小端模式;比如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.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); |
记录在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));
说明:比如在客户端和服务器TCP连接实例中,客户端既要通过fgets()获取输入子串,又要关注sockfd 套接字,当服务器端终止发出FIN时,客户端因阻塞在fgets() 而无法看到EOF信息,因此需要使用I/O 复用。进程需要预先告知内核的能力,使内核一旦发现进程指定的一个或多个I/O 条件就绪时,就通知进程,这个能力称为I/O复用。
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)]
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; }
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 -i:
提供网络接口的信息。
netstat -r:
展示路由表信息。 用-n 标志以输出数值地址。 默认路由器和IP地址。
netstat -a:
查看当前所有sock的状态
ifconfig wlan0
查看网络接口的具体信息
可以找到本地网络中众多主机的IP地址。
if(sscanf(line, “%ld%ld”,&arg1, &arg2)==2)
snprintf(line,sizeof(line),“%ld\n”,arg1+arg2);
当一个进程向某个已收到RST 的套接字(比如服务端sockfd已关闭,但客户端还向sockfd 中写数据,服务端就会立马回应RST)执行写操作时,内核向该进程发送一个SIGPIPE 信号,该信号的默认行为是终止程序。
当子进程结束时,会给父进程发送SIGCHLD 信号,如果父进程没有响应该信号,则子进程会变成僵尸进程。 僵尸进程存在原因是为了某个时间,父进程可以知道子进程的ID 以及CPU占用量等。
如果僵尸进程的父进程终止时,该进程会交给init 进程。(即该子进程的父进程ID设置为1)
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc, int options);
两者比较:
wait 进程没有已终止的子进程,wait 将阻塞到现有子进程第一个终止为止;
waitpid: 等待特定进程终止,第一个参数为-1表示等待第一个终止的子进程。options 附加选项为WNOHANG 时,告知内核没有已终止子进程时不要阻塞。