练好网络编程基本功——实现UDP/IP通信与多线程
编程爱好者
了解:UDP/IP通信
UDP/IP网络通信是一种面向无连接的网络通信,服务器端与客户端之间不需要建立连接便能进行网络通信。面向无连接的网络通信不确保可靠的数据传输,发送端发送数据时,它不管接收端是否准备接收这些数据。接收端在接收到数据后也不会发送消息确认数据已收到。也就是说,面向无连接的网络通信不同于面向连接的网络通信,它不进行数据校验,也正因为如此,它的数据收发速度要比面向连接的网络通信快。
在网络通信时,如果数据的完整性不是十分重要,即在通信时丢掉一部份数据也无伤大雅,用UDP/IP网络通信是一个很好的选择。如在线看电影时,为更好地保证观看的连续性将采用UDP/IP网络通信,而在观看的时候偶尔少了一点点数据并不影响影片的内容,很多时候你甚至发现不了丢失的数据。
实例学习:面向无连接的通信
实例是最好的老师!下面,我们首先来看看服务器端的程序,然后为大家做重点分析。大家可以对比一下它和上期TCP/IP通信服务器端程序的区别。
服务器端
const int BUFFER_LEN = 2048;
void main(void)
{ WSADATA wsaData;
SOCKET ServerSocket;
SOCKADDR_IN ServerAddr;
SOCKADDR_IN ClientAddr;
int ClientAddrSize=sizeof(ClientAddr);
int Port = 5555;
char buffer[BUFFER_LEN];
//初始化Winsock
WSAStartup(MAKEWORD(2,2), &wsaData);
//创建套接字
ServerSocket=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
//绑定
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port=htons(Port);
ServerAddr.sin_addr.s_addr=htonl(INADDR_ANY);
bind(ServerSocket,(SOCKADDR*)&ServerAddr,sizeof(ServerAddr));
while(1)
{ recvfrom(ServerSocket,buffer,BUFFER_LEN, 0,(SOCKADDR*)&ClientAddr, &ClientAddrSize);
cout< //关闭套接字 closesocket(ServerSocket); //释放Winsock分配的资源 WSACleanup();} 程序分析:从给出的UDP/IP服务器源代码可以看出,它比TCP/IP的服务器代码要简短,很快你就会发现,它比TCP/IP服务器少了监听与接受连接两个步骤。这是因为在面向无连接的通信中,机器之间不用建立连接便能进行网络通信。再往下看,你又会发现在UDP/IP网络通信中,用于接收数据的函数是recvfrom。 const int BUFFER_LEN=2048; void main(void) { WSADATA wsaData; SOCKET ClientSocket; SOCKADDR_IN ServerAddr; int Port=5555; char buffer[BUFFER_LEN]; //初始化Socket WSAStartup(MAKEWORD(2,2),&wsaData); //创建套接字 ClientSocket=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); //给出服务器地址信息 ServerAddr.sin_family=AF_INET; ServerAddr.sin_port=htons(Port); ServerAddr.sin_addr.s_addr=inet_addr("192.168.1.72"); while(1) { cin>>buffer; sendto( ClientSocket,buffer,BUFFER_LEN, 0,(SOCKADDR*)&ServerAddr, sizeof(ServerAddr));} closesocket(ClientSocket); WSACleanup();} 程序分析:因为是面向无连接的网络通信,所以在客户端的程序中我们可以看到,程序并没有用connect函数与服务器进行连接。在无连接的套接字上发送数据,使用的是sendto函数。 多线程在网络编程中得到广泛的应用,这里我就用上一篇的TCP/IP网络通信为例,简单向大家介绍多线程的网络编程。 在上一篇的TCP/IP网络通信例子里,服务器端只用了一条主线程执行所有的操作。在调用接受连接函数时,线程将停下来,等待连接。而类似的,当有一个客户端成功进行连接后,在用recv函数接收该客户端的数据时,线程将停下来等待数据。因为服务器端要不等地接收客户端的数据,所以无奈只能调用一次接受连接函数,这也就是为什么上一期的TCP/IP服务端程序只能连入一个客户端的原因。 而在这里,我们将在主线程用一个循环不停接受连接,当有一个客户端成功连接进来后,将为这个客户端开一个线程,用于与此客户端进行数据通信。因为有一个线程不停地进行接受连接,所以这个多线程的TCP/IP服务器端将可以接受多个客户端的连接请求。这里客户端还只是向服务器端发送数据,所以单线程就足够了,我们只需要改动服务器端。 下面是上一期经过改写的多线程TCP/IP通信服务器端程序: #include <winsock2.h> #include <process.h> #include <iostream> using namespace std; const int BUFFER_LEN = 2048; VOID ClientThread(PVOID pvoid); struct ThreadData { SOCKET Socket;SOCKADDR_IN Addr;}; void main(void) { //初始化Winsock,创建套接字,监听 while(1) { ThreadData* td = new ThreadData; //接受连接 int iClentAddrLen = sizeof(td->Addr); td->Socket=accept( ServerSocket,(SOCKADDR*)&td->Addr,&iClentAddrLen ); _beginthread(ClientThread,0,td);} //关闭套接字,释放Winsock分配的资源 } VOID ClientThread(PVOID pvoid) { ThreadData* td=(ThreadData*)pvoid; char buffer[BUFFER_LEN]; //接收数据 while(recv(td->Socket,buffer,BUFFER_LEN,0)) cout<<buffer< //关闭套接字 closesocket(td->Socket); delete td; _endthread();} 要进行多线程编程,必须把process.h包含进程序,并做以下设置: 在Visual C++.Net2003的程序界面中点击菜单“项目→属性”,在TCP Server属性页窗口左边选中“C/C++”下的“代码生成”,在右边的“运行时库”下设为“多线程调试(/MTd)”(图1),在“Release”版本下设为“多线程(/MT)”(图2)。 当然,我在这里只是介绍了非常简单的多线程应用,多线程在网络编程中应用得非常频繁,在以后的章节中我们也将多次用到多线程。有了这两期的知识,我们就可以开始编写网络通信软件了,下一期我们将实现类似QQ的实时通信功能。客户端
认识:多线程网络编程

