在线等:多个socket绑定到同一个IP和端口,这样可以吗?

zhangbinnpu 2009-03-11 09:55:09
我想实现多线程传输。

在客户端,把两个套接字绑定到了同一个IP和端口,然后开启两个线程,两个线程分别使用两个套接字进行数据接收。
在服务器端,等待两次连接,然后开启两个线程,进行数据传输

这种情况下,如果服务器端两个线程同时传不同的数据,会不会在客户端接收时发生数据混乱的现象?

哪位能介绍下socket多线程的知识,或者给推荐点资料,谢谢了

客户端代码如下:
if( WSAStartup( MAKEWORD( 2, 2 ), &wsadata ) != 0 )
{
MessageBox( NULL, "WSAStartUp() 错误!", "错误", MB_OK );
return false;
}

m_Socket = socket (AF_INET, SOCK_STREAM, 0); // 第一个套接字
if( m_Socket < 0 )
{
MessageBox( NULL, "创建socket错误!", "错误", MB_OK );
return false;
}

timeout = 1000;
setsockopt( m_Socket, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout) );
setsockopt( m_Socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout) );

sndbuf = 32 * 1024;
rcvbuf = 256 * 1024;
setsockopt( m_Socket, SOL_SOCKET, SO_SNDBUF, ( const char* )&sndbuf, sizeof( int ) );
setsockopt( m_Socket, SOL_SOCKET, SO_RCVBUF, ( const char* )&rcvbuf, sizeof( int ) );

memset( (char*)&m_ServerAddr, 0, sizeof( sockaddr ) );
m_ServerAddr.sin_family = AF_INET;
m_ServerAddr.sin_port = htons (SERVER_PORT_NUM);
m_ServerAddr.sin_addr.s_addr = (inet_addr)(addrIP);

if (connect (m_Socket, (struct sockaddr *) &m_ServerAddr, sizeof (sockaddr)) == SOCKET_ERROR)
{
MessageBox( NULL, "无法连接设备,请检查设备是否开启!", "Socket错误", MB_OK );
closesocket ( m_Socket );
return false;
}
else
{
MessageBox( NULL, "Socket连接上", "Socket", MB_OK );
}




m_Socket1 = socket (AF_INET, SOCK_STREAM, 0); // 第二个套接字
if( m_Socket1 < 0 )
{
MessageBox( NULL, "创建socket1错误!", "错误", MB_OK );
return false;
}

timeout = 1000;
setsockopt( m_Socket1, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout) );
setsockopt( m_Socket1, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout) );

sndbuf = 32 * 1024;
rcvbuf = 256 * 1024;
setsockopt( m_Socket1, SOL_SOCKET, SO_SNDBUF, ( const char* )&sndbuf, sizeof( int ) );
setsockopt( m_Socket1, SOL_SOCKET, SO_RCVBUF, ( const char* )&rcvbuf, sizeof( int ) );

memset( (char*)&m_ServerAddr, 0, sizeof( sockaddr ) );
m_ServerAddr.sin_family = AF_INET;
m_ServerAddr.sin_port = htons (SERVER_PORT_NUM);
m_ServerAddr.sin_addr.s_addr = (inet_addr)(addrIP);

if (connect (m_Socket1, (struct sockaddr *) &m_ServerAddr, sizeof (sockaddr)) == SOCKET_ERROR)
{
MessageBox( NULL, "无法连接设备,请检查设备是否开启!", "Socket1错误", MB_OK );
closesocket ( m_Socket1 );
return false;
}
else
{
// add by ZB
socket1=m_Socket1;
MessageBox( NULL, "Socket1连接上", "Socket1", MB_OK );
}
...全文
5559 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhxingway 2011-01-23
  • 打赏
  • 举报
回复
呵呵,1楼的代码值得学习,虽然跟问题不太相符.
wanglei5695312 2010-07-12
  • 打赏
  • 举报
回复
学习ing
Vinc 2010-06-01
  • 打赏
  • 举报
回复
[Quote=引用楼主 zhangbinnpu 的回复:]
我想实现多线程传输。

在客户端,把两个套接字绑定到了同一个IP和端口,然后开启两个线程,两个线程分别使用两个套接字进行数据接收。
在服务器端,等待两次连接,然后开启两个线程,进行数据传输

这种情况下,如果服务器端两个线程同时传不同的数据,会不会在客户端接收时发生数据混乱的现象?
[/Quote]

不会:客户端的两个SOCKET对应服务器的两个SOCKET,是一对一的关系,如果服务端两个线程传不同的数据,他们不会互相干扰。但如果服务端的两个线程发送的同一个数据块内的不同部分那你就要自己注意了。
TRUE 2010-01-21
  • 打赏
  • 举报
回复
socket只能绑定一个端口和IP,但是如果一个是TCP,一个是UDP则是可以的,按照你的意思,应该都是同一个协议,因此你的第二个绑定肯定是失败的,再者,你的服务器需要加入锁,来防止数据的发送的混乱,因此你出现混乱的原因就是服务器在发送数据的时候,已经是乱的了,因此你最好解决这2个问题才可以。
zhangds379 2009-03-17
  • 打赏
  • 举报
回复
同一个ip与端口只能绑定一个socket,
while(true)
{
//等待连接,每个连接上的客户端将对应的生成一个socket
SOCKET client=accept(server,(sockaddr *)&clientaddr,&len);
//创建线程,对生成的socket进行操作
...
}
ID大头哈哈 2009-03-15
  • 打赏
  • 举报
回复
对于接收的话,一个套接字足够了。
wchnjstar 2009-03-12
  • 打赏
  • 举报
回复
同一ip和端口只能绑定一次,否则系统收到数据无法区分是给谁的。
djfu 2009-03-11
  • 打赏
  • 举报
回复
没问题啊,线程都有自己的堆栈。
zhangbinnpu 2009-03-11
  • 打赏
  • 举报
回复
可能我没说清楚。


我在客户端开启了两个线程,这两个线程使用了两个套接字进行数据接收(假设分别接收A数据块和B数据块),这两个套接字使用了相同的IP和端口。

服务器端创建两个线程,等待两次连接,然后发送数据,假设发送A数据块和B数据块。

我的问题是,这样可以吗?

如果可以,在客户端会不会把A数据块的数据接收到了B数据块中?

djfu 2009-03-11
  • 打赏
  • 举报
回复
何必呢?
你这个其实可以归并为多客户端下载的问题或者多线程下载的问题。

一个客户端,分成多个线程连接到服务器,下载同一个文件的不同组成段。
华亭真人 2009-03-11
  • 打赏
  • 举报
回复
没看你程序。。。不过MS 一个端口只能Bind一个Socket
总哈哈 2009-03-11
  • 打赏
  • 举报
回复
一个端口只能给一个程序使用,好像是这样的。
upgrade_007 2009-03-11
  • 打赏
  • 举报
回复
不用担心发生混乱,因为服务端在发送的时候是要指定套接字client的,你发给那个线程,就由哪个线程来接收
clhposs 2009-03-11
  • 打赏
  • 举报
回复
多线程传输实现

实现原理

将源文件按长度为分为N块文件,然后开辟N个线程,每个线程传输一块,最后合并所有线线程文件.比如
一个文件500M我们按长度可以分5个线程传输.第一线程从0-100M,第二线程从100M-200M......最后合并5个线程文件.

实现流程

1.客户端向服务端请求文件信息(名称,长度)
2.客户端跟据文件长度开辟N个线程连接服务端
3.服务端开辟新的线程与客户端通信并传输文件
4.客户端将每线程数据保存到一个文件
5.合并所有线程文件

编码实现

大体说来就是按以上步骤进行,详细的实现和一些要点,我们跟据以上流程在编码中实现


结构定义

在通信过程中需要传递的信息包括文件名称,文件长度,文件偏移,操作指令等信息,为了方便操作我们定义如下结构

typedef struct
{
char Name[100]; //文件名称
int FileLen; //文件长度
int CMD; //操作指令
int seek; //线程开始位置
SOCKET sockid;
}FILEINFO;


1.请求文件信息

客户端代码如下

FILEINFO fi;
memset((char*)&fi,0,sizeof(fi));
fi.CMD=1; //得到文件信息

if(send(client,(char*)&fi,sizeof(fi),0)==SOCKET_ERROR)
{
cout<<"Send Get FileInfo Error\n";
}
服务端代码如下

while(true)
{
SOCKET client;
if(client=accept(server,(sockaddr *)&clientaddr,&len))
{
FILEINFO RecvFileInfo;
memset((char*)&RecvFileInfo,0,sizeof(RecvFileInfo));
if(recv(client,(char*)&RecvFileInfo,sizeof(RecvFileInfo),0)==SOCKET_ERROR)
{
cout<<"The Clinet Socket is Closed\n";
break;
}else
{
EnterCriticalSection(&CS); //进入临界区
memcpy((char*)&TempFileInfo,(char*)&RecvFileInfo,sizeof(RecvFileInfo));
switch(TempFileInfo.CMD)
{
case 1:
GetInfoProc (client);
break;
case 2:
TempFileInfo.sockid=client;
CreateThread(NULL,NULL,GetFileProc,NULL,NULL,NULL);
break;
}
LeaveCriticalSection(&CS); //离开临界区
}
}
}
在这里服务端循环接受连接,并跟据TempFileInfo.CMD来判断客户端的请求类型,1为请求文件信息,2为下载文件
因为在下载文件的请求中,需要开辟新的线程,并传递文件偏移和文件大小等信息,所以需要对线程同步.这里使用临界区
其文件信息函数GetInfoProc代码如下

DWORD GetInfoProc(SOCKET client)
{
CFile file;
if(file.Open(FileName,CFile::modeRead|CFile::typeBinary))
{
int FileLen=file.GetLength();
if(send(client,(char*)&FileLen,sizeof(FileLen),0)==SOCKET_ERROR)
{
cout<< "Send FileLen Error\n";
}else
{
cout<< "The Filelen is "<<FileLen<<"\n\n";
}
}
return 0;
}
这里主要是向客户端传递文件长度,而客户端收到长度后则开辟线程进行连接传输文件

2.客户端跟据长度开辟线程

其实现代码如下
FILEINFO FI;
int FileLen=0;
if(recv(client,(char*)&FileLen,sizeof(FileLen),0)==SOCKET_ERROR)//接受文件长度
{
cout<<"Recv FileLen Error\n";
}else
{
cout<<"FileLen is "<<FileLen<<"\n";
int COUNT_SIZE=FileLen/5; //每线程传输大小
for(int i=0;i<5;i++) //分5个线程传输
{
EnterCriticalSection(&CS); //进入临界区
memset((char*)&FI,0,sizeof(FI));
FI.CMD=2; //请求下载文件
FI.seek=i*COUNT_SIZE; //线程文件偏移
if(i+1==5) //最后一线程长度为总长度减前4个线程长度
{
FI.FileLen=FileLen-COUNT_SIZE*i;
}else
{
FI.FileLen=COUNT_SIZE;
}
Thread[i]=CreateThread(NULL,NULL,GetFileThread,&i,NULL,NULL);
Sleep(500);
LeaveCriticalSection(&CS); //离开临界区
}
}
WaitForMultipleObjects(5,Thread,true,INFINITE); //等所有线程结束

这里默认开辟5个线程传输,当然可以改为想要的线程数目,仍然用临界区来实现线程的同步问题

3.服务端开辟线程传输数据

在1.请求文件信息中以说明了服务端的结构,这里主要介绍线程函数的实现,其代码如下

DWORD WINAPI GetFileProc(LPVOID lparam)
{
EnterCriticalSection(&CS); //进入临界区
int FileLen=TempFileInfo.FileLen;
int Seek=TempFileInfo.seek;
SOCKET client=TempFileInfo.sockid;
LeaveCriticalSection(&CS); //离开临界区

CFile file;
if(file.Open(FileName,CFile::modeRead|CFile::typeBinary))
{
file.Seek(Seek,CFile::begin); //指针移至偏移位置
char *date=new char[FileLen];
int nLeft=FileLen;
int idx=0;
file.Read(date,FileLen);
while(nLeft>0)
{
int ret=send(client,&date[idx],nLeft,0);
if(ret==SOCKET_ERROR)
{
cout<<"Send Date Error \n";
break;
}
nLeft-=ret;
idx+=ret;
}
file.Close();
delete[] date;
}else
{
cout<<"open the file error\n";
}
closesocket(client);
return 0;
}
还是比较简单的,主要是获取线程的文件长度和偏移,并移动文件指针到偏移处,最后读取发送数据,而客户端
接受数据并写入文件.


4.客户端将线程数据保存到文件

GetFileThread的实现代码如下

DWORD WINAPI GetFileThread(LPVOID lparam)
{
char TempName[MAX_PATH];
sprintf(TempName,"TempFile%d",*(DWORD*)lparam); //每线程的文件名为"TempName"+线程数
SOCKET client;
SOCKADDR_IN serveraddr;
int port=5555;
client=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=htons(port);
serveraddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
if(connect(client,(SOCKADDR*)&serveraddr,sizeof(serveraddr))==INVALID_SOCKET)
{
cout<<"Connect Server Error\n";
}
EnterCriticalSection(&CS); //进入临界区
if(send(client,(char*)&FI,sizeof(FI),0)==SOCKET_ERROR)
{
cout<<"Send GetFile Error\n";
return 0;
}
CFile file;
int FileLen=FI.FileLen; //文件长度
int Seek=FI.seek; //文件偏移
LeaveCriticalSection(&CS); //离开临界区

if(file.Open(TempName,CFile::modeWrite|CFile::typeBinary|CFile::modeCreate))
{
char *date = new char[FileLen];
int nLeft=FileLen;
int idx=0;
while(nLeft>0)
{
int ret=recv(client,&date[idx],nLeft,0);
if(ret==SOCKET_ERROR)
{
cout<<"Recv Date Error";
break;
}
idx+=ret;
nLeft-=ret;
}
file.Write(date,FileLen);
file.Close();
delete[] date;
}else
{
cout<<"Create File Error\n";
}
return 0;
}
在此线程函数中,将每线程传输的数据存为一个文件,文件名为"TempName"+线程数,只所以存成单独的文件是
因为比较直观且容易理解,但如果文件很大的话这个方法并不好,因为合并文件又会花费很多时间,另一个方法
是 创始一个文件,让每个线程写入文件的不同偏移,这样就可以不必单独合并文件了,但要记得打开文件时
加入CFile::shareDenyNone属性.这样整个过程就完成了.最后一步合并线程文件

5.合并线程文件

int UniteFile() //合并线程文件
{
cout<<"Now is Unite Fileing...\n";
int len;
char *date;
CFile file;
CFile file0;
/*其它文件......*/

if(file.Open(FileName,CFile::modeCreate|CFile::typeBinary|CFile::modeWrite))//创建文件
{
file0.Open("TempFile0",CFile::modeRead|CFile::typeBinary);//合并第一线程文件
len=file0.GetLength();
date=new char[len];
file0.Read(date,len);
file.SeekToEnd();
file.Write(date,len);
file1.Open("TempFile1",CFile::modeRead|CFile::typeBinary);//合并第二线程文件
len=file1.GetLength();
date=new char[len];
file1.Read(date,len);
file.SeekToEnd();
file.Write(date,len);
/*合并其它线程......*/


file0.Close();
file1.Close();
/*.......*/
delete[] date;

return true;
}else
{
return false;
}

}

这个简单,就是打开一个文件读取到缓冲区,写入文件,再打开第二个......现在多线程传输部分就介绍完了
下面讨论断断点续传的实现

18,355

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧