实验一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/72. 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()){AfxMessageBox("请指定服务器IP!");return;}if(sPort.IsEmpty()){AfxMessageBox("请指定端口!");return;}//获取服务器进程的IP和端口BYTE nFild[4];CString sIP;ServerIP.GetAddress(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){MessageBox("连接失败");return;}else{m_ListWords.AddString("连接服务器成功!");m_ListWords.SetTopIndex(m_ListWords.GetCount() - 1);}WSAAsyncSelect(m_client,m_hWnd,10000,FD_READ|FD_CLOSE); //①//界面完善ServerIP.EnableWindow(false);ServerPort.EnableWindow(false);m_ButtonConnect.EnableWindow(false);m_ButtonDisconnect.EnableWindow();m_EditWords.EnableWindow();m_ButtonSend.EnableWindow();m_ButtonExit.EnableWindow(false);m_ButtonClear.EnableWindow();}“断开”按钮的事件过程代码如下:void CChatClientDlg::OnDisconnect(){//断开与服务器的连接closesocket(m_client);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()){AfxMessageBox("发送的消息不能为空!");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);}BOOL CChatClientDlg::PreTranslateMessage(MSG* pMsg){if(pMsg->message == 10000) //识别应用程序中定义的消息号{switch(pMsg->lParam) //判断网络事件类型{case FD_READ:this->ReceiveData(); //若为FD_READ则接收数据break;case FD_CLOSE:this->CloseSock(); //如果服务端断开,客户端也断开break;}}elsereturn CDialog::PreTranslateMessage(pMsg);}下面来实现ReceiveData()和CloseSock()方法。
首先在头文件ChatClientDlg.h中的类CChatClientDlg中定义这两个方法:void ReceiveData();void CloseSock();然后分别编写这两个方法的代码。
ReceiveData()的代码如下:void CChatClientDlg::ReceiveData(){char buffer[1024];//接收服务器传来的数据int num = recv(m_client,buffer,1024,0); //函数recv()接收数据buffer[num] = '\0';//将接收的数据添加到列表框中CString sTemp;sTemp.Format("收到:%s",buffer);//接收完数据后继续侦测WSAAsyncSelect(m_client,m_hWnd,10000,FD_READ|FD_CLOSE);m_ListWords.AddString(sTemp);m_ListWords.SetTopIndex(m_ListWords.GetCount()-1);}CloseSock()的代码如下:void CChatClientDlg::CloseSock(){m_ListWords.AddString("服务器断开了");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()){AfxMessageBox("请设置IP地址!");return;}if(sPort.IsEmpty()){AfxMessageBox("请设置监听端口!");return;}ServerIP.GetAddress(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))){MessageBox("绑定地址失败.");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);ServerIP.EnableWindow();ServerPort.EnableWindow();m_ButtonListen.EnableWindow();m_ButtonStopListen.EnableWindow(false);m_ButtonExit.EnableWindow();}“发送”按钮事件过程代码如下:void CChatServerDlg::OnSend(){//向服务器发送信息UpdateData();if(m_sWords.IsEmpty()){AfxMessageBox("发送的消息不能为空!");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);}“断开”按钮事件过程代码如下:void CChatServerDlg::OnDisconnect(){closesocket(m_client);m_ListWords.AddString("与客户端断开");m_ListWords.SetTopIndex(m_ListWords.GetCount() - 1);m_ButtonDisconnect.EnableWindow(false);m_EditWords.EnableWindow(false);m_ButtonSend.EnableWindow(false);}在头文件ChatServerDlg.h的类CChatServerDlg中声明:void HandleData(); //用于接收客户端的连接请求void ReceiveData();void CloseSock();首先为主对话框对象添加PreTranslateMessage()方法。