实验一 TCP Socket API程序设计
一、预备知识
1.网络编程基本概念
网络上的计算机间的通讯,实质上是网络中不同主机上的程序之间的通讯。在互联网中使用IP地址来标识不同的主机,在网络协议中使用端口号来标识主机上不同进程,即使用(IP地址,端口号)二元组。
套接字(Socket)用于描述IP地址和端口,是一个通信链的句柄,通信时一个网络程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过与网络接口卡相连的传输介质将这段信息发送到另一台主机的Socket中,以供其他程序使用。
图1-1 TCP通信流程 2.TCP通信流程
TCP程序是面向连接的,程序运行后,服务器一直处于监听状态,客户端与服务器通信之前必须首先发起连接请求,由服务器接收请求并在双方之间建立连接后才可以互相通信。
二、实验目的
1.了解Winsock API编程原理; 2.掌握TCP Socket程序的编写; 3.了解C/S模式的特点; 4.学会解决实验中遇到的问题。
三、实验任务
使用Winsock API相关类实现TCP Socket通信程序,并能成功运行。
四、实验环境及工具
1.Windows2000/XP/7 2.Visual C++开发平台 3.Visual Studio2010
五、实验内容和步骤
参照《Visual C++网络编程教程》书中81页,TCP Socket API程序设计。 连接:
void CChatClientDlg::OnConnect() {
WSADATA wsd;
//WSADATA结构
WSAStartup(MAKEWORD(2,2),&wsd);
//加载协议,使用Winsock 2.2版
m_client = socket(AF_INET,SOCK_STREAM,0); //创建流式套接字
//服务器地址
sockaddr_in serveraddr;
UpdateData();
if(ServerIP.IsBlank())
{
AfxMeageBox(\"请指定服务器IP!\");
return;
}
if(sPort.IsEmpty())
{
AfxMeageBox(\"请指定端口!\");
return;
}
//获取服务器进程的IP和端口
BYTE nFild[4];
CString sIP;
ServerIP.GetAddre(nFild[0],nFild[1],nFild[2],nFild[3]);
sIP.Format(\"%d.%d.%d.%d\",nFild[0],nFild[1],nFild[2],nFild[3]);
//设置服务器地址结构的内容
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.S_un.S_addr = inet_addr(sIP);
serveraddr.sin_port = htons(atoi(sPort));
//发起连接须指明要访问的服务器进程地址,这个地址存储在serveraddr中
if(connect(m_client,(sockaddr*)&serveraddr,sizeof(serveraddr)) != 0)
{
MeageBox(\"连接失败\");
return;
}
else
{
m_ListWords.AddString(\"连接服务器成功!\");
m_ListWords.SetTopIndex(m_ListWords.GetCount()1);
ServerIP.EnableWindow();
ServerPort.EnableWindow();
m_ButtonConnect.EnableWindow();
m_ButtonDisconnect.EnableWindow(false);
m_EditWords.EnableWindow(false);
m_ButtonSend.EnableWindow(false);
m_ButtonExit.EnableWindow(); }
“发送”按钮事件过程代码如下:
void CChatClientDlg::OnSend() {
//向服务器发送信息
UpdateData();
if(m_sWords.IsEmpty())
{
AfxMeageBox(\"发送的消息不能为空!\");
return;
}
//开始发送数据
int i = send(m_client,m_sWords.GetBuffer(0),m_sWords.GetLength(),0);
m_ListWords.AddString(\"发送:\" + m_sWords);
m_ListWords.SetTopIndex(m_ListWords.GetCount()1);
closesocket(m_client);
ServerIP.EnableWindow();
ServerPort.EnableWindow();
m_ButtonConnect.EnableWindow();
m_ButtonDisconnect.EnableWindow(false);
m_EditWords.EnableWindow(false);
m_ButtonSend.EnableWindow(false);
m_ButtonExit.EnableWindow(); } “清空”按钮的事件过程: m_ListWords.ResetContent(); “关于”按钮的事件过程: CAboutDlg dlgAbout; dlgAbout.DoModal();
服务器端: 开始监听代码:
void CChatServerDlg::OnListen() {
WSADATA wsd;
//WSADATA结构
WSAStartup(MAKEWORD(2,2),&wsd);
//加载协议栈,使用Winsock 2.2版
m_server = socket(AF_INET,SOCK_STREAM,0); //创建流式套接字
//将网络中的事件关联到窗口的消息函数中,定义消息号为20000,侦测客户端的连接请求
WSAAsyncSelect(m_server,m_hWnd,20000,FD_ACCEPT);
m_client = 0;
BYTE nFild[4];
CString sIP;
UpdateData();
if(ServerIP.IsBlank())
{
AfxMeageBox(\"请设置IP地址!\");
return;
}
if(sPort.IsEmpty())
{
AfxMeageBox(\"请设置监听端口!\");
return;
}
ServerIP.GetAddre(nFild[0],nFild[1],nFild[2],nFild[3]);
sIP.Format(\"%d.%d.%d.%d\",nFild[0],nFild[1],nFild[2],nFild[3]);
//服务器地址
sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.S_un.S_addr = inet_addr(sIP);
serveraddr.sin_port = htons(atoi(sPort));
//绑定地址
if (bind(m_server,(sockaddr*)&serveraddr,sizeof(serveraddr)))
{
MeageBox(\"绑定地址失败.\");
return;
}
//监听开始,服务器等待连接请求的到来
listen(m_server,5);
m_ListWords.AddString(\"监听开始:\");
m_ListWords.AddString(\"地址\" + sIP + \" 端口\" + sPort);
m_ListWords.AddString(\"等待客户端连接„„\");
//界面完善
m_ListWords.SetTopIndex(m_ListWords.GetCount()-1);
ServerIP.EnableWindow(false);
ServerPort.EnableWindow(false);
m_ButtonListen.EnableWindow(false);
m_ButtonStopListen.EnableWindow();
m_ButtonClear.EnableWindow();
m_ButtonExit.EnableWindow(false); } “停止监听”按钮事件过程代码如下: void CChatServerDlg::OnStopListen() {
//停止监听
closesocket(m_server);
m_ListWords.AddString(\"停止监听\");
m_ListWords.SetTopIndex(m_ListWords.GetCount()1); } “断开”按钮事件过程代码如下: void CChatServerDlg::OnDisconnect() {
closesocket(m_client);
m_ListWords.AddString(\"与客户端断开\");
m_ListWords.SetTopIndex(m_ListWords.GetCount()1);
//界面完善
m_ButtonDisconnect.EnableWindow();
m_EditWords.EnableWindow();
m_ButtonSend.EnableWindow(); } ReceiveData()函数代码如下:
void CChatServerDlg::ReceiveData() {
//接收客户端的数据
char buffer[1024];
int num = recv(m_client,buffer,1024,0);
buffer[num] = 0;
CString sTemp;
sTemp.Format(\"收到:%s\",buffer);
m_ListWords.AddString(sTemp); //显示信息
m_ListWords.SetTopIndex(m_ListWords.GetCount()1);
closesocket(m_client); //关闭与客户端通信的Socket
WSAAsyncSelect(m_server,m_hWnd,20000,FD_ACCEPT);//准备接收新的客户端连接
//界面完善
m_ButtonDisconnect.EnableWindow(false);
m_EditWords.EnableWindow(false);
m_ButtonSend.EnableWindow(false); } 服务器的初始化代码如下: //界面初始化
m_ButtonStopListen.EnableWindow(false); m_ButtonDisconnect.EnableWindow(false); m_ButtonClear.EnableWindow(false); m_EditWords.EnableWindow(false); m_ButtonSend.EnableWindow(false);
运行结果:
六、思考题
1.用Winsock API编程时,主要进行哪些通行的操作步骤? 2.阐述C/S模式的通信过程。 答:
1.通行的操作
1.Winsock的打开(WSAStartup())。 2.建立套接字(socket()或WSASocket())。 3.地址绑定(bind())。
4.服务器监听连接(listen())。
5.客户端提出连接申请(connect()或WSAConnect ())。 6.服务器接收客户端的连接请求(accept()或WSAAccept())。 7.数据的发送(send()或WSASend(),sendto()或WSASendTo())。 8.数据的接收(recv()或WSARecv(),recvfrom()或WSARecvfrom())。 9.关闭套接字(closesocket())。 10.关闭Winsock(WSACleanup())。
2通信过程