Win32编程也学习了一个快一个月。前段时间自己写了个基于UDP的简单聊天室程序。在这里做一个总结。
首先网络编程一般都是基于TCP或UDP的。一般聊天室的程序都是基于UDP的面向连接的。一般的过程都是于下:
TCP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、绑定IP地址、端口等信息到socket上,用函数bind();
3、开启监听,用函数listen();
4、接收客户端上来的连接,用函数accept();
5、收发数据,用函数send()和recv(),或者read()和write();
6、关闭网络连接;
7、关闭监听;
TCP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置要连接的对方的IP地址和端口等属性;
3、连接服务器,用函数connect();
4、收发数据,用函数send()和recv(),或者read()和write();
5、关闭网络连接;
与之对应的UDP编程步骤要简单许多,分别如下:
UDP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、绑定IP地址、端口等信息到socket上,用函数bind();
3、循环接收数据,用函数recvfrom();
4、关闭网络连接;
UDP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置对方的IP地址和端口等属性;
3、发送数据,用函数sendto();
4、关闭网络连接;
关于更详细的网络编程方面的大家可以找相关的书籍来看。这里我只是简单的介绍。可能还不准确。呵呵。初学者嘛!一步步学好啊。这里我写的就是基于UDP的。
由于是win32的程序。首先要把软件模型编辑出来。这里就不多说了。一般学了的都会。用一个对话框来实现的。
主要有一个大的编辑框为接收框。一个稍大的在下面作为发送编辑框。两个小的编辑框用来记录对方的IP和自己的用户名。
当然还有一个发送按钮。如下:
好了,现在就来编辑代码了。在编辑代码前我们需要的库是ws2_32.lib和头文件winsock2.h.一定要记得加进去哦。否则就会报错。首先我们做初始化套接字功能。先申明一个全局的WSADATA wsdata;的加载ws2_32.lib套接库的变量。
在对话框的InitDialog部分加入:
WSAStartup(MAKEWORD(2,2),&wsadata);
WSAStartup是启动ws2_32.lib库的函数。MAKEWORD是把两个高位的和地位的数和成一个DWORD类型的数。前一个参数是低字节。后面的是高字节。当然这里你还可以判断加载是否成功。
然后在全局的申明一个SOCKET m_socket 的套接字变量。编写一个函数用来初始化套接字和绑定套接字。
BOOL InitSockt(HWND hwnd)
{
//初始化套接字。socket参数第一个是网络套接字族。一般都是AF_INET,第二个参数是套接字类型。TCP的是SOCK_STREAM
//而UDP的是SOCK_DGRAM的形式。第三个是初始化的协议。一般都是0.
m_socket = socket(AF_INET,SOCK_DGRAM,0);
if(INVALID_SOCKET == m_socket)//初始化失败将返回INVALID_SOCKET.
{
MessageBox(hwnd,TEXT("创建套接字失败!"),TEXT("失败"),MB_OK);
WSACleanup();
return FALSE;
}
//初始化地址。
SOCKADDR_IN addr;
addr.sin_addr.S_un.S_addr =htonl(INADDR_ANY);//地址是一个结构体。用htonl把它转化为网络字节序。
addr.sin_family = AF_INET;//地址家族。
addr.sin_port = htons(port);//端口。这个port是自己申明的变量,一般自己申明的端口号要大于1024.同样用htons转化网络形式。
int retbind;
//绑定套接字。
retbind = bind(m_socket,(SOCKADDR*)&addr,sizeof(addr));
if(SOCKET_ERROR == retbind)
{
MessageBox(hwnd,TEXT("绑定套接字失败,无法启动网络/n检查网络在后再登陆!"),
TEXT("网络失败"),MB_OK | MB_ICONSTOP);
SetWindowText(GetDlgItem(hwnd,IDC_STC),TEXT("绑定套接字失败!"));//在静态控件中显示。
closesocket(m_socket);//如果绑定没有成功记得关闭套接字。
return FALSE;
}
SetWindowText(GetDlgItem(hwnd,IDC_STC),TEXT("绑定套接字成功!")); //同样成功,在静态控件中显示出来。
return TRUE;
}
这个函数作为服务端的初始化。也将放在InitDialog里面来调用。
由于这个程序是在一个对话框中实现客户端和服务端的。所以我们要用到多线程的知识。我们要创建一个线程来接收来自服务端和客户端的数据。
我们还是一样在初始化对话框中来创建线程。
HANDLE handle;//线程句柄。
LPINFO *lpinfo;
lpinfo->Hwnd=hwnd;
lpinfo->server=m_socket;
//创建接收端线程, 将句柄和socket作为结构体变量传入线程函数中。
handle = CreateThread(NULL,0,ThreadFunc,(LPVOID)lpinfo,0,NULL);
CloseHandle(handle);//记得关闭线程。
创建线程函数原型:HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
因为我们的线程函数将用到窗口的句柄和我们刚初始化的套接字作为一个结构的参数传入线程函数中。
DWORD WINAPI ThreadFunc(LPVOID lp)
线程函数只能这么调用。它只有一个LPVOID的无类型的指针。我们就通过它来传递我们需要的数据。因为他是无类型指针。所以我们传入的时候也采用LPVOID强制转换一下。关于CreateThread函数这里就不多讲了。有兴趣可以看看多线程方面的东西就可以了,可以查看MSDN,要注意的是倒数第二个参数是创建后是否立即执行。为0就立即执行。其实我们刚开始就将m_sock变量作为全局的变量,这里可以不用作为参数传入进去了 。但是我是初学者。要懂得有这样的方式来传递数据。以后就是不能有全局数据也能一通百通的用了啊。呵呵。
既然创建了线程 。我们就来处理线程函数吧。为了达到有QQ一样的有用户名和时间,我这里也调用了时间的函数和声音函数。还要注意的是怎么样在编辑框里面让它自动的换行。当然你首先在资源编辑里面属性里面就的让它有换行的功能。然后采用字符串后面加/r/n的方式使它每次需要换行的时候自动换行,这样就实现了QQ的方式。函数如下:
DWORD WINAPI ThreadFunc(LPVOID lp)
{
//这是上面讲的用结构体传入的参数,因为我们传入的时候是将它强制转换为无类型的。在这里我们又的把它强制转换回来。
//注意要强制转换的数据类型。
SOCKET server=((LPINFO*)lp)->server;
HWND hwnd = ((LPINFO*)lp)->Hwnd;
//同样由于我们这里是接收数据的,我们就的再申明一个接收端的地址变量。以便于接收端用户能用到。
SOCKADDR_IN addrfrom;//定义接收端地址信息。
int len = sizeof(SOCKADDR);
TCHAR recvBuf[256];
TCHAR tempBuf[512];
TCHAR Buff[LARGE];
TCHAR cUseName[50];
TCHAR cResult[50];
SYSTEMTIME time;//时间结构体变量。
int retval;
//我们这里采用无限循环来使它接收数据直到对方关闭。
while(TRUE)
{
//因为我们是用UDP的方式。所以我们这里用recvform来接收数据。若是TCP则采用recv。
//recvform的参数。第一是套接字,第二个是你要接收的字符缓冲区。第三个是缓冲区大小。第四个是标记我们设为0就好。
//第五个参数是接收对方地址。第六个是地址长度。
// 函数原型:int recvfrom( SOCKET s, char* buf,int len, int flags,struct sockaddr* from,int* fromlen);
retval = recvfrom(server,recvBuf,sizeof(recvBuf),0,
(SOCKADDR*)&addrfrom,&len);
if(SOCKET_ERROR == retval)
{
break;//不成功我们将退出。
}
SetWindowText(GetDlgItem(hwnd,IDC_STC),TEXT("聊天连接成功!"));
//因为我们PlapSound函数需要窗口实例句柄。我们采用GetWindowLong来的到它。
HINSTANCE appInstance = (HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE);//调用实例句柄
//消息受到声音. 关于这个函数这里不多将了。查看MSDN.
PlaySound(TEXT("IDA_MYMUSIC"),appInstance,SND_RESOURCE|SND_ASYNC);
GetLocalTime(&time);//调用电脑的时间。
WORD wSecond=time.wSecond;
wsprintf(cResult,TEXT("%02u-%02u-%02u"),
time.wHour,time.wMinute,time.wSecond);
//将时间格式化后存入缓冲区。这里我还要说明一点。有两个函数这里可以实现。
//wsprintf和sprintf。但是他们有区别的。主要是能放的缓冲区大小问题。大家可以去试试他们的区别。
GetDlgItemText(hwnd,IDC_EDT_USE,cUseName,sizeof(cUseName));
sprintf(tempBuf,"【%s 】/t %s",cUseName,cResult);
strcat(tempBuf,"/r/n");//实现自动换行。
strcat(tempBuf,recvBuf);
strcat(tempBuf,"/r/n");//实现自动换行。
GetDlgItemText(hwnd,IDC_EDT_RECV,Buff,sizeof(Buff));
strcat(Buff,tempBuf);
//做好了所以工作后将他们集体输出到编辑框中。原理是先得到编辑框的内容。在加要输出的内容。后一起输出到编辑框。
SetDlgItemText(hwnd,IDC_EDT_RECV,Buff);
}
return 0;
}
注意我们用到声音的函数。要加入Winmm.lib库哦。否则就会报错!还有个头文件。Mmsystem.h。这个可加也可不加。有windows.h就可以了。
好啦!我们接收端的线程就完工了。程序完成一半了。先休息一下。呵呵。