一、实验目的:
通过本实验使学员了解和掌握编写基于TCP协议的网络应用程序。任务是开发一个基于TCP Socket API的网络聊天程序。
二、实验内容简要描述
用所学的TCP Socket API知识来开发基于TCP协议的网络。通过编程实现服务端和客户端的信息通信。TCP协议建立交互的流程如下图所示。通过编写C/C++程序,调用windows提供的Socket API,模拟下述流程,建立客户端与服务端的远程TCP通信。使得彼此双方能够接收彼此信息、发送信息。
三、实验步骤与结果分析
基于TCP协议的面向客户/服务器的工作流程是:
- 服务器端:
Ⅰ 服务器首先启动,调用 socket( )创建套接字;
Ⅱ 然后调用bind( )指定服务器socket地址(IP地址+端口号);
Ⅲ 再调用listen( )让服务器做好侦听准备,并规定好请求队列的长度,然后服务器进入阻塞状态,等待客户的连接请求;
Ⅳ 最后通过accept( )来接收连接请求,并获得客户的socket地址。
- 客户端
Ⅰ 客户在创建套接字并指定客户 socket地址
Ⅱ 然后就调用connect( )和服务器建立连接。
Ⅲ 一旦连接建立成功,客户和服务器之间就可以通过调用read( )和write( )来接收和发送数据
Ⅳ 一旦数据传输结束,服务器和客户通过调用close( )来关 闭套接字。
服务端:
- 启动,调用socket()创建套接字
- 在创建套接字之后,套接字作为网络端口的抽象,需要与具体的IP/端口进行绑定,之后我们才可以通过套接字这一层抽象简便地实现通信。我们先设置好地址,然后调用bind()函数将套接字与地址绑定。
这里与本地地20000端口进行绑定。在实际的两台主机之间通信时,将回环地址修改为自己的IPv4地址即可(如果套接字参数选择IPv6,则相应的使用IPv6地址);端口号的选择只需要确保和已有的端口不冲突即可。
- TCP协议是面向连接的通信协议,因此首先要确保服务端与用户端的联系紧密,尔后再进行稳定可靠的通信。建立联系的过程,需要服务端进行监听,等待用户端的连接、通信请求。这个监听的过程,通过listen()、阻塞来实现。
- 当服务端在监听时收到了来自客户端的通信请求(连接),就通过accept()获取客户端的Socket地址,进而建立TCP联系。然后就可以通过套接字与客户端进行交互。
- 交互过程分为接收和发送两个阶段,由于程序的单线程下,接收和发送均存在阻塞状态,因此只能实现接受和发送的交替轮流通信。对于交互过程,我将它封装在interactive()函数中,通过传入客户端的套接字信息,实现本机(服务端)与客户端的信息交流。实际上,对于交互过程的封装,是为了之后进一步的“多线程”互联所作的准备;多线程实现通信将放在本节内容最后进行阐述。
可以注意到其中有一些函数如print_time()等,是为了控制界面的打印。由于这些细节与TCP协议实现并无太大关联,故具体这些控制函数将放在后面进行“结论”的收获部分进行汇报。
- 最后,当通信完毕,就通过close来关闭套接字、断开连接。
客户端:
- TCP协议不同于UDP协议,是更为可靠的,因此客户端和服务端一样,都需要建立套接字对本机端口进行抽象。客户端也通过socket()函数。
- 然后,根据服务端的地址和端口数据,通过connect()主动连接服务端程序。
- 连接建立成功之后,便可以和服务端进行通信交互,方式和服务端相同,只不过约定首先发送信息。
- 同样的,在通信完毕之后,断开连接,与服务端方式相同,这里就不赘述。
基于以上连接策略实现的简易TCP通信结果如下:
可见,达成了TCP简易通信的基本目标。
基于多线程的TCP通信服务
由于TCP自身的特性以及收发信息的阻塞状态,单个线程无法实现多主机互联。因此这里尝试采用服务端多线程的方式,实现多台客户端与服务端交互而客户端之间互相屏蔽的TCP交互功能。
相对于上述服务端代码,主要的区别在于,主线程循环接收连接,子线程进行服务端与客户端的交互。
最终实现效果如下:
四、结论
此次实验,让我更加了解了TCP协议的过程,同时也初步掌握了C++ Socket编程的基础技能,更让我在实际的应用中,尝试使用多线程、利用控制字符美化交互环境等等,受益匪浅。
此次实验完成了基本的TCP通信交互任务,还扩展了多台客户端与服务端交互的实现。接下来我将尝试加入可视化界面,同时更加优化字符打印的交互界面。
对于控制打印的函数,自己通过摸索,也有了很大的收获:
打印时间
通过套接字获取地址
回退到黑窗口上一行并覆写