Step1:
通过ps aux查看进程PID号,再进入/pro/PID号/fd文件夹下,使用ll查看进程的文件描述符。
0表示标准输入文件,1表示标准输出文件。
Step2:
传输方式:(一个是菜鸟驿站式,一个是面签式)
流格式套接字:
socket stream
使用TCP协议
面向连接:有特定的数据传输路线
按顺序传输,因为设有缓冲区,所以接收和发送可以不是同步的
数据报格式套接字:
socket dgram
使用UDP协议,多用于音频视频传输
无连接:无特定的路线,但是能到达目标
效率较高,接收与发送是同步的,当延迟比数据完整性更重要时使用
Step3:
流格式套接字:
在路由之间建立一条固定的信息传输通道,发送端发送数据包给接收端,等待接收端返回含有确认信息的数据包,得以发送下一个数据包,如果没有返回则再次发送。
无连接套接字:
在路由间存在许多条传输线路,数据包可通过任何一条传输线路到达接收端,由于没有确认和传输线路的不稳定,会导致数据包的丢失。
Step4:
TCP/IP模型:网络接口层、网络层、传输层、应用层
两台计算机通信必须在同一层次
每一层的功能必须相同
Step5:
一个局域网具有一个IP
一台电脑一个MAC地址(跟网卡关联)
每个网络程序具有独一无二的端口号
只知道IP和MAC可以把数据报发送给对应的目标电脑,但是无法进行通信,还需要端口号来确定所需要的网络服务
Step6:
源码:
服务器端:
首先用socket函数创建套接字
再创建一个包含服务器ip地址、端口号的结构体
通过bind绑定套接字与ip地址与端口
用listen表示让套接字休眠,直到客户端发起请求唤醒
accept接收客户端请求,感觉类似于回调函数中的ros::spin,执行到这就阻塞
客户端:
connect向服务器发送请求
read从套接字文件中读取数据
Step7:
函数解析
bind(int sock,struct sockaddr *addr,socklen_t addrlen):
sock:socket文件描述符,由socket()返回得到
addr:sockaddr结构体变量指针,输入为sockaddr_in结构体变量,需要强制转换为sockaddr,sockaddr结构体把sockaddr_in中的地址类型、ip地址、端口都合并到了一起
addrlen:为addr变量的大小,用sizeof可以得到
listen(int sock,int backlog):
sock:同上
backlog:请求队列最大长度,服务器正在处理当前客户端请求时,如果有新的请求进来,不会立即执行而是会进入请求队列中,当请求队列满了,客户端会收到ECONNREFUSED错误
accept(int sock,struct sockaddr *addr,socklen_t *addrlen):
第二个参数存放客户端ip+端口的地址信息
accept返回一个新的套接字来和客户端通信
write(int fd,const void *buf,size_t nbytes):
fd:为要写入的文件的描述府,由accept返回值提供
buf:为要写入数据的缓冲区地址
nbytes:要写入数据的字节数
read
同上
send(int sock,const char* buffer,int len,int flags):
sock:为accept返回的新套接字
buffer:需要发送的字符串数组的首地址
len:字符串数组的长度
flags:通常为0或者NULL
recv(int sock,char* buffer,int len,int flags):
同上
Step8:
可以直接通过加入ros相关函数来使得socket成为ROS中的一个节点
只需要在accept前(创建新的套接字)加while循环,服务器持续监听客户端的请求
Step9:
通过参考ros发送串口的节点,发现串口发送的函数写在回调函数中,这样能使传进来的数据在同一个数据块中发送出去。
在把socket通信模块放入回调函数中,但是发送和接受的数组不能继续放在main函数中,因此写了一个类,将数组写进私有变量,把回调函数作为成员函数,刚好解决了这个问题。用ros:spinOnce()而不用ros::spin()的原因是前者可以控制发送的周期,而后者不行。
发送的数组为char型,但是输入的pose为double型,需要转换。方案是先利用std::to_string(double)函数转化为string型,再在字符串的末尾加‘\n’,再用.c_str()将string型转化为char型数组,再用strcpy将数组拷贝。
Step10:
socket发送数据首先是服务器发送给输出缓冲区,然后是由输入缓冲区读入,最后才是客户端读取,输入输出缓冲区大小默认为8K
输出:
当输出的数据大于输出缓冲区剩余空间,send和recv会阻塞(暂停执行)直到缓冲区里的数据发送出去,有足够的空间,才会唤醒send/recv
输出的数据如果大于缓冲区的总长,则会分批发送
输入:
首先检查输入的缓冲区,如果缓冲区没有数据则阻塞运行
如果一次读取数据量小于输入缓冲区,则数据会在输入缓冲区内不断的积压直到recv再次读取
Step11:
TCP建立连接时需要传输三个数据包(三次握手)
第一次握手:
服务器先执行到listen并阻塞,客户端创建socket并执行connect,向服务器发送带有SYN同步信号的数据包,并且随机给数据包附上序号,之后客户端进入SYN-SEND状态
第二次握手:
服务器收到数据包,检测到是带有SYN标志的“请求包“,服务器建立一个新的数据包,并附上新的序号,数据包中由SYN与ACK,SYN用来建立连接,ACK用来确认请求,ACK=一个数据包的序号+1,之后服务器进入SYN-RECV状态
第三次握手:
客户端收到服务器发送的数据包,检测到SYN和ACK,判断ACK是否为上个数据包序号+1,如果是,则建立新的数据包,包含SYN和ACK,ACK为服务器发送过来的数据报序号+1,之后客户端状态为ESTABLISED,之后服务器接收到数据包,并检测ACK,如果符合,服务器的状态也会变成ESTABLISED
Step12:
普通传输过程:
客户端发出带有数据和序号的包给服务器,服务器接收到数据包的序号,返回一个带有ACK码的数据包,ACK=数据包的序列号+传递的字节数+1。以此类推。
如果两次传输之间超时,客户端会重新发送数据包。
Step13:
TCP断开连接时需要传输四个数据包(四次握手)
第一次握手:当客户端运行到close()时,向服务器发送FIN数据包,进入FIN_WAIT_1
第二次握手:服务器收到FIN数据包,服务器进入CLOSE_WAIT状态,并将带有ack的数据包发送给客户端,客户端收到ack的包后,经过校验,进入FIN_WAIT_2状态,继续等待
第三次握手:服务器准备好后,主动向客户端发送FIN包,客户端接收到后进入TIME_WAIT状态
第四次握手:客户端发送ack包,服务器接收到后断开tcp
实现ROS与服务器之间的socket通信学习笔记
最新推荐文章于 2024-06-17 17:50:50 发布