多线程+SOCKET编程实现qq群聊的服务端和客户端"/>
多线程+SOCKET编程实现qq群聊的服务端和客户端
多线程+SOCKET编程实现qq群聊的服务端和客户端
标签(空格分隔): 多线程 网络编程 线程同步
一、设计思路
1、服务端
- 每来一个客户端连接,服务端起一个线程维护;
- 将收到的消息转发给所有的客户端;
- 某个连接断开,需要处理断开的连接;
2、客户端
- 连接服务器;
- 与服务器进行通信;
- 下线机制;
- 用户名定义
二、服务端具体实现
#include <iostream>
#include <WinSock2.h>
#include <process.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
/*********************************************
*服务端:
*1、每来一个连接,服务端起一个线程维护
*2、将收到的消息转发给所有的客户端
*3、某个连接断开的处理
*4、用互斥体进行线程同步
**********************************************/
#define CLIENT_MAX 256 //最多的客户连接数
#define RECV_BUFF 256 //接收buff的最大容量
int clientCnt = 0; //记录当前的客户连接数
HANDLE hMutex; //定义互斥体内核对象,凡是全局变量涉及到资源竞争的问题,都要定义互斥体
SOCKET sockClients[CLIENT_MAX];
static unsigned WINAPI threadCli(void* arg);
void sendMsg(char Msg[RECV_BUFF], int iLen);int main(void) {WORD wVersion;WSADATA wsaData;int err;HANDLE hThread;wVersion = MAKEWORD(1, 1);//初始化套接字库err = WSAStartup(wVersion, &wsaData);if (err != 0) {return err;}if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {//清理套接字库WSACleanup();return -1;}hMutex = CreateMutex(NULL, FALSE, NULL);//创建监听套接字SOCKET sockSvr = socket(AF_INET, SOCK_STREAM, 0);//准备绑定的信息SOCKADDR_IN addrSvr;memset(&addrSvr, 0, sizeof(addrSvr));addrSvr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);addrSvr.sin_family = AF_INET;addrSvr.sin_port = htons(6000);//绑定到本机if (bind(sockSvr, (SOCKADDR*)&addrSvr, sizeof(SOCKADDR)) == SOCKET_ERROR) {cout << "bind error:" << GetLastError() << endl;return -1;}//监听listen(sockSvr, 5);cout << "开始监听" << endl;SOCKADDR_IN addrCli;int len = sizeof(SOCKADDR);while (true) {//接收连接请求SOCKET sockConnet = accept(sockSvr, (SOCKADDR*)&addrCli, &len);WaitForSingleObject(hMutex, INFINITE);sockClients[clientCnt++] = sockConnet;ReleaseMutex(hMutex);//每来一个连接,服务端起一个线程维护hThread=(HANDLE) _beginthreadex(NULL, 0, threadCli, (void*)&sockConnet, 0, NULL);cout << "Client ip:" << inet_ntoa( addrCli.sin_addr )<< endl;cout << "当前连接数:" << clientCnt << endl;}closesocket(sockSvr);//清理套接字库WSACleanup();system("pause");return 0;}
//处理客户端线程的函数
unsigned WINAPI threadCli(void* arg)
{//获取客户端SocketSOCKET sockCli = *((SOCKET*)arg);char recvBuf[RECV_BUFF] = { 0 };int iLen=0;//服务端接收到客户端的消息以广播的方式转发所有连接的客户端while (1) {//iLen记录发送的字节数if ((iLen = recv(sockCli, recvBuf, RECV_BUFF, 0))!=-1) {sendMsg(recvBuf, iLen);}else {break;}}cout << "当前连接的客户数量为:" << clientCnt << endl;WaitForSingleObject(hMutex, INFINITE);//处理断开的连接for (int i = 0; i < clientCnt; i++) {if (sockClients[i] == sockCli) {while (i++< clientCnt) {sockClients[i] = sockClients[i + 1];}}}clientCnt--;cout << "断开连接后的客户连接数量为:" << clientCnt << endl;ReleaseMutex(hMutex);closesocket(sockCli);return 0;
}
//广播消息给其他的客户端
void sendMsg(char Msg[RECV_BUFF], int iLen) {WaitForSingleObject(hMutex, INFINITE);for (int i = 0; i < clientCnt; i++) {send(sockClients[i], Msg, iLen, 0);}ReleaseMutex(hMutex);
}
三、客户端具体实现
#include <WinSock2.h>
#include <iostream>
#include <process.h>
using namespace std;#pragma comment(lib, "ws2_32.lib")
#define BUFF_SIZE 256
#define NAME_SIZE 32unsigned WINAPI sendMsg(void* arg);
unsigned WINAPI recvMsg(void* arg);
char nickName[NAME_SIZE] = "[DEFAULT]";
char Msg[BUFF_SIZE] = { 0 };//保存控制台输入的数据//argc为指针数组元素的个数
int main(int argc,char *argv[])
{WORD wVersion;WSADATA wsaData;int err;HANDLE hSendThread;HANDLE hRecvThread;wVersion = MAKEWORD(1, 1);err = WSAStartup(wVersion, &wsaData);if (err != 0){return err;}if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1){WSACleanup();return -1;}// 创建套接字SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);SOCKADDR_IN addrSrv;memset(&addrSrv, 0, sizeof(addrSrv));addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(6000);//向服务器发起连接请求if (connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)) == SOCKET_ERROR) {cout << "connect error:" << GetLastError() << endl;return -1;}//获取控制台输入的昵称,保存到nickName中sprintf_s(nickName,NAME_SIZE, argv[1]);//起一个线程发送数据给服务器hSendThread =(HANDLE) _beginthreadex(NULL, 0, sendMsg, (void*)&sockCli, 0, NULL);//起一个线程接收服务器发来的数据hRecvThread = (HANDLE)_beginthreadex(NULL, 0, recvMsg, (void*)&sockCli, 0, NULL);WaitForSingleObject(hSendThread, INFINITE);WaitForSingleObject(hRecvThread, INFINITE);// 关闭套接字closesocket(sockCli);WSACleanup();// 暂停system("pause");return 0;
}
//发送数据给服务器的线程
unsigned WINAPI sendMsg(void* arg) {SOCKET sockCli = *((SOCKET*)arg);char szNameMsg[BUFF_SIZE + NAME_SIZE] = { 0 };while (1) {fgets(Msg, BUFF_SIZE, stdin);//从控制台获取输入的数据if (!strcmp(Msg, "q\n") || !strcmp(Msg, "Q\n")) {//按下q或者Q,断开当前客户端的连接closesocket(sockCli);exit(-1);}else {sprintf_s(szNameMsg, "[%s]%s", nickName, Msg);//发送数据到服务端send(sockCli, szNameMsg, strlen(szNameMsg), 0);}}return 1;}
//接收服务器数据的进程
unsigned WINAPI recvMsg(void* arg) {int iLen = 0;SOCKET sockCli = *((SOCKET*)arg);char szNameMsg[BUFF_SIZE + NAME_SIZE] = { 0 };while (1) {iLen = recv(sockCli, szNameMsg, BUFF_SIZE + NAME_SIZE - 1, 0);if (iLen != -1) {szNameMsg[iLen] = '\0';//接收到的数据输出到控制台fputs(szNameMsg, stdout);}else {return -1;}}return 1;
}
四、运行效果
1、先启动服务端,再新建两个客户端连接,发送一些测试数据:
2、客户端A断开连接:
3、所有客户端都下线:
功能基本实现。
更多推荐
多线程+SOCKET编程实现qq群聊的服务端和客户端
发布评论