多线程套接字编程-----程序实例(C++实现)多线程套接字编程-----程序实例(C++实现)标签:多线程编程c++structsocket网络2012-04-12 12:54 9921人阅读评论(1) 收藏举报分类:C/C++(18)网络版权声明:本文为博主原创文章,未经博主允许不得转载。
一.程序介绍该程序主要包括以下内容: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[plain] view plain copy#ifndef NORMALNODE #define NORMALNODE #include "winsock.h" #include "windows.h" #include<iostream> #include <string> using namespace std; constWM_PARTY_MSG=WM_USER+1; struct m_struct1 { int flag; char Value1[15]; }; structm_struct2 { int flag; char Value1[15];char Value2[15]; }; struct m_struct3 { int flag; char Value1[15]; char Value2[15]; charValue3[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); }; #endif2.Node.cpp[plain] view plain copy#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_MS G,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() { charbuf1[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));//发送结构体1cout<<"发送结构体1"<<endl;memset(buf2,0,60);memcpy(buf2,&s2,sizeof(s2));//将结构体2送入char 数组,进行发送TransmitMessage(2,buf2,sizeof(s2));//发送结构体2cout<<"发送结构体2"<<endl;memset(buf3,0,60);memcpy(buf3,&s3,sizeof(s3));//将结构体3送入char 数组,进行发送TransmitMessage(2,buf3,sizeof(s3));//发送结构体4cout<<"发送结构体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[plain] view plain copy#include "Node.h" int main() { int ret; WSADATA wsa; //初始化套接字DLLif(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平台下编译通过顶3踩。