C# 简单的 SIP(VOIP)电话
一个基于C#的简单SIP(VOIP)拨出电话。
引言
这是一个基于C#的简单SIP(VOIP)拨出电话。这个SIP应用程序已开发完成,目前用作“帮助 -> 联系支持”。其理念是创建一个零配置的、非常简单的拨出电话,现在它就是这样(尽管支持基于IP的来电;例如:sip:test@ip:7666,7666是SIP_Call out运行的端口)。
目前,此应用程序仅在Windows上运行。由于某种原因,.NET“仍然”没有对音频输入和音频输出的托管支持。音频部分使用非托管的Windows wave API。
我试图使示例应用程序井然有序、清晰易懂并带有良好的注释——不知道结果如何,您可以自行判断。对于初学者,我建议您使用Google搜索并阅读一些SIP入门教程,否则您将无法理解正在发生的事情。
由于代码充满了注释,我认为这里不需要过多的解释;只需深入研究代码即可。
使用的SIP命令和术语(在示例应用程序中)
- INVITE - Invite有两个含义
- 初始INVITE - 简单来说,我们或远程方只是发送一个呼叫请求。
- 会话中INVITE - 在SIP规范中,这称为“RE-INVITE”。
- RE-INVITE用于修改会话信息;在我们的例子中,实现呼叫保持。RE-INVITE可以由我们或远程方发送。
- ACK - 对于每个INVITE/RE-INVITE的2xx肯定响应,都必须向远程方发送ACK。ACK只是确认我们收到了2xx响应。
- CANCEL - CANCEL可用于取消挂起的INVITE或RE-INVITE请求。
- BYE - BYE用于结束活动呼叫。呼叫终止方必须发送BYE。
- SIP对话 - 我们可以将其想象为我们和远程方之间的会话。
- SIP呼叫 - SIP呼叫由SIP对话和音频RTP会话组成。
建立呼叫
呼叫建立从创建RTP音频会话开始,因为我们需要在SDP中宣传我们的RTP会话IP:端口。之后,如果需要,我们需要进行NAT处理。现在可以创建初始INVITE请求并将其发送到远程方。更多详细信息,请阅读RFC 3216(请参阅下面的链接)。
交换的示例SIP消息
INVITE sip:bob@192.168.1.44 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.33;branch=z9hG4bKnashds8
Max-Forwards: 70
To: Bob <sip:bob@domain.com>
From: Alice <sip:alice@domain.com>;tag=1928301774
Call-ID: a84b4c76e66710
CSeq: 314159 INVITE
Contact: <sip:alice@192.168.1.33>
Content-Type: application/sdp
Content-Length: sdp_size_in_bytes
v=0
o=- 2890844526 2890844526 IN IP4 192.168.1.33
s=
c=IN IP4 192.168.1.33
t=0 0
m=audio 1111 RTP/AVP 0 97
a=rtpmap:0 PCMU/8000
a=sendrecv
SIP/2.0 180 Ringing
Via: SIP/2.0/UDP 192.168.1.33;branch=z9hG4bK4b43c2ff8.1 ;received=192.0.2.3
To: Bob <sip:bob@domain.com>;tag=a6c85cf
From: Alice <sip:alice@domain.com>;tag=1928301774
Call-ID: a84b4c76e66710
Contact: <sip:bob@192.168.1.44>
CSeq: 314159 INVITE
Content-Length: 0
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.33;branch=z9hG4bK4b43c2ff8.1;received=192.0.2.3
To: Bob <sip:bob@domain.com>;tag=a6c85cf
From: Alice <sip:alice@domain.com>;tag=1928301774
Call-ID: a84b4c76e66710
CSeq: 314159 INVITE
Contact: <sip:bob@1192.168.1.44>
Content-Type: application/sdp
Content-Length: sdp_size_in_bytes
v=0
o=- 2890844526 2890844526 IN IP4 192.168.1.44
s=
c=IN IP4 192.168.1.44
t=0 0
m=audio 2222 RTP/AVP 0 97
a=rtpmap:0 PCMU/8000
a=sendrecv
ACK sip:bob@192.0.2.4 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.33;branch=z9hG4bKnashds9
Max-Forwards: 70
To: Bob <sip:bob@domain.com>;tag=a6c85cf
From: Alice <sip:alice@domain.com>;tag=1928301774
Call-ID: a84b4c76e66710
CSeq: 314159 ACK
Content-Length: 0
呼叫保持
在SIP中,呼叫保持并非精确定义。实际上,SIP不知道任何关于保持的信息。保持完全由应用程序处理(这是因为SIP不关心它发送的SDP数据)。因此,不同的实现可能会以不同的方式处理它。
到目前为止,我已经看到了四种不同的电话处理方式
- 在SDP中将音频流IP设置为“0.0.0.0”。
- 通过将端口设置为“0”来禁用SDP中的音频流。
- 将音频流设置为“sendonly”——通常情况下,呼叫保持者会向远程方发送保持音乐。
- 更清晰的方法是将音频流设置为“inactive”(这就是我们的做法)。
禁用或设置非活动音频流的主要区别在于,“inactive”RTCP中,会话仍然存在并且会发送RTCP数据包。关于“inactive”中的RTP流,应用程序可以在保持状态下暂停会话或将其释放。
示例SDP(进行和取消保持)
[onhold our offer]
v=0
o=- 2890844526 2890844526 IN IP4 192.168.1.33
s=
c=IN IP4 192.168.1.33
t=0 0
m=audio 1111 RTP/AVP 0 97
a=rtpmap:0 PCMU/8000
a=inactive
[onhold remote-party answer]
v=0
o=- 2890844526 2890844526 IN IP4 192.168.1.44
s=
c=IN IP4 192.168.1.44
t=0 0
m=audio 2222 RTP/AVP 0 97
a=rtpmap:0 PCMU/8000
a=inactive
------------------------------------------------
[unhold our offer]
v=0
o=- 2890844526 2890844526 IN IP4 192.168.1.33
s=
c=IN IP4 192.168.1.33
t=0 0
m=audio 1111 RTP/AVP 0 97
a=rtpmap:0 PCMU/8000
a=sendrecv
[unhold remote-party answer]
v=0
o=- 2890844526 2890844526 IN IP4 192.168.1.44
s=
c=IN IP4 192.168.1.44
t=0 0
m=audio 2222 RTP/AVP 0 97
a=rtpmap:0 PCMU
a=sendrecv
NAT处理
我们支持两种不同的NAT处理方法
- STUN - 向STUN服务器发送STUN请求,它会回复请求发出的IP:端口。
- UPnP - 使用UPnP API打开路由器端口。只有在路由器支持UPnP的情况下才能使用此方法。
只有在路由器支持UPnP或路由器类型(STUN)不是“对称NAT”的情况下,才能拨打电话。“对称NAT”中,每个对新IP:端口的新UDP请求都映射到一个新的路由器,外部新的IP:端口。有关NAT类型的更多信息,请参阅:http://en.wikipedia.org/wiki/Network_address_translation。
如果路由器支持SIP ALG(应用层网关),则存在一个特殊情况,应用程序**不必**进行NAT处理。路由器会根据需要更改SIP、SDP并打开路由器端口。(到目前为止,我看到汤普森路由器是这样做的。)
最后的寄语
如果你有合适的组件(SIP协议栈、RTP协议栈、音频库),那么构建SIP软电话很容易……但是当你需要从头开始编写所有代码时,这将是一场噩梦。你会陷入大量的混乱的RFC中……
链接
- SIP规范 (RFC 3261): http://tools.ietf.org/html/rfc3261
- SDP规范 (RFC 4566): http://tools.ietf.org/html/rfc4566
- SDP报文示例: http://www.rfc-editor.org/rfc/rfc4317.txt
- STUN规范 (RFC 3489): http://www.faqs.org/rfcs/rfc3489.html
- 路由器NAT类型: http://en.wikipedia.org/wiki/Network_address_translation
- UpnP: http://www.upnp.org/
- RTP: http://www.ietf.org/rfc/rfc3550.txt
- 论坛: http://www.lumisoft.ee/forum/