C++ 实现简单Tcp服务器端 -- Select方式 (包含客户端)

使用C++基于windows平台下实现Tcp聊天服务器端,Select方式

main.cpp


#include <WinSock2.h>
#include <WS2tcpip.h>
#include <iostream>

using namespace std;

#pragma  comment(lib, "Ws2_32.lib")


int main() {
	
	//初始化winsock的环境
	WSADATA  wd;
	if(WSAStartup(MAKEWORD(2, 2), &wd) == SOCKET_ERROR) {
		cout << "WSAStartup  error:" << GetLastError() << endl;
		return 0;
	}

	//1.创建监听套接字
	SOCKET  sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(sListen == INVALID_SOCKET) {
		cout << "socket  error:" << GetLastError() << endl;
		return 0;
	}

	//2.绑定到ip与端口
	sockaddr_in  addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8892);
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);//ip地址转网络字节序

	int len = sizeof(sockaddr_in);
	if(bind(sListen, (SOCKADDR*)&addr, len) == SOCKET_ERROR) {
		cout << "bind  error:" << GetLastError() << endl;
		return 0;
	}

	//3.监听套接字
	if(listen(sListen, 5) == SOCKET_ERROR) {
		cout << "listen  error:" << GetLastError() << endl;
		return 0;
	}

	//4. select 开始了
	fd_set readSet; //定义一个读(接受消息)的集合
	FD_ZERO(&readSet); //初始化集合
	FD_SET(sListen, &readSet);

	//不停的select才可以读取套接字的状态改变
	while (true){
		fd_set tmpSet; //定义一个临时的集合
		FD_ZERO(&tmpSet); //初始化集合
		tmpSet = readSet;//每次循环都是所有的套接字

		//利用select 选择出集合中可以读写的多个套接字 有点像筛选
		int ret = select(0, &tmpSet, NULL, NULL, NULL);//最后一个参数为NULL  一直等待  直到有数据过来
		if(SOCKET_ERROR == ret) {
			continue;
		}

		//成功筛选出来的temSet 可以发送或者接收的socket
		for (int i = 0; i < tmpSet.fd_count; ++i){
			//获取到套接字
			SOCKET s = tmpSet.fd_array[i];
			//接收到客户端的链接
			if(s == sListen) {
				SOCKET  c = accept(s, NULL, NULL);
				//fd_set 集合最大值为64
				if(readSet.fd_count < FD_SETSIZE) {
					//往集合中添加客户端套接字
					FD_SET(c, &readSet);
					cout << "欢迎" << c << "进入聊天室!"<< endl;
					
					//给客户端发送欢迎
					char buf[100] = {0};
					sprintf_s(buf, "欢迎%d 进入聊天室!", c);
					send(c, buf, 100, 0);
				}
				else {
					cout << "达到客户端容量上限!"<< endl;
				}
			}
			else { //一定是客户端
				//接收客户端的数据
				char buf[100] = {0};
				ret = recv(s, buf, 100, 0);
				if(ret == SOCKET_ERROR || ret == 0) {
					closesocket(s);
					FD_CLR(s, &readSet);
					cout << s << "离开聊天室!"<< endl;
				}
				else {
					cout << s << "说: " << buf << endl;
				}
			}
		}
	}

	//关闭监听套接字
	closesocket(sListen);

	//清理winsock环境
	WSACleanup();


	return 0;

}

客户端:

#include <WinSock2.h>
#include <WS2tcpip.h>
#include <iostream>

using namespace std;

#pragma comment(lib, "ws2_32.lib")


int main() {

	//初始化winsock2.2相关的动态库
	WSADATA wd; // 获取socket相关信息
	if(0 != WSAStartup(MAKEWORD(2, 2), &wd)) { //0 表示成功
		cout << "WSAStartup error: " << WSAGetLastError() << endl;
		return 0;
	}
	
	//1. 创建TCP socket 流式套接字
	SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(INVALID_SOCKET == s) {
		cout << "socket error :" << WSAGetLastError() << endl;
		return 0;
	}

	//2. 绑定socket到一个IP地址和端口
	sockaddr_in addr; //不建议使用sockaddr 建议用sockaddr_in
	addr.sin_family = AF_INET; // 地址族
	addr.sin_port = htons(8888);//本地端口 转网络字节序
	//addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//ip地址转网络字节序 inet_addr只能为ipv4转换 属于低版本 不建议用
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);//ip地址转网络字节序

	int len = sizeof(sockaddr_in);
	if(SOCKET_ERROR == bind(s, (sockaddr*)&addr, len)) {
		cout << "bind error: "<< WSAGetLastError() <<endl;
		return 0;
	}

	//3. 监听, 5 代表正在等待完成相应的TCP三次握手过程的队列长度
	if(SOCKET_ERROR == listen(s, 5)) { // 根据电脑配置设定链接数
		cout << "listen error:"<< WSAGetLastError() << endl;
		return 0;
	}

	//4. 接受客户端请求,并且返回和客户端通讯的套接字
	sockaddr_in addrClient; //保存客户端IP地址端口
	memset(&addrClient, 0, sizeof(sockaddr_in));
	len = sizeof(sockaddr_in);
	SOCKET c = accept(s, (sockaddr*)& addrClient, &len); //成功返回套接字
	if(INVALID_SOCKET == c) {
		cout << "accept error:" << WSAGetLastError()<< endl;
		return 0;
	}

	//5. 发送 接受消息
	int ret = 0;
	do {
		//向刚连接的客户端发送数据, 不能用监听套接字,而应该用accept返回的套接字 c
		char str[20] = "I am server!";
		ret = send(c, str, strlen(str), 0);//把flag置0
		
		//接受客户端的消息
		char buf[64] = {'\0'};
		char *address = new char;
		ret = recv(c, buf, 64, 0); //把flag置 0
		//cout << "recv" << inet_ntoa(addrClient.sin_addr) << ":   " << buf << endl; //inet_ntoa 转换为IP字符串   inet_ntoa只能为ipv4转换 属于低版本 不建议用
		cout << "recv : " << inet_ntop(AF_INET, (void*)&addrClient.sin_addr, address, sizeof(SOCKADDR_IN)) << ":   " << buf << endl; //inet_ntoa 转换为IP字符串
	} while(ret != SOCKET_ERROR && ret != 0); //对方关闭 返回0 , 错误返回 SOCKET_ERROR


	//6. 关闭套接字
	closesocket(s);

	//清理winsock环境
	WSACleanup();

	return 0;
}

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值