网站建设 qq业务网制作,文员工作内容,县电子政务办网站建设工作思路,深圳东门密室逃脱1 TCP协议 TCP提供客户机与服务器的链接。一个完整TCP通信过程需要经历三个阶段 1#xff09;首先#xff0c;客户机必须建立与服务器的连接#xff0c;所谓虚电路 2#xff09;然后#xff0c;凭借已建立好的连接#xff0c;通信双方相互交换数据 3#xff09;最后首先客户机必须建立与服务器的连接所谓虚电路 2然后凭借已建立好的连接通信双方相互交换数据 3最后客户机与服务器双双终止连接结束通信过程 TCP保证数据传输的可靠性(超时重传、反向确认 TCP的协议栈底层在向另一端发送数据时会要求对方在一个给定的时间窗口内返回确认。如果超过了这个时间窗口仍没有收到确认则TCP会重传数据并等待更长的时间。只有在数次重传均告失败以后TCP才会最终放弃。TCP含有用于动态估算数据往返时间Round-Trip Time, RTT的算法因此它知道等待一个确认需要多长时间。 TCP保证数据传输的有序性 TCP的协议栈底层在向另一端发送数据时会为所发送数据的每个字节指定一个序列号。即使这些数据字节没有能够按照发送时的顺序到达接收方接收方的TCP也可以根据它们的序列号重新排序再把最后的结果交给应用程序。 TCP是全双工的 在给定的连接上应用程序在任何时候都既可以发送数据也可以接收数据。因此TCP必须跟踪每个方向上数据流的状态信息如序列号和通告窗口的大小。
1.1 三次握手建立连接 三次握手 交换数据 1客户机的TCP协议栈向服务器发送一个SYN分节SYN比特位是1告知对方自己将在连接中发送数据的初始序列号称为主动打开。 2服务器的TCP协议栈向客户机发送一个单个分节其中不仅包括对客户机SYN分节的ACK应答还包含服务器自己的SYN分节ACK和SYN比特位均是1以告知对方自己在同一连接中发送数据的初始序列号。 3客户机的TCP协议栈向服务器返回ACK应答以表示对服务器所发SYN的确认。 交换数据 - 一旦连接建立客户机即可构造请求包并发往服务器。服务器接收并处理来自客户机的请求包构造响应包。 - 服务器向客户机发送响应包同时捎带对客户机请求包的ACK应答反向确认。 - 客户机接收来自服务器的响应包同时向对方发送ACK应答反向确认。
1.2 四次挥手关闭连接 1客户机或者服务器主动关闭连接TCP协议向对方发送FIN分节表示数据通信结束。如果此时尚有数据滞留于发送缓冲区中则FIN分节跟在所有未发送数据之后。 2接收到FIN分节的另一端执行被动关闭一方面通过TCP协议栈向对方发送ACK应答另一方面向应用程序传递文件结束符。 3一段时间后方才接收到FIN分节的进程关闭自己的链接同时通过TCP协议栈向对方发送FIN分节。 4对方在收到FIN分节后发送ACK应答。
2 TCP函数
2.1 listen() #include sys/socket.h int listen( int socket, int backlog ); 功能启动侦听在指定套接字上启动对连接请求的侦听功能 sockfd套接字描述符在调用此函数之前是一个主动套接字是不能感知连接请求的 在调用此函数并成功返回后是一个被动套接字具有感知连接请求的能力。 backlog未决连接请求队列的最大长度一般取不小于1024的值 返回值成0败-1
2.2 accept() 三次握手始于connect()终于accept()结束 #include sys/socket.h int accept( int sockfd, struct sockaddr* addr, socklen_t* addrlen ); 功能等待并接受连接请求在指定套接字上阻塞直到连接建立完成 sockfd侦听套接字描述符 addr输出连接请求发起方的地址信息 addrlen输出连接请求发起方的地址信息字节数 返回值成功返回可用于后续通信的连接套接字描述符失败返回-1
2.3 recv() #include sys/socket.h ssize_t recv( int sockfd, void* buf, size_t count, int flags ); 功能接收数据 flags取0则与read()函数等价。另外也可取以下值 MSG_DONTWAIT 以非阻塞方式接收数据 MSG_OOB 接收带外数据 MSG_WAITALL 等待所有数据即不接收到count个字节就不返回 返回值成功返回实际接收到的字节数失败返回-1
2.4 send() #include sys/socket.h ssize_t send( int sockfd, void const* buf, size_t count, int flags ); 功能发送数据 flags取0则与write()函数等价。另外也可取以下值 MSG_DONTWAIT 以非阻塞方方式发送数据 MSG_OOB 接收带外数据 MSG_DONTROUTE 不查路由表直接在本地网络中寻找目的主机 返回值成功返回实际发送的字节数失败返回-1
3 TCP编程模型 4 通信终止
4.1 客户机主动终止 在某个时刻客户机认为已经不再需要服务器继续为其提供服务器了于是它在接收完最后一个响应包以后通过close()函数关闭与服务器通信的套接字。 客户机的TCP协议栈向服务器发送FIN分节并得到对方的ACK应答。 服务器专门负责与客户机通信的子进程此刻正视图通过recv()接收下一个请求包结果却因为收到来自客户机的FIN分节而返回0。 于是该子进程退出收发循环同时通过close()关闭连接套接字导致服务器的TCP协议栈向客户机发送FIN分节使对方进入TIME_WAIT状态并在收到对方ACK应答以后自己进入CLOSED状态。 随着收发循环的退出服务器子进程终止并在服务器主进程的SIGCHLD(17)信号处理函数中被回收。 通信过程宣告结束。
4.2 服务器主动终止 服务器专门负责和某个特定客户机通信的子进程在运行过程中出现错误不得不调用close()函数关闭连接套接字或者直接退出甚至被信号杀死。于是服务器的TCP协议栈向客户机发送FIN分节并得到对方的ACK应答。 A、如果客户机这时正视图通过recv()接收响应包那么该函数会返回0。客户机可据此判断服务器已宕机直接通过close()关闭与服务器通信的套接字终止通信进程。 B、如果客户机这时正视图通过send()发送请求包那么该函数并不会失败但会导致对方以RST分节做出响应该响应分节甚至会先于FIN分节被紧随其后的recv()收到并返回-1同时置errno为ECONNRESET。这也是终止通信的条件之一。
4.3 服务器主机不可达(主机崩溃、网络中断、路由失效...) 在服务器主机不可达的情况下无论是客户机还是服务器它们的TCP协议栈都不可能再有任何数据分节的交换。因此客户机通过send()函数发送完请求包以后会阻塞在recv()上等待来自服务器的响应包。 这是客户机的TCP协议栈会持续地重传数据分节视图得到对方的ACK应答。源自伯克利的实现最多重传12次最长等待9分钟。 当TCP最终决定放弃时会通过recv()向用户进程返回失败并置errno为ETIMEOUT或EHOSTUNREACH或ENETUNREACH。 此后即使服务器主机被重启或者通信线路被恢复由于TCP协议栈已丢失了与先前连接有关的信息通信依然无法继续对所接收到的一切数据一律响应RST分节只有在重新建立TCP连接后才能继续通信。
5 域名解析 IP地址是网络上标识站点的数字地址为了方便记忆采用域名来代替IP地址标识站点地址。 域名解析就是域名到IP地址的转换过程由DNS服务器完成。 当应用过程需要将一个主机域名映射为IP地址时就调用域名解析函数解析函数将待转换的域名放在DNS请求中以UDP报文方式发给本地域名服务器。本地的域名服务器查到域名后将对应的IP地址放在应答报文中返回。
5.1 gethostbyname() #include netdb.h struct hostnet* gethostbyname( char const* host_name ); 功能通过参数所传的主机域名获取主机信息 host_name主机域名 返回值成功返回表示主机信息的结构体指针失败返回NULL 注意该函数需要在联网情况下使用。 struct hostent { char *h_name; // 主机官方名 char **h_aliases; // 主机别名表 int h_addrtype; // 地址类型 int h_length; // 地址长度 int **h_addr_list; // IP地址表 }; 对于WEB服务器而言主机官方名有一个而主机别名可能有多个这些别名都是为了便于用户记忆。同时IP地址也可能有多个。 h_aliases - * - xxx\n - * - xxx\n NULL; h_addr_list - * - in_addr - * - in_addr NULL
//tcpser.c 基于tcp的服务器
#includestdio.h
#includestring.h
#includectype.h // toupper()
#includesys/socket.h//网络
#includesys/types.h//网络
#includearpa/inet.h//网络
#includesignal.h
#includesys/wait.h
#includeerrno.h
#includeunistd.h
//信号处理函数,收尸int main(void){//捕获17号信号printf(服务器:创建套接字\n);int sockfd socket(AF_INET,SOCK_STREAM,0);if(sockfd -1){perror(socket);}//组织地址结构printf(服务器:组织地址结构\n);struct sockaddr_in ser;ser.sin_family AF_INET;ser.sin_port htons(8980); //8980由小端转大端//ser.sin_addr.s_addr inet_addr(192.168.222.136);ser.sin_addr.s_addr INADDR_ANY;//绑定地址结构和套接字printf(服务器:绑定套接字和地址结构\n);if(bind(sockfd,(struct sockaddr*)ser,sizeof(ser)) -1){perror(bind);return -1;}//开启侦听 套接字 主动--被动--感知连接请求printf(服务器:启动侦听\n);if(listen(sockfd,1024) -1){perror(listen);return -1;}for(;;){//等待并建立通信连接printf(服务器:等待并建立通信连接\n);struct sockaddr_in cli;//用来输出客户端的地址结构socklen_t len sizeof(cli);//用来输出地址结构大小int conn accept(sockfd,(struct sockaddr*)cli,len);if(conn -1){perror(accept);return -1;}printf(服务器:接收到%s:%hu的客户端的连接\n,inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));//创建子进程 fork//子进程服务业务处理//业务处理printf(服务器:业务处理\n);for(;;){//接受客户端发来的小写的串char buf[64] {};ssize_t size read(conn,buf,sizeof(buf)-1);if(size -1){perror(read);return -1;}if(size 0){printf(服务器:客户端断开连接\n);break;}//转成大写for(int i 0;i strlen(buf);i){buf[i] toupper(buf[i]);}//将转成大写的串回传客户端if(write(conn,buf,strlen(buf)) -1){perror(write);return -1;}}printf(服务器:关闭套接字\n);close(conn);}close(sockfd); return 0;
}#includestdio.h
#includestring.h
#includectype.h // toupper()
#includesys/socket.h//网络
#includesys/types.h//网络
#includearpa/inet.h//网络
#includesignal.h
#includesys/wait.h
#includeerrno.h
#includeunistd.h
//信号处理函数,收尸
void sigchild(int signum){printf(服务器:捕获到%d号信号\n,signum);for(;;){pid_t pid waitpid(-1,NULL,WNOHANG);if(pid -1){if(errno ECHILD){printf(服务器:没有子进程\n);break;}else{perror(waitpid);return ;}}else if(pid 0){printf(服务器:子进程在运行\n);break;}else{printf(服务器:回收了%d进程的僵尸\n,pid);}}
}int main(void){//捕获17号信号if(signal(SIGCHLD,sigchild) SIG_ERR){perror(signal);return -1;}printf(服务器:创建套接字\n);int sockfd socket(AF_INET,SOCK_STREAM,0);if(sockfd -1){perror(socket);}//组织地址结构printf(服务器:组织地址结构\n);struct sockaddr_in ser;ser.sin_family AF_INET;ser.sin_port htons(8980);//ser.sin_addr.s_addr inet_addr(192.168.222.136);ser.sin_addr.s_addr INADDR_ANY;//接受任意IP地址的数据服务器可能有多个地址//绑定地址结构和套接字printf(服务器:绑定套接字和地址结构\n);if(bind(sockfd,(struct sockaddr*)ser,sizeof(ser)) -1){perror(bind);return -1;}//开启侦听 套接字 主动--被动--感知连接请求printf(服务器:启动侦听\n);if(listen(sockfd,1024) -1){perror(listen);return -1;}for(;;){//等待并建立通信连接printf(服务器:等待并建立通信连接\n);struct sockaddr_in cli;//用来输出客户端的地址结构socklen_t len sizeof(cli);//用来输出地址结构大小int conn accept(sockfd,(struct sockaddr*)cli,len);if(conn -1){perror(accept);return -1;}printf(服务器:接收到%s:%hu的客户端的连接\n,inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));//创建子进程 forkpid_t pid fork();if(pid -1){perror(fork);return -1;}//子进程服务业务处理//业务处理if(pid 0){close(sockfd);printf(服务器:业务处理\n);for(;;){//接受客户端发来的小写的串char buf[64] {};ssize_t size read(conn,buf,sizeof(buf)-1);if(size -1){perror(read);return -1;}if(size 0){printf(服务器:客户端断开连接\n);break;}//转成大写for(int i 0;i strlen(buf);i){buf[i] toupper(buf[i]);}//将转成大写的串回传客户端if(write(conn,buf,strlen(buf)) -1){perror(write);return -1;}}printf(服务器:关闭套接字\n);close(conn);return 0;//!!!!!!}//父进程代码,关闭通信套接字close(conn);//通信套接字1个通信套接字对应1个客户端}close(sockfd);//侦听套接字整个服务器只有1个return 0;
}//结合tcpcli,1台虚拟机做服务器执行本代码另一台做客户端执行tcpcli
//tcpcli.c 基于的客户端
#includestdio.h
#includestring.h
#includeunistd.h
#includesys/socket.h
#includesys/types.h
#includearpa/inet.hint main(void){//创建套接字printf(客户端:创建套接字\n);int sockfd socket(AF_INET,SOCK_STREAM,0);if(sockfd -1){perror(socket);return -1;}//准备服务器的地址结构printf(客户端:准备服务器的地址结构\n);struct sockaddr_in ser;ser.sin_family AF_INET;ser.sin_port htons(8980);ser.sin_addr.s_addr inet_addr(192.168.222.136);//发起连接printf(客户端:发起连接\n);if(connect(sockfd,(struct sockaddr*)ser,sizeof(ser)) -1){perror(connect);return -1;}//业务处理for(;;){//通过键盘获取小写的串char buf[64] {};fgets(buf,sizeof(buf),stdin);// ! 退出if(strcmp(buf,!\n) 0){break; }//将小写的串发送给服务器if(send(sockfd,buf,strlen(buf),0) -1){perror(send);return -1;}//接受服务器回传大写的串if(recv(sockfd,buf,sizeof(buf)-1,0) -1){perror(recv);return -1;}//显示printf(%s,buf);}printf(客户端:关闭套接字\n);close(sockfd);return 0;
}
1