单个服务器对多个客户端程序:
一。简要说明
二。查看效果
三。编写思路
四。程序源代码
五。存在问题
一。简要说明:
程序名为:TcpSocketOneServerToMulClient
程序功能:实现单个服务器对多个客户端通讯功能的小程序。
PS: 这是继上次简单的 Tcp Windows Socket 编程后的再一程序,程序实现依然不是很严谨,还待完善~
二。查看效果:
三。编写思路:
由上一次的程序思路来看,如果想实现单个服务器对多个客户端程序的通讯的话,这次程序编写尝试从多线程的角度来考虑问题:
在服务器的实现中:可以main函数作为主线程,不断地接客户端的连接请求。
再新建子线程——每连接一个客户端,就专为这个客户端新建一个用于实现接收信息并显示到屏幕上功能的子线程。
然后,新建子线程,专用于本机发送消息。
在客户端的实现中:主线程负责连接服务器,新建子线程,用于从服务器接收信息,再建子线程,用于从客户端向服务器中发送信息。
总的来说,也可以理解为,单个服务器的进程利用这个服务器中的线程与多个客户端进程进行通讯。
四。程序源代码:
// OneServerMain.cpp
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <iterator>
#include <algorithm>
#include <Winsock2.h>
#include <Windows.h>
using namespace std;
HANDLE bufferMutex; // 令其能互斥成功正常通信的信号量句柄
SOCKET sockConn; // 客户端的套接字
vector <SOCKET> clientSocketGroup;
int main()
{
// 加载socket动态链接库(dll)
WORD wVersionRequested;
WSADATA wsaData; // 这结构是用于接收Wjndows Socket的结构信息的
wVersionRequested = MAKEWORD( 2, 2 ); // 请求2.2版本的WinSock库
int err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return -1; // 返回值为零的时候是表示成功申请WSAStartup
}
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { // 检测是否2.2版本的socket库
WSACleanup( );
return -1;
}
// 创建socket操作,建立流式套接字,返回套接字号sockSrv
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
// 套接字sockSrv与本地地址相连
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 将INADDR_ANY转换为网络字节序,调用 htonl(long型)或htons(整型)
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
if(SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){ // 第二参数要强制类型转换
return -1;
}
// 将套接字设置为监听模式(连接请求), listen()通知TCP服务器准备好接收连接
listen(sockSrv, 20);
cout << "服务器已成功就绪,若服务器想发送信息给客户端,可直接输入内容后按回车.\n";
// accept(),接收连接,等待客户端连接
bufferMutex = CreateSemaphore(NULL, 1, 1, NULL);
DWORD WINAPI SendMessageThread(LPVOID IpParameter);
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter);
HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);
while(true){ // 不断等待客户端请求的到来
sockConn = accept(sockSrv, NULL, NULL);
if (SOCKET_ERROR != sockConn){
clientSocketGroup.push_back(sockConn);
}
HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, (LPVOID)sockConn, 0, NULL);
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用)
if(NULL == receiveThread) {
printf("\nCreatThread AnswerThread() failed.\n");
}
else{
printf("\nCreate Receive Client Thread OK.\n");
}
ReleaseSemaphore(bufferMutex, 1, NULL); // V(资源占用完毕)
}
WaitForSingleObject(sendThread, INFINITE); // 等待线程结束
CloseHandle(sendThread);
CloseHandle(bufferMutex);
WSACleanup(); // 终止对套接字库的使用
printf("\n");
system("pause");
return 0;
}
DWORD WINAPI SendMessageThread(LPVOID IpParameter)
{
while(1){
string talk;
getline(cin, talk);
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用)
/* if("quit" == talk){
ReleaseSemaphore(bufferMutex, 1, NULL); // V(资源占用完毕)
return 0;
}
else*/
{
talk.append("\n");
}
printf("I Say:(\"quit\"to exit):");
cout << talk;
for(int i = 0; i < clientSocketGroup.size(); ++i){
// send(clientSocketGroup[i], talk.c_str(), talk.size(), 0); // 发送信息
send(clientSocketGroup[i], talk.c_str(), 200, 0); // 发送信息
}
ReleaseSemaphore(bufferMutex, 1, NULL); // V(资源占用完毕)
}
return 0;
}
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)
{
SOCKET ClientSocket=(SOCKET)(LPVOID)IpParameter;
while(1){
char recvBuf[300];
recv(ClientSocket, recvBuf, 200, 0);
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用)
if (recvBuf[0] == 'q' && recvBuf[1] == 'u' && recvBuf[2] == 'i' && recvBuf[3] == 't' && recvBuf[4] == '\0'){
vector<SOCKET>::iterator result = find(clientSocketGroup.begin(), clientSocketGroup.end(), ClientSocket);
clientSocketGroup.erase(result);
closesocket(ClientSocket);
ReleaseSemaphore(bufferMutex, 1, NULL); // V(资源占用完毕)
printf("\nAttention: A Client has leave...\n", 200, 0);
break;
}
printf("%s Says: %s\n", "One Client", recvBuf); // 接收信息
ReleaseSemaphore(bufferMutex, 1, NULL); // V(资源占用完毕)
}
return 0;
}
// MulClientMain.cpp
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <winsock2.h>
#include <Windows.h>
using namespace std;
SOCKET sockClient; // 连接成功后的套接字
HANDLE bufferMutex; // 令其能互斥成功正常通信的信号量句柄
int main()
{
// 加载socket动态链接库(dll)
WORD wVersionRequested;
WSADATA wsaData; // 这结构是用于接收Wjndows Socket的结构信息的
wVersionRequested = MAKEWORD( 2, 2 ); // 请求2.2版本的WinSock库
int err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) { // 返回值为零的时候是表示成功申请WSAStartup
return -1;
}
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { // 检查版本号是否正确
WSACleanup( );
return -1;
}
// 创建socket操作,建立流式套接字,返回套接字号sockClient
sockClient = socket(AF_INET, SOCK_STREAM, 0);
if(sockClient == INVALID_SOCKET) {
printf("Error at socket():%ld\n", WSAGetLastError());
WSACleanup();
return -1;
}
// 将套接字sockClient与远程主机相连
// int connect( SOCKET s, const struct sockaddr* name, int namelen);
// 第一个参数:需要进行连接操作的套接字
// 第二个参数:设定所需要连接的地址信息
// 第三个参数:地址的长度
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本地回路地址是127.0.0.1;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
cout << "本客户端已准备就绪,用户可直接输入文字向服务器反馈信息。\n";
// send(sockClient, "\nAttention: A Client has enter...\n", strlen("Attention: A Client has enter...\n")+1, 0);
send(sockClient, "\nAttention: A Client has enter...\n", 200, 0);
bufferMutex = CreateSemaphore(NULL, 1, 1, NULL);
DWORD WINAPI SendMessageThread(LPVOID IpParameter);
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter);
HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);
HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, NULL, 0, NULL);
WaitForSingleObject(sendThread, INFINITE); // 等待线程结束
closesocket(sockClient);
CloseHandle(sendThread);
CloseHandle(receiveThread);
CloseHandle(bufferMutex);
WSACleanup(); // 终止对套接字库的使用
printf("End linking...\n");
printf("\n");
system("pause");
return 0;
}
DWORD WINAPI SendMessageThread(LPVOID IpParameter)
{
while(1){
string talk;
getline(cin, talk);
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用)
if("quit" == talk){
talk.push_back('\0');
// send(sockClient, talk.c_str(), talk.size(), 0);
send(sockClient, talk.c_str(), 200, 0);
break;
}
else{
talk.append("\n");
}
printf("\nI Say:(\"quit\"to exit):");
cout << talk;
// send(sockClient, talk.c_str(), talk.size(), 0); // 发送信息
send(sockClient, talk.c_str(), 200, 0); // 发送信息
ReleaseSemaphore(bufferMutex, 1, NULL); // V(资源占用完毕)
}
return 0;
}
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)
{
while(1){
char recvBuf[300];
recv(sockClient, recvBuf, 200, 0);
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用)
printf("%s Says: %s\n", "Server", recvBuf); // 接收信息
ReleaseSemaphore(bufferMutex, 1, NULL); // V(资源占用完毕)
}
return 0;
}
五。存在问题:
1. 将客户端异常退出(不按程序要求输入“quit”退出),而直接用ALT+F4关闭后,服务器会出现死循环显示乱码的情况。
2. 在未启动服务器前提下,直接启动客户端时出现死循环乱码情况。
3. 服务器输入“quit”时不能正常退出。