众所周知,使用多进程的服务端模型有利于程序的健壮性。传统的做法是主进程负责收发数据,然后传给子进程来处理。这种做法的缺陷是需要大量的父子进程IPC,对效率来说是一种损失。
这里,我提出另外一种比较独特的做法,就是多个进程share socket,每次进程都可以accept,然后来自己处理。
几个关键点:
1) CreateProcess使用InheritHandle标记来share socket handle
2) 通过command line直接向子进程来传递父socket的值
3)使用Global Mutext来实现子进程互斥的accept
可以改进的地方
1) 使用动态进程池来程序具有更大的伸缩性
2)监控子进程的状态,处理僵死进程
多进程共享一个socket,急啊!
在网络游戏编程中,假如客户端提供房间让玩家登录游戏,比如说四国军旗,进入游戏后会产生一个新的进程,我想问:客户端进程和四国军旗游戏进程通过wsaduplicatesocket共享一个socket和服务器端进行通信,某个时间只能是一个进程利用这个socket和服务端通信,怎么来协调这个socket,使客户端进程和四国军旗游戏进程很好的交替和服务器很好的通信?想了很久,查了一些资料,但不知道怎么来做?急啊!如果可以提供一部分代码,那非常感激!这是我所有的分了,各位帮帮忙!
------解决方案--------------------------------------------------------
共享原来socket是unix上边的概念,unix下边的早期网络服务器一般都是由父进程
bind
listen
accept
然后子进程read,write,这两个进程共享socket,但是只有一个进程读写。包括unix下边现在的inetd进程也是这样的。
另外有很多大型服务器也是这样的架构,有点有很多了
不可以简单的将socket的值利用。应该通过DuplicateHandle进行。
进程1:
WSAStartup();
s1 = socket();
connect();
进程2:
WSAStartup();
hh = OpenProcess(p1Id);
DuplicateHandle(hh, s1, -1, &s2, PROCESS_ALL_ACCESS, FALSE, DUPLICATE_SAME_ACCESS);
connect(s2);
...
注意:
进程2中的s1和p1Id为进程1中的s1值和进程1的进程ID.
修改,进程2中可以直接利用进程1的socket进行send或recv。不用重新connect。
进程2:
WSAStartup();
hh = OpenProcess(p1Id);
DuplicateHandle(hh, s1, -1, &s2, PROCESS_ALL_ACCESS, FALSE, DUPLICATE_SAME_ACCESS);
send(s2);
下面是一个echo server 的例子来展示这项技术, FYI
父进程(SSParent.cpp)
#include
<
stdio.h
>
#include
<
winsock2.h
>
#include
<
windows.h
>
#include
<
process.h
>
#define
MUTEX_NAME "sschild"
int
main(
int
argc,
char
*
argv[])
{
{
//
init
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested
=
MAKEWORD(
2
,
2
);
WSAStartup( wVersionRequested,
&
wsaData );
}
SOCKET s
=
socket(AF_INET,SOCK_STREAM,
0
);
if
(s
==
INVALID_SOCKET)
{
printf(
"
create socket failed!\n
"
);
return
-
1
;
}
{
//
bind&listen
sockaddr_in sa;
sa.sin_family
=
AF_INET;
sa.sin_port
=
htons(
1500
);
sa.sin_addr.s_addr
=
0
;
int
rc
=
bind(s,(sockaddr
*
)
&
sa,
sizeof
(sa));
if
(rc
==
SOCKET_ERROR)
{
printf(
"
bind failed:%d\n
"
,::WSAGetLastError());
return
-
1
;
}
listen(s,SOMAXCONN);
}
HANDLE hSocketMutex;
{
//
create mutex
hSocketMutex
=
::CreateMutex(NULL,FALSE,MUTEX_NAME);
if
(hSocketMutex
==
NULL)
{
printf(
"
fail CreateMutex:%d\n
"
,::GetLastError());
return
-
1
;
}
}
const
int
CHILD_NUMBER
=
5
;
HANDLE hProcess[CHILD_NUMBER];
{
//
create child process
STARTUPINFO si
=
{
sizeof
(si) };
PROCESS_INFORMATION piProcess[CHILD_NUMBER];
char
pCmdLine[
256
];
sprintf(pCmdLine,
"
SSChild %d
"
,s);
for
(
int
i
=
0
;i
<
CHILD_NUMBER;
++
i)
{
if
(
!
CreateProcess(NULL,pCmdLine,NULL,NULL,TRUE,
0
, NULL, NULL,
&
si,
&
piProcess[i]))
{
printf(
"
fail CreateProcess:%d\n
"
,::GetLastError());
return
-
1
;
}
hProcess[i]
=
piProcess[i].hProcess;
CloseHandle(piProcess[i].hThread);
}
}
::WaitForMultipleObjects(CHILD_NUMBER,hProcess,TRUE,INFINITE);
{
//
close all child handle
for
(
int
i
=
0
;i
<
CHILD_NUMBER;
++
i)
{
CloseHandle(hProcess[i]);
}
}
//
clean
CloseHandle(hSocketMutex);
closesocket(s);
WSACleanup( );
return
0
;
}
子进程(SSChild.cpp)
#include
<
stdio.h
>
#include
<
winsock2.h
>
#include
<
windows.h
>
#include
<
process.h
>
#define
MUTEX_NAME "sschild"
int
main(
int
argc,
char
*
argv[])
{
printf(
"
sschild startup!\n
"
);
{
//
init
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested
=
MAKEWORD(
2
,
2
);
WSAStartup( wVersionRequested,
&
wsaData );
}
DWORD pid
=
::GetCurrentProcessId();
HANDLE hSocketMutex;
{
//
open mutex
hSocketMutex
=
::OpenMutex(MUTEX_ALL_ACCESS,FALSE,MUTEX_NAME);
if
(hSocketMutex
==
NULL)
{
printf(
"
fail OpenMutex:%d\n
"
,::GetLastError());
return
-
1
;
}
}
SOCKET s;
{
//
get socket handle from cmdline
if
(argc
<=
1
)
{
printf(
"
usage: sschild socket_handle\n
"
);
return
-
1
;
}
s
=
(SOCKET) atoi(argv[
1
]);
}
while
(
1
)
{
WaitForSingleObject(hSocketMutex,INFINITE);
sockaddr_in sa;
int
add_len
=
sizeof
(sa);
SOCKET c
=
accept(s,(sockaddr
*
)
&
sa,
&
add_len);
ReleaseMutex(hSocketMutex);
if
(c
!=
INVALID_SOCKET)
{
printf(
"
[%d],client:%s port:%d connected!\n
"
,pid,inet_ntoa(sa.sin_addr),sa.sin_port);
while
(
1
)
{
char
buffer[
256
]
=
{
0
};
int
rc
=
recv(c,buffer,
255
,
0
);
if
(rc
>
0
)
{
printf(
"
[%d]recv msg:%s\n
"
,pid,buffer);
send(c,buffer,strlen(buffer)
+
1
,
0
);
}
else
if
(rc
==
SOCKET_ERROR)
{
printf(
"
[%d]recv msg failed:%d\n
"
,pid,::WSAGetLastError());
closesocket(c);
break
;
}
else
{
printf(
"
[%d]connection close\n
"
,pid);
closesocket(c);
break
;
}
}
}
else
{
printf(
"
[%d]fail accept:%d\n
"
,pid,::WSAGetLastError());
}
}
CloseHandle(hSocketMutex);
return
0
;
}
多进程方式编程简单,程序健壮性相对比较好,但是切换开销比较大。