使用C++基于windows平台下实现Tcp聊天服务器端,Select方式
main.cpp
#include
#include
#include
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
#include
#include
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;
}