基於非阻塞 socket 的多線程服務器
大家好,我是濤哥。
沒錯,今天週三。最近寫了一個服務端程序,能與多個客戶端進行通信。那麼,這個服務端是怎麼實現的呢?很簡單,它就是很常規的多線程服務器。
服務端實現
既然是多線程服務器,那麼,這些線程肯定是有明確分工的。主線程來處理網絡的連接,而通信線程來處理客戶端與服務端的通信。
而且,主線程要負責多個客戶端的連接請求,所以不能阻塞主線程哦,因此必須用非阻塞 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 個客戶端,可以正常通信哦。
由此,我們就實現了多線程服務器,能處理多個客戶端的併發連接。這是一個非常初級的服務器模型,從事後端開發的同學,肯定會很熟悉這個典型模型。
建議有興趣的同學實際玩一下這個例子,加深對多線程服務器的理解,提高實戰能力。網絡編程就是這樣,定好思路,多編程,多抓包,多調試,很舒!
今天先這樣,咱們明天見。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/C2INQqU9nYhHqebNsktOuA