大家好,我是涛哥。
最近写了一个服务端程序,能与多个客户端进行通信。那么,这个服务端是怎么实现的呢?很简单,它就是很常规的多线程服务器。
如果你还不太清楚,那我画个图,你就明白了。三个男生都追一个女生,这个女生又不好拒绝,于是与三个男生之间保持通信关系。
涛哥手绘
服务端实现
既然是多线程服务器,那么,这些线程肯定是有明确分工的。主线程来处理网络的连接,而通信线程来处理客户端与服务端的通信。
而且,主线程要负责多个客户端的连接请求,所以不能阻塞主线程哦,因此必须用非阻塞socket. 于是,我写的服务端程序如下:
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
#define BUF_SIZE 100
sockaddr_in addrClient; // 为了让通信线程获取ip
// 通信线程
DWORD WINAPI CommThread(LPVOID lp)
{
SOCKET sClient = (SOCKET)(LPVOID)lp;
while(1)
{
char buf[BUF_SIZE] = {0};
int retVal = recv(sClient, buf, BUF_SIZE, 0);
if(SOCKET_ERROR == retVal)
{
int err = WSAGetLastError();
if(WSAEWOULDBLOCK == err) // 暂时没有数据
{
Sleep(100);
continue;
}
}
// 输出客户端连接信息
SYSTEMTIME st;
GetLocalTime(&st);
char sDateTime[100] = {0};
sprintf(sDateTime, "%4d-%2d-%2d %2d:%2d:%2d", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
printf("%s, The client is [%s:%d]. Msg from client is : %s\n", sDateTime, inet_ntoa(addrClient.sin_addr), addrClient.sin_port, buf);
char msg[BUF_SIZE] = {0};
sprintf(msg, "Message received is : %s", buf);
while(1)
{
retVal = send(sClient, msg, strlen(msg), 0); // 回显
if(SOCKET_ERROR == retVal)
{
int err = WSAGetLastError();
if(err == WSAEWOULDBLOCK)
{
Sleep(500);
continue;
}
}
break;
}
}
closesocket(sClient);
}
int main()
{
WSADATA wsd;
WSAStartup(MAKEWORD(2, 2), &wsd);
SOCKET sServer = socket(AF_INET, SOCK_STREAM, 0);
// 设置套接字为非阻塞模式
int iMode = 1;
ioctlsocket(sServer, FIONBIO, (u_long FAR*) &iMode);
// 设置服务器套接字地址
SOCKADDR_IN addrServ;
addrServ.sin_family = AF_INET;
addrServ.sin_port = htons(8888);
addrServ.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
bind(sServer,(const struct sockaddr*)&addrServ, sizeof(SOCKADDR_IN));
listen(sServer, 10);
printf("Server start...\n");
int addrClientlen = sizeof(addrClient);
while(1)
{
SOCKET sClient = accept(sServer, (sockaddr FAR*)&addrClient, &addrClientlen);
if(INVALID_SOCKET == sClient)
{
int err = WSAGetLastError();
if(WSAEWOULDBLOCK == err) // 无法立即完成非阻塞套接字上的操作
{
Sleep(100);
continue;
}
}
// 创建通信线程
CreateThread(NULL, NULL, CommThread, (LPVOID)sClient, 0, NULL);
}
// 释放套接字
closesocket(sServer);
WSACleanup();
getchar();
return 0;
}
编译并运行程序,开启服务端,等待客户端的连接。
客户端实现
我们可以看到,上述服务端的程序需要有主线程和通信线程,而客户端的程序就相对简单了,主线程本身就是通信线程。程序如下:
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
WSAStartup( wVersionRequested, &wsaData );
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(8888);
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
send(sockClient, "hello world", strlen("hello world") + 1, 0);
char recvBuf[100] = {0};
recv(sockClient, recvBuf, 100, 0);
printf("%s\n", recvBuf);
while(1);
closesocket(sockClient);
WSACleanup();
return 0;
}
我们编译并运行程序,开启3个客户端进程,分别去连接服务端。实际验证发现,服务端分配了三个通信线程,分别来应对3个客户端,可以正常通信哦。
由此,我们就实现了多线程服务器,能处理多个客户端的并发连接。这是一个非常初级的服务器模型,从事后端开发的同学,肯定会很熟悉这个典型模型。
建议有兴趣的同学实际玩一下这个例子,加深对多线程服务器的理解,提高实战能力。网络编程就是这样,定好思路,多编程,多抓包,多调试,爽爽哒!