1.端口号(传输层协议的重要内容)
当两台主机在网络通信时,实际上是主机将通信数据发送给服务器,IP地址可以用来标识主机的唯一性,但是主机上有许多个进程,也要标识这些每台主机上进行网络通信的进程的唯一性。
为了更好的标识一台主机上服务进程的唯一性,我们采用端口号port来标识服务器进程/客户端进程的唯一性。
在传输层提供了端口号,可以供上层使用,让自己的应用进程和某一个端口号数字关联起来,就可以保证这个端口号的唯一性。
IP地址(主机全网唯一性),该主机上的端口号,标识该服务器上进程的唯一性,ipA+portA ipA+portA。网络通信的本质,其实就是进程间通信!进程间通信需要让不同的进程看到同一份资源—网络。网络通信本质就是I/O操作,收到对方发送的数据或者给对方发送数据。
a.为什么使用端口号port来标定该主机下进程的唯一性,而不使用pid呢?
1>pid是操作系统指定的,这样会使在网络中依赖操作协同,为了网络和系统解耦才采用的端口号;
2>一般客户端和服务器进行网络进程间通信,往往是客户端先发送数据,需要客户端每次都能找到服务器进程—服务器的唯一性不能做任何改变IP+port,不能使用pid(每次开关机都会变化)。
3>不是所有的进程都要提供网络服务和网络请求,但所有的进程都需要pid,不是所有的进程都需要port,需要进行网络通信才需要port。
b.数据包在进行自底向上交付的时候,还要能够保证数据能够递交给应用层某些服务器进程,操作系统未来如何根据port找到目标进程呢?在操作系统内部维护了基于端口号做key值得哈希表,value对应的就是PCB得地址,就可以把数据交付给该进程,找到他得文件描述符表,找到文件对象,将数据拷贝到文件的缓冲区中,然后用户用读文件的方式将数据读入。一个节点可能被链入到多个数据结构中。
一个端口号只能被一个进程绑定从进程到端口是唯一的。而一个进程可以绑定多个端口号,该进程可能要提供多种网络服务。端口号提供不同得服务—???如果一个主机内多个进程都要用到一个端口号,用到该服务呢
在网络通信,我们还需要源端口(数据发送的端口)和目的端口(目的主机得端口)。所以我们IP+端口来标识唯一性,如果客户端发送数据给服务器,同时要将自己的IP地址和端口号发送给对端,因为对方还要将数据返回,多出来的部分一协议的方式添加到报头。在最开始的时候,服务器端的端口号客户端内置了服务器的端口号。
网络传输层的作用是提供两台终端设备上应用进程之间的通信,并保障通信的可靠性。
TCP/UDP
2.网络字节序:
规定数据在网络传输中都是大端,大端、小端(小小小)都是以字节为单位。我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?—在网络传输中硬件会直接按照从内存低地址到高地址读入数据
未来我们在网络发送时,发送的数据一般都是字符串类型。
3.套接字(socket)-ip+port
—网络套接字:可以实现网络通信和本地通信。在网络通信中,必须显式的指明该进程绑定的端口号,一台机器上可能绑多个IP也必须指明用哪个IP。
—原始套接字:目前使用网络编程通信时是应用层直接调传输层的接口,绕过传输层直接访问底层协议中的有效数据。可以用来抓包、网络监测。
—unix域间套接字:本地通信,类似于命名套接字,和网络套接字用的接口一样。
未来要通信,完成三种通信方式,必须要有三套接口。接口的设计者只设计了一套接口,通过不同的参数就解决了网络通信中所有的场景和问题。
4.socket与UDP网络程序
创建一个套接字底层是创建一个文件,将文件和网卡关联起来,返回值是文件描述符。创建套接字就对应打开网卡。
一切皆文件:当我们在文件系统层打开一个文件,虚拟文件系统给打开的文件创建一个 struct file对象,对应的进程有一个文件描述符表,该数组对应的下标就是文件描述符,数组对应下标的内容指向的是struct file;包含文件的属性和函数指针,函数指针指向底层(键盘/显示器/网卡)。我们就可以用操作文件的方式操作网络。
创建好套接字之后,给其绑定对应的IP,port;接口:bind
传递的第二个参数sockaddr_in结构体:
4.3介绍IP:
127.0.0.1;本地环回。数据贯穿本地协议栈又返回到应用层,用于服务器代码的测试。
127.0.0.1本地IP,如果要让别人来访问,需要公网IP。但是如果使用的是云服务器,它是虚拟化的服务器,不能直接bind云服务器公网IP。如果有虚拟机或独立的Linux系统,可以绑定。
内网IP/局域网IP可以绑定,但是没在一个内网也不能进行通讯。(ifconfig查询inet)
服务器绑定时不需要再指明自己的IP,本地IP/内网IP/外网IP/...服务器主机已经收到的数据报都能被指定端口接收。
4.4.服务器接收客户端信息
测试结果:
业务模块:(客户端向服务端发送指令,服务端返回输出结果)
popen的接口,type类型决定你是以管道读端/写端,则返回值就是另一端
5.socketTCP网络传输
每次调用都会返回一个文件描述符,第一个参数sockfd只负责用来监听是否有新的链接到来,每一个返回值sockfd是用来对每一个客户提供服务。
TCP服务器给客户端提供服务的方式
1.serviceIO,单进程为客户端提供死循环式服务直到客户端退出,accept下一个客户端连接。
2.fork()子进程serviceIO,但子进程需要回收。
—回收方法1:直接waitpid(),父进程阻塞式等待,问题没有解决。
—回收方法2:使用信号处理函数等待子进程退出触发父进程非阻塞式等待,造成问题:当没有新用户连接时,一直陷入accept的continue循环中,依旧造成僵尸进程无法回收。
—回收方法3:父进程创建子进程,子进程创建孙子进程后退出随机被父进程回收,孙子进程变成孤儿进程为客户端提供完服务后退出被操作系统领养利用wait回收它;过于频繁创建进程会给系统造成压力。
—回收方法4:创建子进程并设置signal(SIGCHLD,SIG_IGN);父进程收到SIGCHLD信号时要调用wait或waitpid显式回收子进程,signal将SIGCHLD信号设置为忽略后,父进程就不会收到信号,操作系统会调用wait清理退出的子进程。
3.pthread_create()创建多线程,多线程共享文件描述符。线程分离后系统也会自动释放资源。不论是多进程还是多线程,都是任务到来时才会创建然后关闭,会频繁创建线程或进程;而且,如果有很多客户端连接就会创建过多,造成效率低。
4.使用进程池,提前用父进程创建好的很多子进程,后续父进程打开文件描述符时并不能共享给其它子进程,得使用域间套接的方式传递给另一个进程,但它十分不方便。
5.使用线程池,线程间会共享打开的文件描述符。线程池不适合处理这样需要长期占用线程池的任务,它适合处理快速结束的任务。
守护进程---不受终端关闭影响
PGID组ID,兄弟进程把第一个兄弟的ID作为组ID。
SID会话ID,以bash的进程ID作为一个会话的ID。且这些进程都是Bash创建,他们的PPID一样。
jobs—查看当前会话作业。
fg 1,将1号作业切换到前台。CTRL+Z停止当前的前台作业。bg 1将被停止的一号作业恢复到后台继续运行。
当你运行一个服务器程序时,你输入的Bash指令就会失效,CTRL+Z停止当前进程才可以,是因为服务器进程当前在前台运行,Bash变成后台。
如果想让一个服务器程序永久运行的话,不仅要让它变成一个后台进程。它会受当前会话用户登录和注销的影响,所以要让它自成会话,自成进程组,和终端设备无关,这就叫做守护进程。
系统提供的守护进程接口:
参数1:是否更改新会话工作目录;参数2:是否选择关闭012文件描述符
将来我们可以对系统中的守护进程定制化。
一个进程要想变成守护进程必须调用setsid();谁调用这个接口就会自己新建一个会话,它作为会话中的组长。要求调用这个接口的进程原本不能是组长。
1. 让调用进程忽略掉异常信号--服务器运行时会产生异常信号,不能因为这些信号让服务器退出(客户端文件符关闭,服务器写端就会收到类似SIGPIPE)
2. setsid不允许用户直接调用会报错,因为有可能被组长调用;要先让自己不是组长
3.守护进程是脱离终端的,关闭或者重定向以前进程默认打开的文件,它不关心012这些fd,因为他不交互
4.可选:一个进程运行都有自己的当前路径 ls /proc/7951 -l(显示所查找进程路径软链接),进程所创建的文件就会在进程的执行目录下
#define DEV "/dev/null"void daemonSelf(const char *currPath = nullptr)
{//1. 让调用进程忽略掉异常信号--服务器运行时会产生异常信号,不能因为这些信号让服务器退出(客户端文件符关闭,服务器写端就会收到类似SIGPIPE)signal(SIGPIPE,SIG_IGN);//2. setsid不允许用户直接调用会报错,因为有可能被组长调用;要先让自己不是组长if(fork()>0) exit(0);//守护进程本质是精灵进程,也是孤儿进程的一种(当父进程调用fork()时,系统会为新创建的子进程分配一个新的PID,并让它成为与父进程相同的进程组的成员。)pid_t n = setsid();assert(n != -1);//3.守护进程是脱离终端的,关闭或者重定向以前进程默认打开的文件,它不关心012这些fd,因为他不交互//简单粗暴关闭012未来有打印任务向已经关闭的显示器打印进程会出错然后挂掉。/dev/null文件黑洞,凡是向该文件写的数据全部丢弃掉,凡是从该文件读不阻塞也什么都读不到。//将012重定向到/dev/null中int fd = open(DEV,O_RDWR);//以读写方式打开if(fd >= 0){dup2(fd,0);dup2(fd,1);dup2(fd,2);close(fd);}else {close(0);close(1);close(2);}//4.可选:一个进程运行都有自己的当前路径 ls /proc/7951 -l(显示所查找进程路径软链接),进程所创建的文件就会在进程的执行目录下if(currPath) chdir(currPath);
}