以下示例来自CANoe DEMO,路径为C:\Users\Public\Documents\Vector\CANoe\Sample Configurations 12.0.101\IO_HIL\TCP_IP
Server TCP
由于TCP连接是服务器的tcp socket在listen状态下接受连接请求,从而完成三次握手操作
所以,首先服务器应该先创建好tcp socket后,绑定本地端口,然后处于监听状态,这样才能随时接受客户端的连接请求
CANoe DEMO里关于TCP连接通信是在面板上完成的
这个面板有TCP和UDP两种通信,UDP通信不需要建立连接,比较简单,这里就不分析了
server端tcp首先需要填入本地的IP地址和监听的端口号,然后通过点击“Start listening”,完成从创建tcp socket到绑定、监听和接受连接请求的一系列动作
我们看看是如何用capl实现的
Server端创建tcp套接字,绑定、监听并接受连接请求
- 点击按钮“Start listening”
将触发这个按钮关联的系统变量的回调函数的执行
- 按下去,会执行里面的函数StartListenTcp
可以看出,端口号用的是面板上的,IP地址却没有使用面板上的,选择IP地址变量gIpAddress,跳到定义的地址,发现它定义时是INVALID_IP,0xffffffff
定义IP地址变量时赋值为无效值的好处是,这样使用时可以通过判定是不是无效值来判断是否操作成功,同样的还有socket句柄,定义时先赋值为无效套接字,然后在创建后,如果还是无效值,就说明没有创建成功
这里IP地址被赋值无效IP,然后又没有使用面板上的值,就拿来创建tcp socket,肯定是不对的
所以肯定是在我们点击按钮之前,capl已经实现了读取正确的IP地址了
从CANoe运行到点击按钮,只有on start回调函数里有实现的可能
CANoe运行时会执行函数SetupIp
可以看出,IP地址是从网卡上自动获取的,然后显示到面板上,这些是在CANoe运行起来的时候就完成的操作
然后我们回到上面的函数StartListenTcp,有了IP和Port,接着就是创建tcp socket,调用TcpListen,让主动套接字转为被动套接字,监听外部的连接请求
- 触发回调函数OnTcpListen
如果有连接请求,虽然监听到了,但是没有接受的动作啊,就会阻塞在那
所以我们应该调用TcpAccept函数来接受连接请求,那为什么不在上面的TcpListen后直接调用TcpAccept呢
因为这样只能接受一个连接请求,而对于服务器来说,套接字是处于监听状态下,我希望的是过来一个连接请求,我就接受一个,可以实现接受多个连接请求,也就是多次调用TcpAccpet
我们知道python可以通过while True的循环来不停地接受多个连接请求,而capl则是通过回调函数OnTcpListen
OnTcpListen,回调函数,在调用TcpListen函数后,接收到客户端的连接请求时,就会调用它
这就意味着,服务器的tcp socket在listen以后,只要监听到有连接请求,就会自动触发OnTcpListen的执行
那我们就可以在OnTcpListen里实现接受连接请求的操作,也就是调用TcpAccept函数
Server端tcp建立连接后自动接收数据
这里采用的是调用TcpAccept函数成功后,跟着调用了TcpReceive。相当于是tcp建立连接后就开始不断地接收数据
- 调用函数TcpRecv
- 触发回调函数OnTcpReceive
虽然上面调用了一次TcpReceive函数,但是数据只能接收一次。我们想实现的是,只要有客户端的数据发过来,服务器的gTcpDataSocket就可以把数据接收,存入缓存
所以还需要用回调函数OnTcpReceive实现只要有数据进来,就接收的操作
OnTcpReceive,回调函数,tcp套接字上的接收操作完成时调用,也就是执行TcpReceive函数接收到数据时调用这个回调函数
所以,现在你知道面板上Client IP和Port是干嘛用的了吧,就是可以获取对方的IP和Port,然后显示到面板上
为什么在OnTcpReceive里还要执行TcpReceive呢?是为了循环接收数据。如果没有它,那么接收到数据时触发回调函数OnTcpReceive执行一次,就没了
Server端tcp建立连接后发送数据
- 点击按钮“Send data to client”
将触发这个按钮关联的系统变量的回调函数的执行
- 然后会执行里面的函数SendTcpData
对于服务器来说,用的是连接成功后新的套接字发送数据
- 触发回调函数OnTcpSend
这里还实现了OnTcpSend回调函数,不过我觉得要不要这个回调函数都无所谓,它实现的功能也就是判断下实际发送数据的套接字是不是执行TcpSend的套接字,顺便看看是否发送成功
Server端关闭套接字
- 点击按钮“Stop listening”
将触发这个按钮关联的系统变量的回调函数的执行
- 执行里面的函数StopListenTcp
这里用TcpClose关闭两个套接字,关闭gTcpDataSocket这个套接字,其实是断开这个tcp连接,关闭这个套接字并不会影响“被动”套接字gTcpSocket监听其他连接请求。而关闭gTcpSocket,是把监听的“被动”套接字也给关闭了,这样就没法监听客户端的连接请求了
Client TCP
tcp建立连接,是以客户端的主动发起连接请求开始的,所以客户端TCP面板上应该有一个请求连接的button
客户端向服务器发起连接请求,必须知道服务器的IP地址和端口号,所以server那一行的IP和Port就是目的IP和Port
Client端创建tcp套接字,绑定并发送连接请求
- 点击按钮“Connect to server”
将触发这个按钮关联的系统变量的回调函数的执行
- 调用函数ConnectTcp
从上面可以看出
客户端绑定的端口(ip, port)中的源IP不是从面板上获取的,那么肯定和服务器的一样,也是当CANoe运行时从网卡上自动获取的IP地址,且显示到面板上
从下面的代码可以看出:
用TcpOpen创建的socket,赋值给了gTcpDataSocket,表明连接成功后就是用这个套接字传输数据。并没有服务器那种“被动”套接字用来监听
Client端连接成功后自动接收数据
客户端连接成功后直接让其开始接收数据,这里的代码和服务器那边接收数据的代码一样
- 调用函数TcpRecv
- 触发回调函数OnTcpReceive的执行
Client端发送数据
发送数据的代码也和服务器的差不多
- 点击按钮“Send data to server”
将触发这个按钮关联的系统变量的回调函数的执行
- 调用函数SendTcpData
这里证明了我上面的猜测,上面确认有一部分代码是错误的
- 触发回调函数OnTcpSend的执行
Client端关闭套接字
只有一个套接字,所以只需要关闭gTcpDataSocket
- 点击按钮“Disconnect from server”
将触发这个按钮关联的系统变量的回调函数的执行
- 调用函数DisconnectTcp
客户端和服务器还分别实现了当CANoe停止运行时,关闭套接字并初始化
这是客户端的,服务器的还要加上gTcpSocket的代码