0
点赞
收藏
分享

微信扫一扫

TCP_Socket

本篇文章,继续和大家分享与网络相关的知识。本次的内容主要会涉及到TCP客户端的模拟实现,TCP服务器的模拟实现和守护进程等相关知识。

服务器

tcp和udp创建套接字,bind的步骤是一样的。

makefile

TCP_Socket_守护进程

Main.cc

TCP_Socket_守护进程_02

TcpServer.hpp

TCP_Socket_TCP客户端_03

TCP是面向连接的服务器,在正式通信之前,一定得随时随地的等待别人连接。也就是说通信之前,得先建立连接。

如果一直没有人连接,TCP服务器就会一直处于等待的状态,这种状态,我们称之为listen状态

进入listen状态需要使用函数listen函数。

listen函数

TCP_Socket_守护进程_04

listen函数的第一个参数是套接字,第二个参数表示全连接的长度,这个后面解释。

TCP_Socket_TCP服务端_05

有连接来了,我们怎么获取,需要使用函数accept。

accept函数

TCP_Socket_TCP客户端_06

accept函数的返回值是一个文件描述符。它的第一个参数是套接字,后面两个参数用于了解是谁连接了我。

我们把Start函数完善一下:

这里多了一个套接字,为了便于区分,把原来类内的套接字改成listensock_。

TCP_Socket_TCP服务端_07

为什么TCP这里多了一个套接字呢?通信的时候,为什么用这个而不是那个?

讲个小故事,你就明白了。一个餐厅里,有一个店员叫张三,一个李四。张三负责在餐厅外揽客。揽到客人后,就引到餐厅里,交由李四来协助点菜,自己则继续到餐厅外揽客。

监听用的套接字,就相当于张三,只负责监听,不提供其他服务。而accept返回的套接字则负责服务。

我们现在还没有写客户端,怎么测试服务器呢?可以使用telnet进行指定服务的远程登录,说是登录,其实就是连接。

TCP_Socket_守护进程_08

TCP这里能不能直接绑定云服务器公网IP呢?不能。

我们可以简单提供一个Service服务,进行测试:

由于read和write是面向字节流的,所以TCP通信不需要另外设置接口,直接用write和read就可以了。网络通信,规定使用大端。为什么发过去的消息,不需要转网络序列呢?因为你使用的接口,或默认把,套接字正常通信的内容,由主机序列转成网络序列。

TCP_Socket_TCP客户端_09

编译运行,使用telnet指令进行连接,就可以进行通信了。

TCP_Socket_TCP客户端_10

客户端

客户端要获取服务端的服务,首先得建立连接。怎么建立连接?使用connect函数。

connect函数

TCP_Socket_TCP客户端_11

connect的第一个参数是套接字,后两个参数说明向那个服务器获取服务。

TcpClient.cc

TCP_Socket_TCP服务端_12

改善后的服务器:

TcpServer.hpp

TCP_Socket_守护进程_13

编译运行,就能让客户端和服务端通信起来了。

TCP_Socket_TCP服务端_14

改善服务器

当我们打开两个客户端同时请求服务时,就会发现,只有一个客户端能获取服务。

TCP_Socket_TCP客户端_15

这是什么原因?这是因为我们的Start是单进程版的。

TCP_Socket_守护进程_16

我们改成多进程版的,才能进行通信处理多个连接。

像下面这样改可以吗?不可以,主进程在等待进程的时候,会阻塞住。这不还是只能,处理一个服务吗?那怎么办呢?一种方式是采用捕获信号的方式回收。

TCP_Socket_守护进程_17

 另一种方式是,让孙子进程提供服务。这种方式,子进程创建孙子进程后,马上退出被回收,主进程就不会阻塞住了。而孙子进程变成孤儿进程被领养,会自动回收。

TCP_Socket_TCP客户端_18

编译运行,就能同时进行多个服务了。

TCP_Socket_守护进程_19

上面这种方式,我们把监听套接字关闭了,会不会有问题?不会

TCP_Socket_TCP服务端_20

创建一个进程的成本太高了,我们可以采用成本较小的线程。

TCP_Socket_TCP服务端_21

TCP_Socket_TCP服务端_22

TCP_Socket_TCP服务端_23

编译运行,我们就能进行通信了。

TCP_Socket_TCP客户端_24

说到线程,还记得我们之前做的线程池吗?

我们还可以使用线程池来提供服务,由于线程池的线程数量有限,我们只能提供短连接。

TcpServer.hpp

TCP_Socket_守护进程_25

Task.hpp

TCP_Socket_守护进程_26

ThreadPool.

TCP_Socket_TCP服务端_27

编译运行,通信一次,服务就停止了。

TCP_Socket_TCP客户端_28

简单的翻译

我们可以基于刚刚线程池版的服务,做一个简单的翻译功能。

一,先写一个文本。

TCP_Socket_守护进程_29

在写一个加载字典的头文件Init.hpp

TCP_Socket_守护进程_30

Task.hpp

TCP_Socket_守护进程_31

编译运行,就能使用客户端获取翻译服务了。

TCP_Socket_TCP服务端_32

读端问题

write函数,也会有出错的时候,我们也要对write函数进行出错处理。

TCP_Socket_守护进程_33

我们这里可以故意向100号文件写入。

TCP_Socket_TCP服务端_34

100号文件不存在,write出错返回了,符合预期。

TCP_Socket_TCP服务端_35

还有一种情况,如果读端关掉了,写端向管道里写。OS就会发送sigpipe信号把写端杀掉。

TCP_Socket_TCP客户端_36

如果我们不想因为这种异常终止服务器,可以忽略此信号。

TCP_Socket_TCP服务端_37

我们平时玩游戏的时候,如果遇到网络不好,就会掉线,然后需要掉线重连。掉线重连是怎么做到的呢

下面,我们给客户端增加一个掉线重连功能。

TcpClient.cc

TCP_Socket_守护进程_38

编译运行,在客户端连上服务器后,断连,就会进行重连了。

TCP_Socket_TCP客户端_39

服务端终止后,无法立即重新启动,如果需要立即重新启动,需要用到函数setsockopt。

TCP_Socket_TCP客户端_40

这里暂时先使用,后面再讲解。

TCP_Socket_TCP客户端_41

编译运行,让客户端连上服务端后,终止服务端,再重启服务端,就能看到断线重连的过程了。

TCP_Socket_守护进程_42

守护进程

我们使用xshell进行登录。每次用户登录,就会创建一个会话框,称为session。一个会话框只有一个前台进程,但有多个后台进程。

什么是前台进程,什么又是后台进程呢?谁拥有键盘文件,谁就是前台进程。前台进程,会一直从标准输入获取数据,而后台进程不行。

TCP_Socket_守护进程_43

那怎么创建后台进程呢?运行程序的时候,再其后面加上取地址符号。

我们可以使用jobs指令查看后台任务,成列出来的信息中,最前面的数字叫任务号

TCP_Socket_TCP客户端_44

我们可以是fg+任务号的方式,将后台任务提到前台。

fg+任务号

后台任务向显示器输出信息,并不会影响指令的正常接受,只不过回显的时候是乱序而已。将后台任务提到前台后,就可以使用Ctrl+C的方式终止

TCP_Socket_守护进程_45

如果我们使用Ctrl+Z将一个前台进程暂停了,前台进程会自动放到后台。为什么会自动呢?因为命令行中,要一直存在前台进程,接受键盘的数据,实施对会话框的操作。

[common_108@iZj6cdwczct2c4sl0aulbpZ ~]$ bg 1

我们可以使用bg+任务号指令,将暂停的程序唤醒。

TCP_Socket_TCP客户端_46

这里我们启动两个后台任务。一个是./process,另一个是sleep 1000 | sleep 2000 | sleep 3000。

TCP_Socket_TCP客户端_47

启动任务后,可以使用如下指令查看:

[common_108@iZj6cdwczct2c4sl0aulbpZ ~]$ ps ajx | head -1 && ps ajx | grep -Ei 'process|sleep'

TCP_Socket_TCP客户端_48

PGID是进程组ID的意思,单独启动的程序会自成一个进程组。每个进程组会有一个组长。如果一个进程组只有一个进程,那它就是这个组的组长。如果一个组里有多个进程,这些进程中,第一个启动的就是组长。

进程关系,不仅可以是进程独立,还可以是进程之间构成一组。

进程组和任务有什么关系呢?以前的进程就是任务。一个任务可以指派给一个进程或者多个进程完成。

不要叫前台进程和后台进程,更喜欢叫前台任务和后台任务

多个任务在同一个session内启动的sid是一样的。

TCP_Socket_守护进程_49

我们打开两个会话窗口后,使用如下指令查看,就会发现它们两个的SID是不同的。

[common_108@iZj6cdwczct2c4sl0aulbpZ ~]$ ps ajx | head -1 && ps ajx | grep -E '\-bash$'

TCP_Socket_TCP客户端_50

我们关掉xshell,重新登录,会发现后台进程不会关掉,但会收到用户登陆和退出的影响。

TCP_Socket_TCP服务端_51

如果不想受到任何用户登录和注销的影响,就需要守护进程化。我们把自成进程组、自成会话的进程,称之为守护进程

注销就是把整个会话全部关掉,包括后台任务。

TCP_Socket_守护进程_52

如何变成守护进程?

将一个进程变成守护进程,需要使用函数setsid,让进程组ID变成会话ID。调用这个函数的进程,不能是组长。

TCP_Socket_TCP客户端_53

启动一个程序,自己就是组长,怎么让自己不是组长?让自己不是第一个进程,就不是组长了。代码上怎么操作?很简单,创建一个子进程,让子进程继续往后执行,而父进程终止退出。父进程终止退出,那子进程不就变成了孤儿进程了吗?是的,所以守护进程的本质是孤儿进程。

TCP_Socket_TCP客户端_54

我们这里将守护进程化,封装成一个函数,这样调用它的进程,就会变成守护进程。

守护进程化后,进程是一直运行在后台的,我们不需要信息向屏幕打印。

有一个文件叫/dev/null

TCP_Socket_TCP客户端_55

写入/dev/null中的内容,会被自动丢弃。

Daemon.hpp

TCP_Socket_TCP客户端_56

TcpServer.hpp

TCP_Socket_TCP服务端_57

启动服务器后,退出登陆。

TCP_Socket_守护进程_58

再次登录xshell,我们依然能获取服务。

TCP_Socket_守护进程_59

我之所以xshell登录Linux,是因为OS默认会启动一个端口为22的服务。通常守护进程都是以d结尾的。

TCP_Socket_TCP客户端_60

其实,守护进程化有现成的函数。

TCP_Socket_TCP客户端_61

TCP建立连接的过程会进行三次握手,释放连接的时候,会进行四次挥手。

TCP_Socket_TCP服务端_62

TCP通信时全双工的,发送和接受使用的是两个不同的缓冲区。

TCP_Socket_守护进程_63

好了,到这里,我们本次的分享就到此结束了,不知道我有没有说明白,给予你一点点收获。关于更多和网络相关的知识,会在后面的文章更新。如果你有所收获,别忘了给我点个赞,这是对我最好的回馈,当然你也可以在评论发表一下你的收获和心得,亦或者指出我的不足之处。如果喜欢我的分享,别忘了给我点关注噢。

举报

相关推荐

0 条评论