练好网络编程基本功——实现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)。

20-g16-1-1.jpg
图1
20-g16-1-2.jpg
图2

当然,我在这里只是介绍了非常简单的多线程应用,多线程在网络编程中应用得非常频繁,在以后的章节中我们也将多次用到多线程。有了这两期的知识,我们就可以开始编写网络通信软件了,下一期我们将实现类似QQ的实时通信功能。