TCP编程和UDP编程,套路不一样,
UDP是无连接协议,不用关心,发数据的对端是否存在,数据发出去即可,编程模型比较简单,作为服务器端一般是要固定在一个端口上,,这样别人才能连接到,UDP和TCP的服务都需要一个端口绑定
TCP要多一个listen监听端口,UDP绑定之后直接就可以了
下面就是收发的过程:
TCP比较麻烦,每链接一次,就要借助一个ACCEPT,链接成功后,建立一个有链接的链接通道。
TCP每开一个链接就会创建一个新的socket。建立连接后,TCP就可以通过新的socket通讯,
UDP不需要维护一个链接,不需要新的socket(用同一个socket来回收发数据,开几个socket,关几个socket)
scocket内部维护了一些数据,laddr本地,raddr远程,从A发到B,就需要自己有个端口才能向对方发数据,没有端口是不能发送数据的,首先要在某个协议上占一个端口,然后才能发送数据,对端的地址和端口也需要知道,一个数据包必须有源IP和目标IP用来做路由,应用层就需要知道端口,否则不能在应用层进行通讯
TCP协议知道两端IP和端口,
UDP是不知道的,
bind是给自己的应用程序绑定地址和端口
connect链接,填写两个值,本地laddr,raddr、
sendto,会把自己地址端口填充,远端不记住
send,(如果前面没有laddr,连这个都过不了)自己没绑定laddr是发送不出去数据的
recv 一定要有本地端口laddr
UDP多个客户端现在来通讯没什么问题
初始化后,就可以进行bind绑定,阻塞开启一个线程recv
等待客户端发来数据,每发来数据,就认为是跟自己链接的客户端,用set把客户端记录,然后去重(因为客户端重复发送数据,每次记录不合适)
记录地址后,遍历地址发送消息,但是不保证对方能收到
如果友好一点,客户端主动告诉下线了,这里加个判断,然后remove元素,(元素就是raddr)不能break,因为还是这个socket在使用,只能continue
当对方发主动退出信息,就把对方从set中remove
TCP需要加锁,每一个socket recv扔到一个线程中执行,在其他线程群发的时候,另一个线程可能就退出了,这时候就会出现问题,因为同一个字典要变化长度,正在遍历就不允许,所以要加锁
UDP不管连接几个客户端。,都是一个线程,所以客户端都在一个线程里操作,除了主线程,其他只有一个recv线程,不需要加锁
如果调皮,进来每直接给信息,直接quit,在set里就找不到raddr
做一个keyerror判断即可
写一个客户端,connect就知道对端地址,stop清理资源
还需要造一个send方法发送数据。,凑个时间发送过去
recv和recv from别人也是可以给你发送消息的,对客户端来讲要临时占用端口
recv阻塞的较多,还是开启线程,connect之后,laddr和raddr都有,下面开启recv线程,阻塞在等待别人发送消息
TCP是要建立链接才能发送数据,但是UDP可以不这么做
先启动一个server
准备和客户端开始连接
收到了
现在laddr是58122
发送一个hello ack
这里就收到了
使用自己的server端代码试试,现在有两个线程
客户端起来先发个hello
服务器收到消息就回复了
客户端再发送消息
服务器端收到了
再启动一个客户端,发送22222
新起来的客户端在51165上
发送quit
服务器端没有写接收quit的回应消息,但是确实退出了
下次作为类,这一段代码导入的时候就不执行,测试代码都这么写
服务器端加个输出信息
运行服务器端,
运行客户端
客户端发送消息,都分别受到了信息
这样是同一个进程跑两个客户端对象
这样run是两个进程,两个进程两个客户端
右键运行客户端要理解,是一个进程多个线程还是多个进程多个线程,socket如何去跑的是要清楚的
现在不给数据就是永久阻塞,一个是悄悄给自己发一个,第二种是变成非阻塞,但是代码就不是这么写了
一般不会用socket来写,因为太底层了,后面开发用高级库了
遗留了问题,友好告诉服务器端quit,(TCP要是断了链接就可以感觉到,是协议保证的),UDP是一种无连接协议,必须要一种机制知道谁下线了,心跳机制
心跳机制只是发个包过去,还有ACK机制(响应机制),心跳是定时发的包,从一端发往另外一端,心跳包没有大的,都是很小的,能少一个字节就是一个字节,间隔看具体情况,你能容忍多久不在线合适
ACK,一端收到另一端消息,回复收到了(TCP和UDP都用,)
(TCP链接有个通病,长时间不管,隔一段时间就不工作了,想用这个链接就不能工作了,保持活性,就发心跳包)
心跳机制:
1.客户端定时向服务器发,服务器可以ack,也可以不ack(大部分采用)
2.服务器定时向客户端发,客户端一般需要ack,如果没有ack,就认为掉线了,(ack需要一个次数,比如5次)使用比较少
3.可以双向发心跳包,使用很少
采用第一种心跳机制改进代码
现在定义一个服务端收到心跳包的代码
需要记录时间
现在收到一个心跳包,包来了记录一下直接跳过,记录最后一次与服务器连接时间,
假如没有发心跳包,设定间隔时间
假如有2W个客户端,只有2个下线了,聊天工具往往是一个慢的程序,不是一个高效的网络传输程序,
懒的策略是,在发送的时候,顺道遍历的过程中把已经超时的清理掉。
所以准备在当前线程中,发送消息的时候,顺带把超时的清理掉
raddr是key,ts是时间戳,当前时间-时间戳(时间戳是从unix元年1970年开始)=浮点数>阈值
大于时间阈值
大于就等于对方下线了,下面遍历把超时的踢出去即可
客户端一开始就发了心跳包,这样一开始记录的是通讯时间,说明这个客户端活着
服务器端代码主要是在recv解决
服务器代码运行
消息是从端口10000发来的
敲一个回车,只剩10001
10000的记录就被清除了
发心跳包的目的就是,维护一个客户端有效的列表
现在开始客户端
客户端一旦工作,就需要向服务器端汇报,也是起线程
现在要完成这个heartbeat函数
主线程退出,心跳包也退出,daemon,recv的话需要等别人返回所以none-daemon比较好
客户端发送心跳,服务端只是记录列表,没有ack,没有alive的就被删除
加心跳需要记录,把原来的set,变成字典,用字典来记录每一个客户端,最后一次发数据的时间,记录这个时间,
最后和当前时间的差和阈值做比较
心跳一般都是daemon
主动退出
有链接TCP,无连接udp,
UDP设计的时候是基于,网络好,消息不丢包,包没乱序,
但是在广域网的情况下,这个情况就有一些问题了,丢包可能比较严重
就可以在应用层的时候,校验一下,发送的数据是否正确,CRC校验,奇偶校验
UDP应用就可以用视频,音频
有些传感器是微秒级的传输数据,比如桥梁震动,UDP协议来传输就比较好
DNS协议,把域名转换成IP地址,DNS协议用UDP协议访问,53端口,你发送的包和回来的包,都是一个包,不会乱序的问题,就采用了UDP,发过去丢了,再发即可
UDP性能优于TCP,可靠性高的还需要选择TCP
TCP和UDP,面试问该怎么用?
UDP在局域网用的时候问题不大,但是有些问题,效率高,但是有可能丢包,乱序,对方挂了也不知道,如果在这种情况下,要保证数据可靠性就可以用TCP
TCP有链接协议,但是成本比较高,(可以尽量维持这个链接,使用心跳)