基于TCP/IP协议的网络通信实例——公共聊天室

功能说明:服务端设定客户端连接个数,达到上限不可用。客户端可以单独同服务端进行通信交流。
在这里插入图片描述

一、服务端代码:MyServer.cpp

// MyServer.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
using namespace std;

#include<stdlib.h>
#include<winsock2.h>//引用头文件
#pragma comment(lib,"ws2_32.lib")//引用库文件

//最大连接数
#define g_MaxConnect  20
int g_Connect = 0;

struct sock_params
{
	SOCKET hsock;
	int nSockIndex;
};

//线程实现函数
DWORD WINAPI threadpro(LPVOID pParam)
{
	sock_params* sockPam = (sock_params*)pParam;
	SOCKET hsock = sockPam->hsock;
	int nSockIndex = sockPam->nSockIndex;

	char aIndex[4];
	_itoa_s(nSockIndex, aIndex, 10);

	char buffer[1024];
	char sendBuffer[1024];
	if(hsock != INVALID_SOCKET)
	{
		cout<<"客户端 "<< nSockIndex << " 加入服务器!" <<endl;
	}
		
	while(1)
	{
		//循环接收发送的内容
		int num = recv(hsock,buffer,1024,0);//阻塞函数,等待接收内容

		if(num > 0)
		{
			cout << "客户端 "<< nSockIndex << ": "<< buffer<<endl;

			memset(sendBuffer,0,1024);

			char strValue[30] = "服务器 ";
			strcat_s(strValue, aIndex);
			strcpy_s(sendBuffer,strValue);

			char strSpace[30] = " 收到数据如下: ";
			strcat_s(sendBuffer,strSpace);
			strcat_s(sendBuffer,buffer);

			int ires = send(hsock,sendBuffer,sizeof(sendBuffer),0);//回送消息

			//cout<<"Send to Client: "<<sendBuffer<<endl;
		}
		else
		{
			cout<<"客户端 "<< nSockIndex <<" 关闭!" <<endl;
			//cout<<"Server Process " << nSockIndex << " Closed"<<endl;
			return 0;
		}
	}

	return 0;
}

//主函数
void main()
{
	WSADATA wsd;//定义WSADATA对象
	WSAStartup(MAKEWORD(2,2),&wsd);
	SOCKET m_SockServer;
	sockaddr_in serveraddr;

	sockaddr_in serveraddrfrom;
	SOCKET m_Server[g_MaxConnect];

	serveraddr.sin_family = AF_INET;//设置服务器地址
	serveraddr.sin_port = htons(4600);//设置端口号
	serveraddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	m_SockServer = socket(AF_INET,SOCK_STREAM,0);
	int nStatus = bind(m_SockServer,(sockaddr*)&serveraddr,sizeof(serveraddr));
	if (nStatus == 0)
	{
		cout << "服务端启动成功!" <<endl;
	}
	else
	{
		cout << "服务端启动失败!" <<endl;
		return;
	}

	int iLisRet = 0;
	int len = sizeof(sockaddr);
	while(1)
	{
		iLisRet = listen(m_SockServer,0);//进行监听
		m_Server[g_Connect] = accept(m_SockServer,(sockaddr*)&serveraddrfrom,&len);

		//同意连接
		if(m_Server[g_Connect] != INVALID_SOCKET)
		{
			if(g_Connect > g_MaxConnect-1)
			{
				char WarnBuf[50]="客户端连接个数:超限!";
				int ires = send(m_Server[g_Connect],WarnBuf,sizeof(WarnBuf),0);
			}
			else
			{
				char cIndex[4];
				_itoa_s(g_Connect, cIndex, 10);
				char buf[50]="你的服务端ID: ";
				strcat_s(buf, cIndex);

				int ires = send(m_Server[g_Connect],buf,sizeof(buf),0);//发送字符过去
				//cout << buf <<endl;

				HANDLE m_Handel;	//线程句柄
				DWORD nThreadId=0;	//线程ID

				sock_params sockPam;
				sockPam.hsock = m_Server[g_Connect];
				sockPam.nSockIndex = g_Connect;

				m_Handel = (HANDLE)::CreateThread(NULL,0,threadpro,&sockPam,0,&nThreadId);
				CloseHandle(m_Handel);
			}

			++g_Connect;
		}
	}

	WSACleanup();
}

二、客户端:MyClient.cpp

// MyClient.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include<iostream>

using namespace std;

#include<stdlib.h>
#include<stdio.h>
#include"winsock2.h"
#include<time.h>
#pragma comment(lib,"ws2_32.lib")

void main()
{
	WSADATA wsd;//定义WSADATA对象
	WSAStartup(MAKEWORD(2,2),&wsd);
	SOCKET m_SockClient;
	sockaddr_in clientaddr;

	clientaddr.sin_family = AF_INET;	//设置服务器地址
	clientaddr.sin_port = htons(4600);  //设置服务器端口号
	clientaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	m_SockClient = socket(AF_INET,SOCK_STREAM,0);
	
	if (m_SockClient == INVALID_SOCKET)
	{
		printf("Sock 初始化失败: %d\n", WSAGetLastError());
		WSACleanup();
		return ;
	}
	// 获取发送缓冲区和接送缓冲区大小
	{
		int optlen = 0;
		int optval = 0;

		optlen = sizeof(optval);
		getsockopt(m_SockClient, SOL_SOCKET, SO_SNDBUF, (char*)&optval, &optlen);
		printf("send buf len is %d\n",optval);           //64位 默认发送缓冲区64k
		getsockopt(m_SockClient, SOL_SOCKET, SO_RCVBUF, (char*)&optval, &optlen);
		printf("Recv buf len is %d\n",optval);          //64位 默认接收缓冲区64k
	}
	// 设定发送缓冲区大小
	// optval = 1024 * 2;  
    // setsockopt(ConnectSocket, SOL_SOCKET, SO_SNDBUF, (char*)&optval, optlen); 
    
	int nSuccess = connect(m_SockClient,(sockaddr*)&clientaddr,sizeof(clientaddr));//连接超时
	if (nSuccess == 0)
	{
		cout<<"连接服务器成功!"<<endl;
	}
	else
	{
		cout<<"连接服务器失败!"<<endl;
		return;
	}

	char buffer[1024];
	char inBuf[1024];
	int num = 0;
	num = recv(m_SockClient,buffer,1024,0);//阻塞
	if(num > 0)
	{
		cout << /*"Receive from server:"<<*/ buffer<<endl;

		char* pResult = strstr(buffer, "超限");
		if (pResult != NULL)
		{
			cout<<"服务器连接个数超限,不可用!"<<endl;
			system("pause"); 
			return;
		}

		while(1)
		{
			cout<<"请输入要发送的消息:"<<endl;
			cin >> inBuf;

			if(strcmp(inBuf,"exit") == 0)
			{
				send(m_SockClient,inBuf,sizeof(inBuf),0);//发送退出指令
				return;
			}

			int send_len = send(m_SockClient,inBuf,sizeof(inBuf),0);
			if (send_len < 0) 
			{
				cout << "发送失败!请检查服务器是否开启!" << endl;
				system("pause"); 
				return;
			}

			int recv_len = recv(m_SockClient,buffer,1024,0);//接收客户端发送过来的数据
			if(recv_len>=0)
			{
				cout<< buffer <<endl;
			}
		}
	}
}

资源文件:https://download.csdn.net/download/m0_37251750/36164589

三、 额外代码:加入线程锁或者识别消息类型

(可以跳过)

// 线程锁
class CAutoLock
{
public:
	CAutoLock();
	~CAutoLock();

	void Lock();
	void UnLock();

private:
	CRITICAL_SECTION m_Section;
};
#endif

CAutoLock::CAutoLock()
{
	InitializeCriticalSection(&m_Section);
}

CAutoLock::~CAutoLock()
{
	DeleteCriticalSection(&m_Section);
}

void CAutoLock::Lock()
{
	EnterCriticalSection(&m_Section);
}

void CAutoLock::UnLock()
{
	LeaveCriticalSection(&m_Section);
}


#pragma once
#define INVALID_MSG			0	// 无效的消息
#define MSG_FOLDER_INFO		1	// 文件目录信息消息
#define MSG_CREATE_FOLDER	2	// 创建文件目录消息
#define MSG_DELETE_FOLDER	3	// 删除文件目录消息
#define MSG_RENAME_FOLDER	4	// 重命名文件目录消息

class Message
{
public:
	struct MsgHead     //头消息
	{
		int msgId;    //消息标识
		MsgHead(int msg = INVALID_MSG) :msgId(msg) {};
	};
	struct MsgFolderInfo :public MsgHead
	{
		char chProjCode[_MAX_FNAME];
		MsgFolderInfo() :MsgHead(MSG_FOLDER_INFO) {}
	};
	struct MsgCreateFolder :public MsgHead
	{
		char chFolder[_MAX_DIR];
		MsgCreateFolder() :MsgHead(MSG_CREATE_FOLDER) {}
	};
	struct MsgDeleteFolder :public MsgHead
	{
		char chFolder[_MAX_DIR];
		MsgDeleteFolder() :MsgHead(MSG_DELETE_FOLDER) {}
	};
	struct MsgRenameFolder :public MsgHead
	{
		char chOldFolder[_MAX_DIR];
		char chNewFolder[_MAX_DIR];
		MsgRenameFolder() :MsgHead(MSG_RENAME_FOLDER) {}
	};
};

// 使用
CAutoLock g_lock;

bool ProcessConnection(SOCKET clientSocket)
{
	int nLen = 0;
	int nSize = 0;
	nLen = recv(clientSocket, (char*)&nSize, sizeof(int), 0);
	if (nLen <= 0)
	{
		hcDebugLog(_T("ZLY:接收Msg长度失败 \n"));
		cout << "接收Msg长度失败" << WSAGetLastError() << endl;
		return false;
	}

	char buff[MAX_PACK_SIZE];
	memset(buff, 0x00, sizeof(char)*MAX_PACK_SIZE);
	nLen = recv(clientSocket, buff, nSize, 0);
	if (nLen <= 0)
	{
		if (SOCKET_ERROR == nLen)
		{
			hcDebugLog(_T("ZLY:接收Msg数据失败 \n"));
			cout << "接收Msg数据失败" << WSAGetLastError() << endl;
		}
		else
		{
			hcDebugLog(_T("ZLY:客户端关闭连接 \n"));
			cout << "客户端关闭连接" << endl;
		}
		return false;
	}

	Message::MsgHead* msgHead;
	msgHead = (Message::MsgHead*)&buff;
	switch(msgHead->msgId)
	{
	case MSG_FOLDER_INFO:
		{}
		break;
	case MSG_CREATE_FOLDER:
		{}
		break;
	default:
		{
			cout<<"无效消息"<<endl;
			return false;
		}
	}

	return true;
}

DWORD WINAPI CreateClientThread(LPVOID pData)
{
	SocketData* pSocketData = (SocketData*)pData;
	if (pSocketData == NULL)
	{
		HY_SAFE_DELETE(pSocketData);
		return -1;
	}

	// 循环监听客户端发送消息
	while (pSocketData->serverSocket.ProcessConnection(pSocketData->clientSocket))
	{
	}

	// 处理客户端非正常关闭的情况
	g_lock.Lock();
	for (auto iter = g_vecProjCode.begin(); iter != g_vecProjCode.end(); iter++)
	{
		if (pSocketData->clientSocket == iter->socket)
		{
			g_vecProjCode.erase(iter);
			break;
		}
	}

	if (g_vecProjCode.size() == 0)
	{
		// 关闭套接字
		pSocketData->serverSocket.CloseSocket();
		exit(0);
	}
	g_lock.UnLock();

	HY_SAFE_DELETE(pSocketData);
	return 0;
}
bool MessageUtils::SendMsg(SOCKET socket, Message::MsgHead* pMsg)
{
	int nSize = 0;
	switch (pMsg->msgId)
	{
	case MSG_FOLDER_INFO:
		nSize = sizeof(Message::MsgFolderInfo);
		break;
	case MSG_CREATE_FOLDER:
		nSize = sizeof(Message::MsgCreateFolder);
		break;
	case MSG_DELETE_FOLDER:
		nSize = sizeof(Message::MsgDeleteFolder);
		break;
	case MSG_RENAME_FOLDER:
		nSize = sizeof(Message::MsgRenameFolder);
		break;
	
	default:
		return false;
	}

	// 发送消息长度
	if (sizeof(nSize) != send(socket, (char*)&nSize, sizeof(nSize), 0))
		return false;

	// 发送消息数据
	if (nSize != send(socket, (char*)pMsg, nSize, 0))
		return false;

	return true;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
一、实验目的 1.掌握通信规范的制定及实现。 2.练习较复杂的网络编程,能够把协议设计思想应用到现实应用中。 二、实验内容和要求 1.进一步熟悉VC++6编程环境; 2.利用VC++6进行较复杂的网络编程,完成网络聊天室的设计及编写; 三、实验(设计)仪器设备和材料 1.计算机及操作系统:PC机,Windows; 2.网络环境:可以访问互联网; 四、 TCP/IP程序设计基础 基于TCP/IP通信基本上都是利用SOCKET套接字进行数据通讯,程序一般分为服务器端和用户端两部分。设计思路(VC6.0下): 第一部分 服务器端 一、创建服务器套接字(create)。 二、服务器套接字进行信息绑定(bind),并开始监听连接(listen)。 三、接受来自用户端的连接请求(accept)。 四、开始数据传输(send/receive)。 五、关闭套接字(closesocket)。 第二部分 客户端 一、创建客户套接字(create)。 二、与远程服务器进行连接(connect),如被接受则创建接收进程。 三、开始数据传输(send/receive)。 四、关闭套接字(closesocket)。 CSocket的编程步骤:(注意我们一定要在创建MFC程序第二步的时候选上Windows Socket选项,其中ServerSocket是服务器端用到的,ClientSocket是客户端用的。) (1)构造CSocket对象,如下例: CSocket ServerSocket; CSocket ClientSocket; (2)CSocket对象的Create函数用来创建Windows Socket,Create()函数会自行调用Bind()函数将此Socket绑定到指定的地址上面。如下例: ServerSocket.Create(823); //服务器端需要指定一个端口号,我们用823。 ClientSocket.Create(); //客户端不用指定端口号。 (3)现在已经创建完基本的Socket对象了,现在我们来启动它,对于服务器端,我们需要这个Socket不停的监听是否有来自于网络上的连接请求,如下例: ServerSocket.Listen(5);//参数5是表示我们的待处理Socket队列中最多能有几个Socket。 (4)对于客户端我们就要实行连接了,具体实现如下例: ClientSocket.Connect(CString SerAddress,Unsinged int SerPort);//其中SerAddress是服务器的IP地址,SerPort是端口号。 (5)服务器是怎么来接受这份连接的呢?它会进一步调用Accept(ReceiveSocket)来接收它,而此时服务器端还须建立一个新的CSocket对象,用它来和客户端进行交流。如下例: CSocket ReceiveSocket; ServerSocket.Accept(ReceiveSocket); (6)如果想在两个程序之间接收或发送信息,MFC也提供了相应的函数。如下例: ServerSocket.Receive(String,Buffer); //String是你要发送的字符串,Buffer是发送字符串的缓冲区大小。ServerSocket.Send(String,Butter);//String是你要接收的字符串,Buffer是接收字符串的缓冲区大小。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欧特克_Glodon

打赏我五毛,我吃饱接着搞!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值