????个人主页:企鹅不叫的博客
????专栏
⭐️ 博主码云gitee链接:代码仓库地址
⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!
????系列文章????
【网络编程】第一章 网络基础(协议+OSI+TCPIP+网络传输的流程+IP地址+MAC地址)
【网络编程】第二章 网络套接字(socket+UDP协议程序)
【网络编程】第三章 网络套接字(TCP协议程序+多进程+多线程+线程池)
文章目录
????一、守护进程
????1.进程组和会话
进程组:进程组由一个进程或者多个进程组成,每个进程组有唯一的进程组ID,每个进程组有一个进程组组长(和进程组ID一样)。
会话:有一个或者多个进程组组成的集合,一个会话的几个进程组可以分为前台进程和后台进程。
????2.守护进程概念和特点
守护进程,精灵进程,当Linux系统启动的时候,会启动很多系统服务,这些进程服务是没有终端的也就是说你把终端关闭了这些系统服务是不会停止的,他们一直运行着他们有一个名字就叫做守护进程。
- 生存周期长[不是必须]:一般是操作系统启动的时候他启动,操作系统关闭的时候他也关闭
- 守护进程和终端没有关联,也就是说他们没有控制终端,所以你控制终端退出也不会导致守护进程退出
- 守护进程是在后台运行不会占着终端,终端可以执行其它命令
????3.守护进程创建
setsid
#include <unistd.h> pid_t setsid(void);
功能:创建一个新会话,谁调用这个函数,谁就成为这个新会话进程组的组长
返回值:成功返回进程ID,失败返回-1
注意:进程组组长不能调用
下面是创建一个守护进程方法,之后将下面函数加到我们之前写的TCP服务器代码中,服务器就部署到Linux代码当中,此时我们退出服务器只运行客户端也能成功运行。
#include <cstdio> #include <iostream> #include <signal.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> void daemonize() { int fd = 0; // 1. 忽略SIGPIPE signal(SIGPIPE, SIG_IGN); // 2. 让子进程成为进程组组长 if (fork() > 0) exit(1); // 3. 设置自己是一个独立的会话 setsid(); // 4. 重定向0,1,2,此时fd是3,并且打开黑洞文件 if ((fd = open("/dev/null", O_RDWR)) != -1) { dup2(fd, STDIN_FILENO);//0 dup2(fd, STDOUT_FILENO);//1 dup2(fd, STDERR_FILENO);//2 // 5. 关闭掉不需要的fd,此时完成重定向 if(fd > STDERR_FILENO) close(fd); } } //./tcpClient _server_port _server_ip main (int argc, char* argv[]){ if(argc != 2 && argc != 3){ cout << "Usage: " << argv[0] << " port" << " IP "<< endl; exit(3); } int port = atoi(argv[1]); string ip; if(argc == 3) ip = argv[2]; daemonize();//此时我们进程是守护进程 TcpServer svr(port, ip); svr.InitServer(); svr.start(); return 0; }
列出所有守护进程
ps -ajx | grep server | grep -v grep
结果:下面是端口 8081 和8082的守护进程,想要关闭就直接kill -9 pid即可,(8447或者8521)
1 8447 8447 8447 ? -1 Ssl 1001 0:00 ./server 8081 127.0.0.1 1 8521 8521 8521 ? -1 Ssl 1001 0:00 ./server 8082 127.0.0.1
总结
守护进程和后台进程的区别:
守护进程和终端没有关系但是后台进程能往终端上输出东西和终端有关联
守护进程关闭终端时不受影响而,后台进程会随着终端的退出而退出
????二、TCP英译汉
英译汉TCP服务器要做的就是,根据客户端发来的英文单词找到其对应的中文意思,然后将该中文意思作为响应数据发给客户端。
主要程序使用TCP通讯线程池代码,所以只用修改任务类中任务函数即可
英译汉时需要根据英文单词找到其对应的中文意思,因此我们需要建立一张映射表,其中英文单词作为映射表中的键值key,而中文意思作为与键值相对应的value,这里可以直接使用C++STL容器当中的unordered_map容器。
以下代码只修改了任务类中的read读取部分,将读取到的英文转化为中文然后返回给客户端
static void Service(int sock, string client_ip, int client_port) { char command[1024]; while (1) { unordered_map<string, string> dict; dict.insert(std::make_pair("penguin", "企鹅")); dict.insert(std::make_pair("blog", "博客")); dict.insert(std::make_pair("socket", "套接字")); string value; ssize_t size = read(sock, command, sizeof(command) - 1); // 我们认为我们读到的都是字符串 if (size > 0) { command[size] = '\0'; cout << client_ip << ":" << client_port << "# " << command << endl; string key = command; auto it = dict.find(key); if (it != dict.end()) { value = it->second; } else { value = key; } write(sock, value.c_str(), value.size()); else if (size == 0) { // 对端关闭连接 cout << client_ip << ":" << client_port << " close!" << endl; break; } else { // 读取失败 cerr << sock << " read error!" << endl; break; } } // 服务结束 close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏! cout << client_ip << ":" << client_port << " service done!" << endl; }
结果:
//client [Jungle@VM-20-8-centos:~/lesson38]$ ./client 8080 127.0.0.1 socket creat succes, sock: 3 connect success... Please Enter# blog server echo# 博客 //server [Jungle@VM-20-8-centos:~/lesson38]$ ./server 8080 127.0.0.1 socket creat succes, sock: 3 bind success listen success get a new link->4 [127.0.0.1]:44420 127.0.0.1:44420# blog
????三、TCP协议通讯流程
TCP协议的客户端/服务器程序的一般流程
????1.三次握手
初始化服务器
服务器初始化:
调用socket,创建文件描述符。
调用bind,将当前的文件描述符和IP/PORT绑定在一起,如果这个端口已经被其他进程占用了,就会bind失败。
调用listen,声明当前这个文件描述符作为一个服务器的文件描述符,为后面的accept做好准备。
调用accept,并阻塞,等待客户端连接到来。
建立连接
建立连接的过程:
调用socket,创建文件描述符。
调用connect,向服务器发起连接请求。
connect会发出SYN段并阻塞等待服务器应答(第一次)。
服务器收到客户端的SYN,会应答一个SYN-ACK段表示“同意建立连接”(第二次)。
客户端收到SYN-ACK后会从connect返回,同时应答一个ACK段"时间"(第三次)。
数据交互
数据传输的过程“:
建立连接后,TCP协议提供全双工的通信服务,所谓全双工的意思是,在同一条连接中,同一时刻,通信双方可以同时写数据,相对的概念叫做半双工,同一条连接在同一时刻,只能由一方来写数据。
服务器从accept返回后立刻调用read,读socket就像读管道一样,如果没有数据到达就阻塞等待。
这时客户端调用write发送请求给服务器,服务器收到后从read返回,对客户端的请求进行处理,在此期间客户端调用read阻塞等待服务器端应答。
服务器调用write将处理的结果发回给客户端,再次调用read阻塞等待下一条请求。
客户端收到后从read返回,发送下一条请求,如此循环下去。
????2.四次挥手
端口连接
服务器和客户端close的过程
断开连接的过程:
如果客户端没有更多的请求了,就调用close关闭连接,客户端会向服务器发送FIN段(第一次)。
此时服务器收到FIN后,会回应一个ACK,同时read会返回0(第二次)。
read返回之后,服务器就知道客户端关闭了连接,也调用close关闭连接,这个时候服务器会向客户端发送一个FIN(第三次)。
客户端收到FIN,再返回一个ACK给服务器(第四次)。
为什么要断开连接?
建立连接本质上是为了保证通信双方都有专属的连接,这样我们就可以加入很多的传输策略,从而保证数据传输的可靠性。但如果双方通信结束后不断开对应的连接,那么系统的资源就会越来越少。
因为服务器是会收到大量连接的,操作系统必须要对这些连接进行管理,在管理连接时我们需要“先描述再组织”。因此当一个连接建立后,在服务端就会为该连接维护对应的数据结构,并且会将这些连接的数据结构组织起来,此时操作系统对连接的管理就变成了对链表的增删查改。
如果一个连接建立后不断开,那么操作系统就需要一直为其维护对应的数据结构,而维护这个数据结构是需要花费时间和空间的,因此当双方通信结束后就应该将这个连接断开,避免系统资源的浪费,这其实就是TCP比UDP更复杂的原因之一,因为TCP需要对连接进行管理。
????四、TCP和UDP对比
- 可靠传输 vs 不可靠传输
- 有连接 vs 无连接
- 字节流 vs 数据报