当前位置: 首页 > news >正文

TCP socket api详解

文章目录

    • netstat -nltp
    • accept
    • 简单客户端工具 telnet 指定服务连接
    • connect
    • 异常处理
      • version 1 单进程版
      • version 2 多进程版
      • version 3 -- 多线程版本
      • version 4 ---- 线程池版本
    • 应用-简单的翻译系统
    • 守护进程化
    • 服务器细节
      • write 返回值
    • 客户端

在这里插入图片描述

在这里插入图片描述
创建套接字socket + sockaddr_in结构体 + bind
之后就和UDP不一样了。
因为TCP是一个面向连接的服务器,在正式通信之前,他一定得随时随地等别人先和我连接上再说
也就是通信前得先建立连接,服务期就得一直想办法等别人来连。

第三个基本工作
listen
在这里插入图片描述

将套接字设置为监听状态,他才能随时听到有客户来连我了,然后我就可以帮我们把连接获取上来,然后基于连接再通信。
sockfd网络文件描述符
backlog 全连接队列 一般10不要设置的太大,后面讲tcp协议再谈。

netstat -nltp

n 显示数字
l listen状态
t tcp
p 进程
在这里插入图片描述
这里虽然tcpserver 绑定的是0.0.0.0 但是你客户端访问的时候是拿着公网ip去访问的,所以能找到。

accept

把服务器设置为listen状态完全不够,所以还要建立链接
在这里插入图片描述
返回值
成功,返回一个整数文件描述符,从你设置为监听状态的sockfd里面获取

所以tcp搞出多个文件描述符
在这里插入图片描述
accept返回又一个套接字,那我到底用哪个通信?

好再来鱼庄
一个门口的拉客服务员张三
张三不服务具体客人,他只是拉客
真正提供服务的是店内的李四

我们曾经的监听套接字,他的核心工作就是从底层把新连接获取上来
而提供IO数据通信服务的,是accept返回的fd。
监听套接字一般只有一个
accept返回值fd可以有多个

我们把经历过bind + listen的这个套接字 直接叫做listensock

在这里插入图片描述
如果listensock获取连接失败了,错误等级顶多也就是waning,然后直接continue继续获取连接
也就是张三拉客失败,你不去吃鱼,那 他就转头去找另一个小哥哥小姐姐问来不来吃鱼呀?
在这里插入图片描述
从上面到下面,我们讲的所有接口都是阻塞的。
也就是张三拉客路边根本没人,所以张三只能在那等着。
当然未来可以设置为非阻塞。

简单客户端工具 telnet 指定服务连接

127.0.0.1 本地换回 通过网络层从下往上 返回给本主机
telnet默认使用tcp协议
在这里插入图片描述
回车 ctrl+] quit
在这里插入图片描述

无论UDP and tcp ,云服务器的公网IP无法绑定进套接字

然后我就从accept返回的fd 通信呗
我还要获取从accpet返回给我哪一个客户端连接我的ip和端口信息
char ipstr[32]
4字节 转 点分十进制ip地址 需要多少空间?
点分十进制 四个区域,每个区域3个数字,就是12再加上三个点15,32是保守点
在这里插入图片描述
问题
不仅仅是IP和端口会发送到网络里,你做了大小端转化处理
那我们发过去的字符串消息 难道不做大小端转化吗?
也就是recvfrom sendto read write 这样的接口难道不用大小端转化吗
recvfrom sendto 读上来我也没见你转呀,最后通信也没问题
正常的通信内容,你所使用的接口会自动转化,只不过端口和ip很特殊,是要给OS的。
正常通信时不用操心大小端问题。

管道的读取 支持字节流能用read write
文件读取 也是支持字节流的 也直接read write
tcp 也是面向字节流的
所以读网络就像读取文件一样。
在这里插入图片描述
目前只处理正常的情况


tcp客户端

tcp客户端需不要bind?
一定要,但是不需要显示编码bind

为什么?
socket通信必须用ip+port 对于客户端来说保证唯一性就可以了,端口是几不重要。
由OS自动随机选择。

tcp这里什么时候自动绑定呢?

那我就打开网络文件描述符然后关闭它,在这之间我们tcp客户端需要干什么呢?
在这里插入图片描述

记住了,tcp是面向连接的,所以注定了客户端你得链接服务器,连接成功才能进行通信。客户端既然都不bind了,也就根本不存在listen,客户端也就不会让别人来连他,所以也就不会accept
客户端需要连别人,能向服务器发起连接的接口

connect

在这里插入图片描述
后两个参数和sendto是一毛一样的,意思是连接你是想向谁发起连接呢?
在这里插入图片描述
客户端发起connect的时候,进行自动随机bind
在这里插入图片描述
给谁发起连接,那就用命令行参数,你用户传我要连接哪个服务器ip+port
在这里插入图片描述
然后就是从文件描述符进行IO通信了
关于getline
会清空while循环外的string msg
在这里插入图片描述

异常处理

读写通信连接建立好,任何一方都可能出问题,服务器可能挂掉,客户端可能退出。
尤其对于服务器而言,如果客户端直接关闭自己连接怎么办?
客户端退了,服务端读取时会read返回0
返回0就跳出循环,service函数结束,关闭这个链接的描述符
在这里插入图片描述
read 返回值小于0 说明读取直接出错了 我们也让他break
在这里插入图片描述
而且获取新连接这件事是再死循环里面的,你关闭一个链接还能继续获取下一链接
在这里插入图片描述

如果服务器向已经关闭的套接字文件描述符写入时怎么办呢?
在这里插入图片描述
打游戏时,突然卡主不动了,当前你已掉线正在重连
这就是你的tcp客户端重新connnect
我们模拟一下


在这里插入图片描述
把早连上的客户端ctrl + c调之后下面的服务期就可以回显消息了。
在这里插入图片描述

为什么当前上面客户端可以发消息,下面的也不是发不了,只有上面退了下面才回显。

version 1 单进程版

这是因为目前为止我们是单进程版的服务,单进程版一旦进入service函数中只有给这个客户端服务完了执行流才会回到最开始,重新获取连接,重新进入服务函数提供服务。
在这里插入图片描述
在这里插入图片描述
你见过哪个服务器只能有一个客户服务,另一个客户你得等一等
这种服务器我们根本用不了。
就像餐厅里只有一张桌子。
所以今天

version 2 多进程版

每到一个新连接我们既想给新到的客户提供服务,提供服务期间还想让服务器继续处理其他对应的新连接,连接不断增多我也不怕。
在这里插入图片描述
fork创建子进程
子进程中执行服务函数,执行完了把fd关了,子进程也就退了
父进程呢就等待子进程,不等子进程会僵尸
这里有非常多的细节
文件描述符处理工作

  1. 文件描述符sockfd当前是不是父进程打开的文件描述符,会不会被子进程继承下去呢? 会的,父进程打开sockfd子进程能看到,子进程会和父进程虽然有两个不同的文件描述符表,子进程拷贝父进程Fd表,而子进程能看到父进程的sockfd,他连listensock也能看到,所以子进程不需要listensock直接关掉
    在这里插入图片描述
    在这里插入图片描述
    2. 先不考虑waitpid等待,因为这本身就不对 
    父进程他获取了新的套接字,他就回过头继续获取新连接了,他会重新获取文件描述符 ,可是你刚刚打开的sockfd已经被子进程继承下去了,这个sockfd指向打开的网络文件,那父进程还需要关心这个sockfd吗?
    相当于子进程和父进程看到的文件和Fd表都是一样的,只不过大家有不同的文件描述符表,此时子进程不关心listensock,他就关掉了。
    答:
    对父进程来讲,已经获取的新连接sockfd已经被子进程继承下去了,他就不需要了。
    父进程就把Sockfd关掉了。
    如果他不关,最后OS就可能有很多文件没有被关闭,最后就导致一些问题。
    .这和之前管道问题也是类似的。在这里插入图片描述
    在这里插入图片描述
    父进程每创建一个子进程就把sockfd交给子进程,由子进程全全负责给这个套接字提供服务。
    对于父进程必须要关掉曾经打开的fd,因为已经被子进程拿到了,不关导致父进程可用的Fd越用越少。

父进程把这个文件sockfd关闭了不会出问题吗,这个文件不会关闭吗?
父进程把他关了,这个文件不会被释放,因为文件struct file中包含引用计数,子进程还指向他呢。

我发现waitpid 为0 他是阻塞等待,子进程确实跑去执行服务了,可是父进程不是还在这里等待 吗,等你子进程执行完了退出了,我再继续获取连接accept。
这不和刚才单进程一样吗。
我想让父进程回到accept继续获取新连接,子进程继续服务,两个并发跑起来。
阻塞等待断然不能满足要求。
那我们把等待设置为非阻塞轮询,那也有点扯,非阻塞也可以,但不好
包括子进程退出会给父进程发信号,我们把等待工作放到信号处理中,这个也不想写。
所以两种做法。

  1. 子进程里面又继续fork创建了孙子进程,if(fork() >0) exit(0) 也就是子进程自己直接就退了,真正提供服务的是孙子进程
    因为子进程一启动创建立马退出,所以waitpid立马返回
    孙子进程提供服务的时候,waitpid已经回收了子进程的僵尸,然后waitpid返回直接继续accept
    有人就说,我们要不要等孙子进程呢?
    不用,你只要管好你儿子就行了。你儿子已经退出并且终止了,那你waitpid直接返回。
    所以孙子进程对他来讲,他的父进程直接就挂掉了,所以孙子进程会被系统领养,系统领养之后跟你有什么关系呢?
    孙子进程一旦把任务执行完了,这个孙子进程的资源就会被系统自动回收。
    在这份代码中,waitpid也要做,不然就会出现很多僵尸。

在这里插入图片描述
现象
我们每个人拿到的文件描述符都是4,这也很正常,因为3是listensock,获取一个新连接,就是4
你又关了然后继续获取还是4
我们能看到出了主进程其余都的子进程pid都是1,说明是孤儿进程。也就是被OS领养
在这里插入图片描述

  1. 其实我们可以不用等的,我们可以用signal将SIGCHLD的处理动作
    置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。
    在这里插入图片描述

客户来了新连接,我才忙着创建子进程呢这是其一

第二,来了连接,子进程会变得越来越多,我们其实比较难对他进行处理的。
我们也可以提前创建一批进程,但是进程池我们不谈,创建进程成本非常高,创建进程,创建地址空间,pcb,维护页表,初始化各种数据,还要做一大堆各种工作,创建进程成本很高,所以我们不愿意这样做。
这样多进程版的服务器也就满足小型应用。我们一核2G云服务顶天跑20个顶天了。所以我们不可能用这样的代码。我们不想使用这种版本。

version 3 – 多线程版本

在这里插入图片描述
创建线程,那我依然要等待新线程,还是在这里卡主,那accept也没有并发运行了。
此时新线程直接线程分离。因为主线程一直要获取新连接,创建出线程就不管了,让新线程跑去执行任务处理
在这里插入图片描述
第二个问题,
进程打开的所有文件描述符表,其他新创建出的线程能看见我曾经打开的listenscok,主线程获取到文件描述符新线程最终能不能通过文件描述符直接找到对应的文件呢?换句话说在线程这里要不要关闭不需要的文件描述符?

在线程当中,如果线程所在进程打开了一个文件,只要这个线程拿到对应的文件描述符,该线程直接可以访问这个文件,另外多线程这里完全不需要让多线程在关闭多余的文件描述符,因为他没有多余的。

答案是多线程这里不能关,一关就出大事了,线程大部分资源共享,比如文件描述符表。

为了让新线程提供服务,你要传文件描述符,你还需要客户端ip+port字段
那就打个结构体吧!
在这里插入图片描述
在这里插入图片描述
未来新线程负责套接字的整个生命周期
那Routine这样写能编过吗?
在这里插入图片描述
在这里插入图片描述

当前Routine必须是static的,因为类内函数自带的this指针与线程pthread_create的第三个形参类型不匹配
静态方法无法使用类内非static成员方法,也没有办法访问类内成员,因为他没有this指针。
那怎么办?
其实service函数和类内成员一点关系没有,你直接放到类外一样跑。
在这里插入图片描述

今天不想这么干
所以我们要把this指针传给新线程。
在这里插入图片描述
在这里插入图片描述
用this指针就可以访问类内非静态方法 了。
在这里插入图片描述
他比多进程好,创建的资源少。
可是每来一个客户都要创建新线程,那不会出问题吗?
客户不退,线程就越来越多,一般服务不是死循环,所以系统中不会维护太多线程,但是万一峰值情况,线程就会非常多,所以多线程版本也不适合应用于较大的应用。

来一个连接才给我创建线程,这也要花空间时间,我们这种情况登上就不退了,服务期就想办法提供一次服务。

所以

  1. 我们想预先创建好所有线程
  2. 我们不想提供长服务,否则提供短服务。
  3. 我们来了200个客户,每个客户都需要文件描述符,难道一定要有200个线程吗? 能不能限定线程个数的上限。
    每个用户访问,把访问变成对应任务,把任务交给后端。

version 4 ---- 线程池版本

在这里插入图片描述
在这里插入图片描述

先构建任务,什么任务呢?
你得需要IO通信吧,那就需要文件描述符,连接你的客户端的ip和端口, 主线程把这个任务push给线程池
push完之后呢,你就别管了,这个任务后端线程池自动处理,你的 主线程只需要回到最开始,继续accept.

别忘记服务器开始时,启动这个线程池,不要套进循环了
在这里插入图片描述

接下来的任务就是把通信service改成短时通信一次性通信的task类封装好

service本来就是可以放在类外的函数,直接放到task类里面的run方法里
在这里插入图片描述
在这里插入图片描述
读数据我只想给你返回一次,当然也可以构建任务再设置一些回调方法,让处理任务按照我们预期处理
在这里插入图片描述
今天就不倒来倒去的,今天继续是个简单echo服务器,但不想是个长服务,因为长服务在线程池里跑的话是不合理的,因为线程池里线程个数是确定的。
所以改成你只能访问一次,你给我发个消息我就给你结果
短任务
在这里插入图片描述
线程池里面有任务就拿,没任务就休眠,拿到了就处理任务
在这里插入图片描述
至此呢让线程池就把任务处理起来了。
在这里插入图片描述
task任务里的run方法 这次服务服务完了就算完了,就得把文件描述符关了,不关就会卡在这里,最终我们想让它直接就退出的。

在这里插入图片描述
close之后如图,相当于向服务器发起请求,服务器把结果给你,连接关闭,理解成线程池专门给统一处理任务。
在这里插入图片描述

应用-简单的翻译系统

我们的翻译系统就是打开一个文件,这个文件是如此KV结构
弄一个新的任务类init,红色是C++打开文件流和关闭流
黑色是getline按行读取,从定义的流in里面读,split是封装函数把这个kv切开,然后把part1 和part2放进map里,方便我们查询翻译
getline套循环是什么意思?
其实是返回布尔类型去判断的,它内部对强制类型转化符做运算符重载,如果getline读取字符到文件结束了,它直接返回空,循环就结束了。
在这里插入图片描述
.分割是如此分割的,基本功在这里插入图片描述
这样的话把Init对象放到task类里面,虽然这里不太好,但是先这样放
这个对象一构造,KV的map也就好了
在这里插入图片描述
在Init里面还要有一个翻译的方法,方便run方法调用 用key查Value
在这里插入图片描述
就是通过map的接口一查
在这里插入图片描述

我们用telnet去输入apple 翻译不出来苹果
telnet的行分隔符会导致我们的翻译查不出来,行分隔符不一定是一个还有可能是两个。
在这里插入图片描述
所以要把客户端发来的数据去掉分隔符 buffer [n-2]
在这里插入图片描述
翻译成功
在这里插入图片描述
翻译就是用一次给你一次,相当于有在线翻译了。

守护进程化

服务器细节

write 返回值

写了多少字节返回值就是几,失败了-1被返回,错误码被设置。
在这里插入图片描述
我们这里做差错处理就可以,往fd=100不存的文件描述符写让他报错
在这里插入图片描述

但如果你刚把数据读上来(read刚刚结束,write还没开始),客户端就把连接关闭了,此时我在向这个文件写write会怎么样?
在这里插入图片描述

我们就想知道向一个已经断开的连接里写入会出什么问题
于是在中间close(sockfd)
或者不加close就让客户端直接退了,但是实验现象还是没做出来,现象就是卡住了
在这里插入图片描述
结论
TCP协议,偶发会出现,当我们向一个底层连接被双方释放掉的文件描述符写入的话,程序就好比管道进程通信,读端关闭,写端继续写的话当前进程就会收到SIGPIPE信号,OS直接干掉写端进程。

这种情况在网络里也可能存在,所以为了服务器安全你不仅要处理读(read),你也要处理写,所以写write 的 差错处理你做了
这个没问题。
可是wrtie写时也有可能出现写崩溃问题,所以代码层面上还要再加一个细节。
让服务器启动的时候,进行signal(SIGPIPE,SIG_IGN);
在这里插入图片描述
为了防止出现服务期向一个已经被关闭的文件描述符写入时,此时就像管道读端被关闭,写端继续写,连接也就没有意义了,OS就会直接把你的进程SIGPIPE 杀掉。

所以服务器建议

  1. 忽略SIGPIPE
  2. 对write进行差错处理,虽然什么都没做但是你得打出错误消息
    在这里插入图片描述

客户端

在这里插入图片描述
简单翻译系统情况下,进程池提供短时服务,翻译服务完成直接就关闭文件描述符,连接也就断了,客户端这边也就卡住了
今天程序已经变了,把代码再改改,服务器每次服务完把连接就断了,所以我把循环提到connect上面,相当于每次进行翻译时都要重新建立链接,因为服务器只会给我提供一次服务。

在客户端write最后也做一次判断
在这里插入图片描述
在这里插入图片描述
我们完成了一次请求,可是第二次connect失败了,主要原因是,我们发起一次请求后,对方把我们对应整个的套接字关掉了,你下次再拿这个套接字的话,当前客户端对应的资源也已经被释放掉了,发起连接时会创建套接字,创建套接字时需要对信息重新绑定,你可以理解为这个文件sockfd也被对方释放掉了。
在这里插入图片描述
所以要没问题,还要继续把while循环往上提,提到socket调用之前
在这里插入图片描述
此时每一次服务器把连接关掉完成一次服务,重新创建套接字,重新发起连接,填充sockaddr_in结构体字段可以不用每次循环都填入,放到循环外面,因为里面只有类型,ip,端口,都是循环外可以做的。
在这里插入图片描述
此时客户端就可以不断的发起请求

重连过程中你得知道是网络出问题了,你得先让他写出错,或读出错,说明网络出问题了,所以交给上面让上面去重连
在这里插入图片描述
客户端read之前,如果服务器挂掉,那么客户端read也就读到0了。

读端如果没了,写端就会失败
客户端write之前,如果服务器挂掉,write返回小于0,但这个现象在客户端没实验出来。待定
不过没关系,反正连接断开,最后都会反映到connect返回值失败的差错判断里
在这里插入图片描述

后面就是常规提供服务过程,最开始加了重连模块,正常情况下套接字被创建,连接发起,都没问题,cnt 和 isreconnect只有后者是falsed ,所以直接继续往后走提供服务,但如果重连时,创建套接字这个在你本地工作肯定能成功,但是connnect总是连不上,那就要让他do while再去connect。
socket文件的处理放到do while外面,只需要处理connect。
如果重连五次还是不对,循环自动结束,客户端也就离线了。
在这里插入图片描述
在这里插入图片描述
就像我们打游戏要重连,这个重连工作是由客户端来做的。
下面这个服务可以改为长时服务,循环里面再套循环,有的服务就是长连接。
服务里面的read 和 write 失败了就要break,我们就认为是异常退出了 ,此时就重连了。
在这里插入图片描述


http://www.mrgr.cn/news/77918.html

相关文章:

  • 基于企业微信客户端设计一个文件下载与预览系统
  • Ubuntu上安装MySQL并且实现远程登录
  • 【Spring MVC】初步了解Spring MVC的基本概念与如何与浏览器建立连接
  • 【2024APMCM亚太赛A题】完整参考论文与代码分享
  • “AI玩手机”原理揭秘:大模型驱动的移动端GUI智能体
  • springboot/ssm考试系统Java学生在线考试系统web学习论坛源码
  • Android 常用命令和工具解析之GPU相关
  • 数字信号处理(Digital Signal Procession)总结
  • 从搭建uni-app+vue3工程开始
  • Linux高阶——1117—TCP客户端服务端
  • HarmonyOS:使用ArkWeb构建页面
  • 工具学习_Docker
  • 用Tauri框架构建跨平台桌面应用:1、Tauri快速开始
  • 学习python的第十三天之函数——函数的返回值
  • 如何使用docker、docker挂载数据,以及让docker使用宿主机器的GPU环境 + docker重启小妙招
  • 华为云鸿蒙应用入门级开发者认证考试题库(理论题和实验题)
  • 论文阅读——Intrusion detection systems using longshort‑term memory (LSTM)
  • 阅读《先进引信技术的发展与展望》识别和控制部分_笔记
  • Glide源码学习
  • 【AI技术赋能有限元分析应用实践】将FEniCS 软件安装在Ubuntu22.04
  • 预训练模型与ChatGPT:自然语言处理的革新与前景
  • 【2024 Optimal Control 16-745】Ubuntu22.04 安装Julia
  • Edify 3D: Scalable High-Quality 3D Asset Generation 论文解读
  • 网络(TCP)
  • 项目实战:基于Vue3实现一个小相册
  • _FYAW智能显示控制仪表的简单使用_串口通信