使用多线程来实现可与多个客户端通信的服务端。
当客户端连接上服务端之后,为该客户端创建一个新的线程,在该线程中与客户端进行通信。
服务端程序中的主线程负责监听并接受客户端的连接请求,创建与客户端通信的线程。
另外,这是一个在windows下实现的回音(客户端发啥服务端就回传给客户端啥)服务端。
注意下多线程下共享内存,还有printf
的使用,需要线程之间同步,共享资源同一时间只能有一个线程使用。
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
/* 服务端相关配置 */
#define SERVER_IP "127.0.0.1" //服务端IPV4地址
#define SERVER_PORT 1234 //服务端端口
#define MAX_CLIENT_NUMS 50U //最大可连接客户端数量
SOCKET serverSocket = { 0 }; //服务端套接字句柄
SOCKADDR_IN serverSocketAddr = { 0 }; //服务端套接字配置结构体
volatile unsigned int currentClientNums = { 0 }; //当前连接的客户端数量
/* 客户端相关配置 */
typedef struct
{
char isOnline; //客户端是否在线 1则在线 0则不在线
SOCKET clientSocket; //客户端SOCKET套接字句柄
SOCKADDR_IN clientSocketAddr; //客户端套接字配置结构体
HANDLE threadFunc; //客户端对应的线程句柄
}CLIENT_t, * PCLIENT_t;
CLIENT_t client[MAX_CLIENT_NUMS] = { 0 };
void ClientThreadFunc(void*); //客户端线程函数
#define RECEIVE_BUFF_SIZE 200U //接收缓冲区字节大小
/* 线程同步相关 */
CRITICAL_SECTION cs_printf;//关键段(临界区)
CRITICAL_SECTION cs_currentClientNums;
int main()
{
/* 初始化DLL */
WSADATA wsadata = { 0 };
if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)//版本2.2
{
printf("初始化DLL失败!\n");
return 0;
}
/* 创建SOCKET */
serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//TCP套接字
if (serverSocket == INVALID_SOCKET)
{
printf("创建服务端TCP套接字失败,错误代码为:%d\n", WSAGetLastError());
goto END;
}
/* 配置服务端套接字信息 */
serverSocketAddr.sin_family = AF_INET;//IPV4
serverSocketAddr.sin_addr.S_un.S_addr = inet_addr(SERVER_IP);//IP地址
serverSocketAddr.sin_port = htons(SERVER_PORT);//端口
if (bind(serverSocket, &serverSocketAddr, sizeof(serverSocketAddr)) == SOCKET_ERROR)//绑定服务端套接字
{
printf("绑定服务端套接字失败,错误代码为:%d\n", WSAGetLastError());
goto END;
}
/* 服务端进入监听状态 */
if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR)
{
printf("服务端进入监听状态失败,错误代码为:%d\n", WSAGetLastError());
goto END;
}
/* 初始化关键段(临界区) */
if (!InitializeCriticalSectionAndSpinCount(&cs_printf, 4000))
{
printf("关键段(临界区)cs_printf初始化失败!\n");
DeleteCriticalSection(&cs_printf);//销毁关键段
goto END;
}
InitializeCriticalSection(&cs_currentClientNums);
/* 等待客户端连接服务端 */
printf("服务端已经开启,IP:%s,端口:%d\n", inet_ntoa(serverSocketAddr.sin_addr), ntohs(serverSocketAddr.sin_port));
while (1)
{
static SOCKET clientSocket = { 0 };//客户端套接字句柄
static SOCKADDR_IN clientSocketAddr = { 0 };//客户端套接字配置结构体
static unsigned int cLength = sizeof(clientSocketAddr);//结构体长度
static unsigned int temp = { 0 };
/* 接收客户端的连接请求 */
/*
* 没有新的连接程序会阻塞在这,accept是阻塞式函数
* 直到出现异常,或者有新的连接
*/
clientSocket = accept(serverSocket, &clientSocketAddr, &cLength);
if (clientSocket == SOCKET_ERROR)
{
EnterCriticalSection(&cs_printf);//进入关键段
printf("客户端连接错误,错误代码为:%d\n", WSAGetLastError());
LeaveCriticalSection(&cs_printf);//离开关键段
continue;//继续等待下一次连接
}
else
{
/* 判断是否超过允许连接的客户端数量 */
EnterCriticalSection(&cs_currentClientNums);//进入关键段
temp = currentClientNums + 1;
LeaveCriticalSection(&cs_currentClientNums);//离开关键段
if (temp > MAX_CLIENT_NUMS)
{
send(clientSocket, "连接已断开,已经超过可连接的最大数量!\n", strlen("连接已断开,已经超过可连接的最大数量!\n"), 0);
EnterCriticalSection(&cs_printf);//进入关键段
printf("有新的客户端连接请求,IP:%s,端口:%d,但已经超过可连接的最大数量!\n", inet_ntoa(clientSocketAddr.sin_addr), ntohs(clientSocketAddr.sin_port));
LeaveCriticalSection(&cs_printf);//离开关键段
closesocket(clientSocket);//断开与客户端的连接
}
else
{
for (size_t i = 0; i < MAX_CLIENT_NUMS; i++)
{
/* 遍历哪个客户端断开连接了,如果断开连接则将相应的线程句柄关闭 */
if (client[i].isOnline == 0)
{
client[i].isOnline = 1;//设置客户端在线
client[i].clientSocket = clientSocket;//保存客户端SOCKET套接字
memcpy(&client[i].clientSocketAddr, &clientSocketAddr, sizeof(clientSocketAddr));//保存客户端SOCKET配置结构体
/* 创建客户端线程 */
//注意:_endthread 会自动关闭线程句柄。
(该行为与 Win32 ExitThread API 不同。
)
//因此,当你使用 _beginthread 和 _endthread 时,不要通过调用 Win32 CloseHandle API 来显式关闭线程句柄。
client[i].threadFunc = _beginthread(ClientThreadFunc, 0, &client[i]);
EnterCriticalSection(&cs_currentClientNums);//进入关键段
EnterCriticalSection(&cs_printf);//进入关键段
currentClientNums++;//当前在线客户端数量加一
printf("当前已有 %d 个客户端连接,最多可连接 %d 个客户端\n", currentClientNums, MAX_CLIENT_NUMS);
LeaveCriticalSection(&cs_printf);//离开关键段
LeaveCriticalSection(&cs_currentClientNums);//离开关键段
break;
}
}
}
}
}
DeleteCriticalSection(&cs_currentClientNums);//销毁关键段
END:
/* 断开服务端套接字 */
closesocket(serverSocket);
/* 终止DLL的调用 */
WSACleanup(wsadata);
return 0;
}
/* 客户端线程 */
void ClientThreadFunc(void* arg)
{
PCLIENT_t client = (PCLIENT_t)arg;
char receiveBuff[RECEIVE_BUFF_SIZE] = { 0 };//接收来自客户端的数据缓冲区
int result = { 0 };//recv函数返回值
unsigned short clientPort = ntohs(client->clientSocketAddr.sin_port);//客户端端口
IN_ADDR clientIP = client->clientSocketAddr.sin_addr;//客户端IP
EnterCriticalSection(&cs_printf);//进入关键段
printf("有新的客户端连接,IP:%s,端口:%d\n", inet_ntoa(clientIP), clientPort);
LeaveCriticalSection(&cs_printf);//离开关键段
/* 发送欢迎信息至客户端 */
send(client->clientSocket, "Hello i am server\n", strlen("Hello i am server\n"), 0);
while (1)
{
/* 接收来自客户端的数据 */
/*
* recv函数 的实质就是从socket的缓冲区里拷贝出数据
* 返回值就是拷贝出字节数的大小。
* 当缓冲区内没有内容的时候,会处于阻塞
* 状态,这个while函数会停在这里。
直到新的数据进来或者出现异常。
*/
result = recv(client->clientSocket, receiveBuff, RECEIVE_BUFF_SIZE, 0);
if (result > 0)
{
//添加字符串结束标志,便于用字符串输出
if (result >= RECEIVE_BUFF_SIZE)
{
receiveBuff[RECEIVE_BUFF_SIZE - 1] = ';'}
else
[
{
receiveBuff]result= ';' }send
(
,,client->clientSocketstrlen receiveBuff( ),receiveBuff0) ;//数据回传EnterCriticalSection(
&);cs_printf//进入关键段printf(
"收到来自IP:%s,端口:%d客户端的信息:%s\n",inet_ntoa( ),clientIP,) clientPort; receiveBuffLeaveCriticalSection(
&);cs_printf//离开关键段}else
if
( == 0result ) //result == 0 说明客户端断开连接EnterCriticalSection(
{
&);cs_printf//进入关键段printf(
"客户端断开连接,IP:%s,端口:%d\n",inet_ntoa( ),clientIP); clientPortLeaveCriticalSection(
&);cs_printf//离开关键段break;
}else
//result < 0 说明出现异常
EnterCriticalSection(
{
&);cs_printf//进入关键段printf(
"与客户端通信时发生异常,IP:%s,端口:%d\n",inet_ntoa( ),clientIP); clientPortLeaveCriticalSection(
&);cs_printf//离开关键段break;
}}
closesocket
(
);client->clientSocket//断开连接=0
client->isOnline ; //设置客户端不在线EnterCriticalSection(
&);cs_currentClientNums//进入关键段--;
currentClientNums//当前在线客户端数量减一LeaveCriticalSection(
&);cs_currentClientNums//离开关键段_endthread(
);//终止当前线程}#
include
客户端
#include
#include
#pragma
comment( ,"ws2_32.lib"lib)/* 服务端信息 */#
define
SERVER_IP"127.0.0.1" //服务端IPV4地址 # define
SERVER_PORT1234 //服务端端口 = 0
SOCKADDR_IN serverSocketAddr } { ; //服务端套接字配置结构体/* 客户端相关配置 */ =
0
SOCKET clientSocket } { ; //客户端SOCKET套接字# define
BUFF_SIZE200U //缓冲区字节大小 char [
] sendBuff=BUFF_SIZE0 } { ; //发送缓冲区char [
] receiveBuff=BUFF_SIZE0 } { ; //接收缓冲区void RecevieThreadFunc
( void*);//客户端接收来自服务端的数据线程函数volatile char
= 0 isOnline } { ; //客户端是否在线 1则在线 0则不在线/* 线程同步相关 */ ;
//关键段(临界区)
CRITICAL_SECTION cs_printf;int
CRITICAL_SECTION cs_isOnlinemain
( )/* 初始化DLL */=
{
0
WSADATA wsadata } { ; if(
WSAStartup (MAKEWORD(2,2) ,&) !=wsadata0 ) //版本2.2printf(
{
"初始化DLL失败!\n");return0
; }/* 创建SOCKET */
=
socket
clientSocket ( ,,AF_INET) SOCK_STREAM; IPPROTO_TCP//TCP套接字if(
== )clientSocket printf INVALID_SOCKET(
{
"创建客户端TCP套接字失败,错误代码为:%d\n",WSAGetLastError( ));goto;
} END/* 配置服务端套接字信息并连接服务端 */
.
=
serverSocketAddr;sin_family //IPV4 AF_INET..
serverSocketAddr.sin_addr=S_uninet_addrS_addr ( );SERVER_IP//IP地址.=
serverSocketAddrhtonssin_port ( );SERVER_PORT//端口if(
connect (,&clientSocket, sizeofserverSocketAddr( ))serverSocketAddr==) //连接服务端 SOCKET_ERRORprintf(
{
"连接服务端失败,错误代码为:%d\n",WSAGetLastError( ));goto;
} END/* 初始化关键段(临界区) */
if
(
! InitializeCriticalSectionAndSpinCount(&,4000cs_printf) )printf(
{
"关键段(临界区)cs_printf初始化失败!\n");DeleteCriticalSection(
&);cs_printf//销毁关键段goto;
} ENDInitializeCriticalSection
(
&);cs_isOnline/* 客户端连接上了服务端 */printf
(
"已经连接上服务端,服务端IP:%s,端口:%d\n",inet_ntoa( .)serverSocketAddr,sin_addrntohs( .)serverSocketAddr)sin_port;=1
isOnline ; /* 创建接收来自服务端数据的线程 */_beginthread
(
,0RecevieThreadFunc, NULL) ;/* 接收用户的输入并发送至服务端 */while
(
1 )EnterCriticalSection(
{
&);cs_isOnline//进入关键段if(
! )//判断客户端是否在线isOnlineLeaveCriticalSection(
{
&);cs_isOnlinegoto;
} ENDLeaveCriticalSection
(
&);cs_isOnline//离开关键段EnterCriticalSection(
&);cs_printf//进入关键段printf(
"请输入需要发送至服务端的数据:\n");LeaveCriticalSection(
&);cs_printf//离开关键段scanf(
"%s",); sendBuffsend(
,,clientSocketstrlen sendBuff( ),sendBuff0) ;//发送至服务端Sleep(
100);}DeleteCriticalSection
(
&);cs_isOnline//销毁关键段:/* 断开服务端套接字 */
ENDclosesocket
(
);clientSocket/* 终止DLL的调用 */WSACleanup
(
);wsadatareturn0
; }void
RecevieThreadFunc
( void*)int arg=
{
0 result } { ; //recv函数返回值while(
1 )/* 接收来自服务端的数据 */=
{
recv
/*
* recv函数 的实质就是从socket的缓冲区里拷贝出数据
* 返回值就是拷贝出字节数的大小。
* 当缓冲区内没有内容的时候,会处于阻塞
* 状态,这个while函数会停在这里。
直到新的数据进来或者出现异常。
*/
result ( ,,clientSocket, receiveBuff0 BUFF_SIZE) ;if(
0 )result > //添加字符串结束标志,便于用字符串输出if
{
(
) [result >= BUFF_SIZE-
{
receiveBuff1BUFF_SIZE ] =';' } else[
]
=
{
receiveBuff';'result} EnterCriticalSection (&
)
;//进入关键段printfcs_printf("收到来自服务端的信息:%s\n",
);LeaveCriticalSection( receiveBuff&)
;//离开关键段}cs_printfelseif(
==
0 ) //result == 0 说明服务端断开连接result EnterCriticalSection (&)
{
;//进入关键段EnterCriticalSectioncs_isOnline(&)
;//进入关键段printfcs_printf("服务端断开连接\n")
;=0;LeaveCriticalSection
isOnline ( &)
;//离开关键段LeaveCriticalSectioncs_printf(&)
;//离开关键段breakcs_isOnline;}else
//result < 0 说明出现异常EnterCriticalSection
(
&)
{
;//进入关键段printfcs_printf("与服务端通信时发生异常\n")
;LeaveCriticalSection(&)
;//离开关键段breakcs_printf;}}
closesocket(
)
;
//断开连接_endthreadclientSocket();
//终止当前线程}
结果
服务端
客户端
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)