大家好,我是涛哥。
最近写了一个服务端程序,能与多个客户端进行通信。那么,这个服务端是怎么实现的呢?很简单,它就是很常规的多线程服务器。
如果你还不太清楚,那我画个图,你就明白了。三个男生都追一个女生,这个女生又不好拒绝,于是与三个男生之间保持通信关系。
涛哥手绘
服务端实现
既然是多线程服务器,那么,这些线程肯定是有明确分工的。主线程来处理网络的连接,而通信线程来处理客户端与服务端的通信。
而且,主线程要负责多个客户端的连接请求,所以不能阻塞主线程哦,因此必须用非阻塞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个客户端,可以正常通信哦。
由此,我们就实现了多线程服务器,能处理多个客户端的并发连接。这是一个非常初级的服务器模型,从事后端开发的同学,肯定会很熟悉这个典型模型。
建议有兴趣的同学实际玩一下这个例子,加深对多线程服务器的理解,提高实战能力。网络编程就是这样,定好思路,多编程,多抓包,多调试,爽爽哒!