多线程套接字编程-----程序实例(C++实现)

时间:2022-09-26 10:59:21

一.程序介绍

该程序主要包括以下内容:

1.多线程程序设计的一般框架,新线程负责循环接收网络数据,一旦收到网络数据就交由主线程处理;主线程负责循环处理网络数据。

2.(UDP)套接字编程的一般框架,为了方便实验该程序只是用到了UDP套接字,没有考虑丢包延迟等网络问题,在实际程序设计中可以采用TCP套接字。

3.如何使用套接字发送多个不同的结构体,通过对不同的结构体添加不同的标志位,从而区分不同的结构体类型。

该程序的默认设置为:

1.各参与者IP地址为127.0.0.1(环回地址,用于单机测试)。
2.编号为i的参与者端口号为10000+i,例如,编号为1的参与者其端口号为10001。由于是采取多机测试,所以每个进程的IP地址都是127.0.0.1,所以需要使用不同的端口,从而区分出不同的进程,这样才能保证网络数据发送到正确的参与者处理。
3.编号为1的参与者向编号为2的参与者发送测试数据。编号为1的参与者向2发送三个不同的结构体,参与者2收到后首先提取结构体的标志位,然后确定用何种结构体变量来接收网络数据。提取到正确的结构体后,打印结构体中的数据。

二.程序源码

1.Node.h

#ifndef NORMALNODE
#define NORMALNODE
#include "winsock.h"
#include "windows.h"
#include <iostream>
#include <string>
using namespace std;

const WM_PARTY_MSG=WM_USER+1;

struct m_struct1
{
int flag;
char Value1[15];
};

struct m_struct2
{
int flag;
char Value1[15];
char Value2[15];
};

struct m_struct3
{
int flag;
char Value1[15];
char Value2[15];
char Value3[15];
};

struct ThreadParameter
{
int Port;
DWORD MainThreadID;
};

class Node
{
private:
int ID;//自身ID
public:
Node();
~Node();
void StartMyThread();
int DealMessage(char *MyMessage,int MessageLength);
static DWORD WINAPI StartAcceptThread(LPVOID lpData);
int SendStruct();
int TransmitMessage(int ParticipatorID,char *StructBuffer,int BufferLength);
};

#endif

2.Node.cpp

#include "Node.h"
const MSG_STRUCT1=1;
const MSG_STRUCT2=2;
const MSG_STRUCT3=3;

Node::Node()//构造函数
{
cout<<"***************************************************"<<endl;
cout<<"\n该程序主要包括:\n"<<endl;
cout<<"1.多线程套接字程序设计框架"<<endl;
cout<<"2.利用套接字发送结构体的方法"<<endl;
cout<<"3.多个不同结构体的发送和接收"<<endl;
cout<<"\n默认设置为:\n"<<endl;
cout<<"1.各参与者IP地址为127.0.0.1(环回地址,用于单机测试)"<<endl;
cout<<"2.编号为i的参与者端口号为10000+i"<<endl;
cout<<"3.编号为1的参与者向编号为2的参与者发送测试数据"<<endl;
cout<<"***************************************************"<<endl;
cout<<"输入编号:"<<endl;
cin>>ID;
}

Node::~Node()//析构函数
{

}

void Node::StartMyThread()//启动新线程
{
DWORD myMainThreadID=::GetCurrentThreadId(); //获取当前线程也就是主线程的ID号
static ThreadParameter tp;//此处需设置为静态变量
tp.MainThreadID=myMainThreadID;
tp.Port=10000+ID;//端口号初始化
HANDLE hThread = CreateThread(NULL,0,StartAcceptThread,(LPVOID)&tp,0,NULL);//创建新线程
CloseHandle(hThread);
}

DWORD WINAPI Node::StartAcceptThread(LPVOID lpData)//线程的启动函数,用来循环接收来自参与者发来的轮消息
{
ThreadParameter tp=*((ThreadParameter *)lpData);
char RecveBuffer[20][4096];//为了保证,消息能够被安全处理,也就是在消息被线程处理之前,保证没有新的消息覆盖,我们定义了一个二维数组,相当于二十个缓冲区,用来接收数据
for(int i=0;i<20;i++)
memset(RecveBuffer[i],0,4096);
SOCKET RecveSocket = ::socket(AF_INET, SOCK_DGRAM, 0);//创建UDP套接字
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_addr.S_un.S_addr = INADDR_ANY;
sin.sin_port = ::htons(tp.Port);//端口号设置
if(::bind(RecveSocket, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)//地址与套接字绑定
{
cout<<"套接字绑定错误!"<<endl;
return 0;
}
/*以下是循环接收网络数据部分,在通信量大的时候,
**可能数据不能及时传至主线程,而造成信息的覆盖
**所以此处设置了一个二位数组RecveBuffer,相当于
**20个长度为4096的缓冲区,可以适应通信量较大的情况。
*/
SOCKADDR_IN addrRemote;
int nLen = sizeof(addrRemote);
int recvcount=0;
while(true)
{
if(recvcount==20)
recvcount=0;
//套接字接收函数
int nRet = ::recvfrom(RecveSocket, RecveBuffer[recvcount],4096, 0, (sockaddr*)&addrRemote, &nLen);
if(nRet==SOCKET_ERROR)
{
cout<<"接受错误!"<<endl;
return 0;
}
if(nRet > 0)
{
//收到网络数据以后向主线程发送消息,并将数据交由主线程处理
if(PostThreadMessage(tp.MainThreadID,WM_PARTY_MSG,0,(LPARAM)RecveBuffer[recvcount])==0)
{
cout<<"向主线程发送消息失败"<<GetLastError()<<endl;
}
recvcount++;
}
}
return 1;
}

//消息处理函数,该函数用处理不同的网络数据,对其进行分类响应
int Node::DealMessage(char *MyMessage,int MessageLength)
{
int StrcutFlag;
//获取网络数据的标志位,根据不同的标志位进行不同的处理
memcpy(&StrcutFlag,MyMessage,sizeof(StrcutFlag));
if(StrcutFlag==MSG_STRUCT1)//结构体1的数据
{
m_struct1 ms1;
memcpy(&ms1,MyMessage,sizeof(ms1));//取出结构体1
cout<<ms1.Value1<<endl;
}
else if(StrcutFlag==MSG_STRUCT2)//结构体2的数据
{
m_struct2 ms2;
memcpy(&ms2,MyMessage,sizeof(ms2));//取出结构体2
cout<<ms2.Value2<<endl;
}
else if(StrcutFlag==MSG_STRUCT3)//结构体3的数据
{
m_struct3 ms3;
memcpy(&ms3,MyMessage,sizeof(ms3));//取出结构体3
cout<<ms3.Value3<<endl;
return 0;
}
return 1;
}

//结构体发送函数
int Node::SendStruct()
{
char buf1[60],buf2[60],buf3[60];
if(ID!=1)
return 0;
//定义三种类型的结构体
m_struct1 s1;
m_struct2 s2;
m_struct3 s3;
//对不同的结构体进行不同的赋值
s1.flag=1;
strcpy(s1.Value1,"结构体1");
s2.flag=2;
strcpy(s2.Value2,"结构体2");
s3.flag=3;
strcpy(s3.Value3,"结构体3");
memset(buf1,0,60);
memcpy(buf1,&s1,sizeof(s1));//将结构体1送入char数组,进行发送
TransmitMessage(2,buf1,sizeof(s1));//发送结构体1
cout<<"发送结构体1"<<endl;
memset(buf2,0,60);
memcpy(buf2,&s2,sizeof(s2));//将结构体2送入char数组,进行发送
TransmitMessage(2,buf2,sizeof(s2));//发送结构体2
cout<<"发送结构体2"<<endl;
memset(buf3,0,60);
memcpy(buf3,&s3,sizeof(s3));//将结构体3送入char数组,进行发送
TransmitMessage(2,buf3,sizeof(s3));//发送结构体4
cout<<"发送结构体3"<<endl;
return 1;
}

//消息发送函数
int Node::TransmitMessage(int ParticipatorID,char *StructBuffer,int BufferLength)
{
char SendBuffer[1024];
memset(SendBuffer,0,1024);//将消息放入特定长度的缓冲区进行发送
memcpy(SendBuffer,StructBuffer,BufferLength);
SOCKET SendSocket = ::socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN bcast;
bcast.sin_family = AF_INET;
bcast.sin_port=htons(10000+ParticipatorID);//目标地址的端口号设置
bcast.sin_addr.S_un.S_addr=::inet_addr("127.0.0.1");//目标地址设置
//发送数据
if(sendto(SendSocket, SendBuffer,1024, 0, (sockaddr*)&bcast, sizeof(bcast))==SOCKET_ERROR)
{
cout<<"数据发送错误"<<endl;
return 0;
}
return 1;
}


3.testmian,cpp

#include "Node.h"
int main()
{
int ret;
WSADATA wsa;
//初始化套接字DLL
if(WSAStartup(MAKEWORD(2,2),&wsa)!=0)
{
cout<<"套接字初始化失败!"<<endl;
}
char MessageBuffer[1024];
Node A;
MSG ThreadMessage;
A.StartMyThread();//启动新线程
ret=A.SendStruct();//发送结构体数据
if(ret==1)
{
system("pause");
return 1;
}
while(1)//循环接收线程的消息
{
if(PeekMessage(&ThreadMessage,NULL,0,0,PM_REMOVE))//搜集线程的消息
{
if(ThreadMessage.message==WM_PARTY_MSG)//判断消息类型
{
memcpy(MessageBuffer,(char *)ThreadMessage.lParam,1024);//提取需要处理的网络信息
ret=A.DealMessage(MessageBuffer,1024);//处理网络信息
if(ret==0)
break;
}
}
}
system("pause");
}

三.运行截图

该程序在VC60平台下编译通过

多线程套接字编程-----程序实例(C++实现)多线程套接字编程-----程序实例(C++实现)