目录
一、前言
我们在学习完windows网络编程后,我想大多数人心底一直都有一个问题。
网络编程就是实现多台电脑进行通信的,可我们自己在学习测试的时候,服务器端和客户端都是自己的电脑进行通信(此时ip地址为本机127.0.0),而这样的学习效果没有那么显著,没有发挥真正的网络通信的功能。
那下面就跟着我的步骤:只需要准备两台电脑,服务端和客户端源代码即可实现。
二、详细步骤
1.两台电脑连网(同一WIFF)
我们的第一步骤就是保证两台电脑连上同一个WIFF,手机热点也可以(座机情况不考虑,需要额外步骤)。
2.断开网络防火墙
这一步很重要,我们需要断开自己电脑的防火墙(保护机制),要不然不能实现电脑通信。
3.获取网络IP地址
(注意:这一步骤需要获取的是服务端的IP地址。)第一步: WIN + R , 输入cmd,回车。
第二步:输入 ipconfig,获取连接无线网 WIFF 的 IPv4 地址。
4.修改客户端绑定的IP地址
在上一个步骤获取网络ip地址后,我的是 192.168.0.101,修改客户端代码绑定的IP地址。
5.连接测试
我们需要将准备的两台电脑一台电脑作为服务端,另一台(客户端可以有多个,多台电脑也可以)作为客户端,分别启动项目,连接测试。
这里我用的是我写的的项目代码,可以参考多线程网络实战之仿qq群聊的服务器和客户端,
我这里测试的仿照qq的通过服务器,多个客户端通过通信。测试结果如下:
服务端:
客户端:
三、源码
这里你可以用自己的代码最好,我的代码有些复杂,不推荐,而且运行需要用开发者平台打开,参考文章:多线程网络实战之仿qq群聊的服务器和客户端
1.服务端代码:
如下所示:
// 1. 对于每一个上线的客户端,服务端会起一个线程去维护
// 2. 将受到的消息转发给全部的客户端
// 3. 当某个客户端断开(下线),需要处理断开的链接。怎么处理呢?
#include <stdio.h>
#include <windows.h>
#include <process.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
#define MAX_CLEN 256
#define MAX_BUF_SIZE 1024
SOCKET clnSockets[MAX_CLEN]; // 所有的连接客户端的socket
int clnCnt = 0; // 客户端连接的个数
HANDLE hMutex;
// 将收到的消息转发给所有客户端
void SendMsg(char* msg, int len)
{
int i;
WaitForSingleObject(hMutex, INFINITE);
for (i = 0; i < clnCnt; i++)
{
send(clnSockets[i], msg, len, 0);
}
ReleaseMutex(hMutex);
}
// 处理消息, 收发消息
unsigned WINAPI handleCln(void *arg)
{
SOCKET hClnSock = *((SOCKET *)arg);
int iLen = 0;
char recvBuff[MAX_BUF_SIZE] = { 0 };
while (1)
{
// iLen 成功时返回接收的字节数(收到EOF时为0),失败时返回SOCKETERROR。
iLen = recv(hClnSock, recvBuff, MAX_BUF_SIZE, 0);
//
if (iLen >= 0)
{
// 将收到的消息转发给所有客户端
SendMsg(recvBuff,iLen);
}
else
{
break;
}
}
printf("此时连接的客户端数量 = %d\n", clnCnt);
WaitForSingleObject(hMutex, INFINITE);
for (int i = 0; i < clnCnt; i++)
{
// 找到哪个连接下线的,移除这个连接
if (hClnSock == clnSockets[i])
{
while (i++ < clnCnt)
{
clnSockets[i] = clnSockets[i + 1];
}
break;
}
}
// 断开连接减 1
clnCnt--;
printf("断开连接后连接的客户端数量 = %d\n", clnCnt);
ReleaseMutex(hMutex);
// 断开连接
closesocket(hClnSock);
return 0;
}
int main(int argc, char* argv[])
{
printf("this is Server\n");
//0. 初始化网络
#if 1
// 0 初始化网络库
// 初始化库
WSADATA wsaData;
int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (stu != 0) {
std::cout << "WSAStartup 错误:" << stu << std::endl;
return 0;
}
#endif
HANDLE hThread;
// 1. 创建一个互斥对象
hMutex = CreateMutex(NULL, false, NULL);
// 2. socket 创建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
if (sockSrv == INVALID_SOCKET)
{
std::cout << "socket failed!" << GetLastError() << std::endl;
WSACleanup(); //释放Winsock库资源
return 1;
}
// 3 bind 绑定套接字
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 地址 IP地址any
addrSrv.sin_family = AF_INET; // ipv4协议
addrSrv.sin_port = htons(6000); // 端口号
if ( SOCKET_ERROR == bind(sockSrv, (sockaddr*)&addrSrv, sizeof(SOCKADDR)))
{
std::cout << "bind failed!" << GetLastError() << std::endl;
WSACleanup(); //释放Winsock库资源
return 1;
}
// 4. 监听
if (listen(sockSrv, 5) == SOCKET_ERROR) // 5 是指最大的监听数目,执行到listen
{
printf("listen error = %d\n", GetLastError());
return -1;
}
// 5
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
while (true)
{
// 接受客户端的连接
SOCKET sockCon = accept(sockSrv, (sockaddr*)&addrCli, &len);
// 全局变量要加锁
WaitForSingleObject(hMutex, INFINITE);
// 将连接放到数组里面
clnSockets[clnCnt++] = sockCon;
// 解锁
ReleaseMutex(hMutex);
// 每接收一个客户端的连接,都安排一个线程去维护
hThread = (HANDLE)_beginthreadex(NULL, 0, &handleCln, (void*)&sockCon, 0, NULL);
printf("Connect client IP = %s\n, Num = %d \n", inet_ntoa(addrCli.sin_addr), clnCnt);
}
closesocket(sockSrv);
CloseHandle(hMutex);
WSACleanup();
return 0;
}
2.客户端代码:
如下所示:
// 客户端做的事情:
//1 请求连接上线,
//2 发消息
//3 客户端等待服务端的消息
//4 等待用户自己的关闭(下线)
#include <stdio.h>
#include <windows.h>
#include <process.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
#define NAME_SIZE 256
#define MAX_BUF_SIZE 1024
char szName[NAME_SIZE] = "[DEFAULT]"; // 默认的昵称
char szMsg[MAX_BUF_SIZE]; // 收发数据的大小
unsigned WINAPI SendMsg(void* arg)
{
SOCKET hClnSock = *((SOCKET*)arg);
char szNameMsg[NAME_SIZE + MAX_BUF_SIZE] = { 0 }; // 昵称和消息
while (1)
{
memset(szMsg, 0, MAX_BUF_SIZE);
// 阻塞这一句,等待控制台的消息
//fgets(szMsg, MAX_BUF_SIZE, stdin);
std::cin >> szMsg;
if (!strcmp(szMsg, "Q\n") || !strcmp(szMsg, "q\n"))
{
// 处理下线
closesocket(hClnSock);
exit(0);
}
// 拼接 名字和字符串一起发送
sprintf_s(szNameMsg, "%s %s", szName, szMsg);
send(hClnSock, szNameMsg, strlen(szNameMsg) + 1, 0);
}
}
unsigned WINAPI RecvMsg(void* arg)
{
SOCKET hClnSock = *((SOCKET*)arg);
char szNameMsg[NAME_SIZE + MAX_BUF_SIZE] = { 0 }; // 昵称和消息
int len;
while (1)
{
len = recv(hClnSock, szNameMsg, sizeof(szNameMsg), 0);
if (len <= 0)
{
break;
return -2;
}
szNameMsg[len] = 0;
std::cout << szNameMsg << std::endl;
// fputs(szNameMsg, stdout);
}
}
int main(int argc, char* argv[])
{
if (argc != 2)
{
printf("必须输入两个参数,包括昵称\n");
printf("例如: WXS\n");
system("pause");
return -1;
}
sprintf_s(szName, "[%s]", argv[1]);
printf("this is Client");
//0. 初始化网络
#if 1
// 0 初始化网络库
// 初始化库
WSADATA wsaData;
int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (stu != 0) {
std::cout << "WSAStartup 错误:" << stu << std::endl;
return 0;
}
#endif
// 定义两个线程
HANDLE hSendThread, hRecvThread;
// 1. 建立 socket
SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
if (sockCli == INVALID_SOCKET)
{
std::cout << "socket failed!" << GetLastError() << std::endl;
WSACleanup(); //释放Winsock库资源
return 1;
}
// 2, 配置IP地址 和 端口号
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET; // ipv4协议
addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.7"); // 地址 IP地址any
addrSrv.sin_port = htons(6000); // 端口号
// 3. 连接服务器
int res = connect(sockCli, (sockaddr*)&addrSrv, sizeof(sockaddr));
// 4. 发送服务器消息,启动线程
hSendThread = (HANDLE)_beginthreadex(NULL, 0, &SendMsg, (void*)&sockCli, 0, NULL);
// 5. 等待
hRecvThread = (HANDLE)_beginthreadex(NULL, 0, &RecvMsg, (void*)&sockCli, 0, NULL);
WaitForSingleObject(hSendThread,INFINITE);
WaitForSingleObject(hRecvThread, INFINITE);
closesocket(sockCli);
WSACleanup();
return 0;
}