最近 一直在看MFC编程,并且动手实现了一款能够获取服务器信息的客户端,总共分为两个部分(服务器,客户端(带界面的))下面就来看看实现的方法与步奏。
由于Winsock的服务事宜动态链接库Winsock DLL形式实现的,因此必须先调用WSAStartup函数对Winsock DLL 进行初始化,获取Winsock的版本支持,并分配必要的资源。
总之函数有两个作用
https://msdn.microsoft.com/en-us/library/windows/desktop/ms742213(v=vs.85).aspx
这里有着调用WSAStartup函数的例子直接拷贝下面是网页上的代码片段
int __cdecl main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
/* Confirm that the WinSock DLL supports 2.2.*/
/* Note that if the DLL supports versions greater */
/* than 2.2 in addition to 2.2, it will still return */
/* 2.2 in wVersion since that is the version we */
/* requested. */
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return 1;
}
}
把上面的代码稍加修改得到如下代码:
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
我们用的是1.1版本的套接字库
该套接字为侦听套接字,作用只是进行客户端之间的连接。
**
【1】服务进程创建套接字
**
SOCKET WSAAPI socket(
_In_ int af,
_In_ int type,
_In_ int protocol
);
第二个参数指定Socke类型,对于1.1版本的Socket只支持SOCK_STREAM(流式套接字)和SOCK_DGRAM (数据报套接字)。
第三个参数选择协议,如果为零则自动选择协议
**
vc++6.0 创建名为sockSrv的套接字
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);
【2】将本地地址绑定到所创建的套接字上
**
套接字建立后,SOCKET 返回所创建的句柄,然后将本地地址绑定到所创建的套接字上以便在网络上识别套接字
利用的是bind函数
int bind(
_In_ SOCKET s,
_In_ const struct sockaddr *name,
_In_ int namelen
);
1.第一参数指未捆绑套接字的句柄这里指的是sockSrv
2.第二参数指赋予套接字sockSrv的地址 是一个结构体指针
3.第三个参数为名字长度
下面在vc++6.0创建地址并绑定给套接字
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//IP地址 INADDR_ANY表示允许套接字向任何分配给本地机器的IP地址发送或接受数据 u_long类型
addrSrv.sin_family=AF_INET;//地址族 对于IP地址必须设为AF_INET()
addrSrv.sin_port=htons(6000);//将要分配给套接字的端口,必须用网络字节顺序表示
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
u_long WSAAPI htonl(
_In_ u_long hostlong
);//转化u_long为网络字节序
u_short WSAAPI htons(
_In_ u_short hostshort
);//转化u_short为网络字节序
**
【3】将套接字置入监听模式并准备接受连接请求
**
此处用listen函数
int listen(
_In_ SOCKET s,
_In_ int backlog
);
1.第一个参数s标识一个已捆绑未连接套接字的描述字
2.第二参数backlog用于指定正在等待连接的最大队列的长度
在进入监听状态之后,我们通过调用accept函数使得套接字做好接受客户的链接的准备
SOCKET accept(
_In_ SOCKET s,
_Out_ struct sockaddr *addr,
_Inout_ int *addrlen
);
1.参数1已捆绑的套接字
2.参数2客户端的地址信息
3.必须赋初始值len
accept函数接受了客户端的链接请求之后会返回一个新的套接字所以这里必须在定义一个新的套接字进行与客户端的通信,并且先前的套接字继续监听客户的连接请求
vc++6.0
如下:
listen(sockSrv,5);//使套接字进入监听状态
SOCKADDR_IN addrClient;//为了保存接受的客户端的
int len=sizeof(SOCKADDR);//accept第三参数的初始值
while(1)
{
SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);
char result[1024*4]=""; //存储发送的信息
char buffer[128];//缓存
FILE* fp=_popen("systeminfo","r");//执行shell命令读取本机信息并保存至文件流fp中
while(!feof(fp)){//判断文件流是否结束
fgets(buffer,sizeof(buffer),fp);//读取每行fp数据至buffer缓冲区
strcat(result,buffer);//将result追加buffer
strcat(result,"\r\n");//再将result追加回车
}
_pclose(fp);//关闭文件流
send(sockConn,result,strlen(result)+1,0);//向客户端发送数据
closesocket(sockConn);//关闭此次连接等待下一次连接
}
至此服务器端的建立就结束了附上服务器的整体代码:
#include
#include
# include
# include
using namespace std;
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
listen(sockSrv,5);
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
while(1)
{
SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);
char result[1024*4]="";
char buffer[128];
FILE* fp=_popen("systeminfo","r");
while(!feof(fp)){
fgets(buffer,sizeof(buffer),fp);
strcat(result,buffer);
strcat(result,"\r\n");
}
_pclose(fp);
send(sockConn,result,strlen(result)+1,0);
closesocket(sockConn);
}
}
下面介绍一下客户端图形化界面的设计与建立
总共三个控件ID分别为IDC_BUTTON1(请求)、IDC_BUTTON2(保存)、IDC_EDIT1(编辑)
5.建立类向导,为控件起变量名
6.双击请求按钮,在请求处添加句柄代码
void CClientDlg::OnButton1()
{
// TODO: Add your control notification handler code here
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
//以上代码同建立服务器端一样的操作步骤
//首先建立套接字sockClient,
//然后绑定要通讯服务器的ip地址、端口、协议类型
//最后建立connect函数实现客户端向服务器发出连接请求 (下句)
connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
char recvBuf[10000];
recv(sockClient,recvBuf,10000,0);//接受来自服务器的信息并存入字符串数组recvBuf[]中
send(sockClient,"This is ji",strlen("This is lisi")+1,0);//不必要的步骤(向服务器发送连接信息告知服务器是谁在连接)
m_cat=recvBuf;//m_cat为编辑区的变量名,将数据复制给编辑区变量
UpdateData(FALSE);//将变量值上传至控件及m_cat显示在编辑区
closesocket(sockClient);//关闭连接
WSACleanup();//对应于一个任务进行的每一次WSAStartup()调用,必须有一个WSACleanup()调用.只有最后的WSACleanup()做实际的清除工作;
}
7.双击保存按钮,在保存处添加句柄代码
void CClientDlg::OnButton2()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);//将控件界面值赋值给变量
CFile file;//定义一个CFile类型的file类
file.Open("data.txt",CFile::modeCreate|CFile::modeWrite|CFile::modeNoTruncate,NULL);
//参数1文件路径
//CFile::modeCreate 让构造器创建一个新文件,如果那个文件已经存在,把那个文件的长度重设为
//CFile::modeWrite 打开文件只供写
//CFile::modeNoTruncate 可以同modeCreate. 一起用,如果要创建的文件已经存在,并不把它长度设置为0,因而这个文件获取或者作为一个新建文件或者作为一个已存在文件打开。
//参数三可有可无
file.Write(m_cat,strlen(m_cat)); //将字符串写入文档
// file.close( );
}
最后附上操作图片:
1.服务器(上线信息)
2.客户端(向服务器请求)
3.data.txt文本
到此所有的工作都已经结束,自我感觉总结还算详细,仅供大家参考有问题一起讨论。