网络编程
- 前提是应用程序所在的机器在一个网络(同一主机,局域网,互联网)之中
- 多个程序(进程)之间能交换数据
- 全双共(双向通信),即时,安全的TCP协议,一种用于网络数据传输的协议
- IP协议用于机器在网络中的定位
Window下用TCP协议实现网络编程
网络应用的两种架构:C/S架构,B/S架构
通用windows网络编程的步骤
服务器 | 客户端 | 备注 |
---|---|---|
1. 请求协议版本 | 1. 请求协议版本 | 统一服务器和客户端使用的通信协议 |
2. 创建socket | 2. 创建socket | 类似于微信这么一个通信的工具 |
3. 创建本机协议地址族 | 3. 获取服务器的协议地址族 | 地址族即存储IP地址,网络端口,通信协议相关信息的结构体(类似于服务器通信工具(socket)的微信号) |
4. 绑定 | – | 将协议地址族绑定到服务器的socket上 |
5. 监听 | – | 为了形成一个安全稳定的传输通道 |
6. 等待客户端连接 | 4. 连接服务器 | – |
7. 通信(收/发) | 5. 通信(收/发) | – |
8. 关闭socket | 6. 关闭socket | – |
9. 清理协议 | 7. 清理协议 | – |
Demo第一版:单客户端-服务器(双向)
客户端程序
#include <stdio.h>
#include <windows.h>//包含window封装的通信相关的结构及接口
//加载静态库,该库包含WSAStartup,WSAClean,socket等接口
#pragma comment(lib, "ws2_32.lib")
int main()
{
//1. 请求协议版本2.2
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);//wsaData存储返回的协议版本
if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("请求协议版本失败!\n");
return -1;
}
printf("请求协议版本成功!\n");
//2. 创建socket
//para1 —— 通信协议类型
//para2 —— 通信所用的数据类型,TCP使用的是数据流
//para3 —— 保护方式
SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(SOCKET_ERROR == clientSocket)//-1
{
printf("创建socket失败\n!");
//清理协议
WSACleanup();
return -2;
}
printf("创建socket成功!\n");
//3. 获取协议地址族
SOCKADDR_IN addr = { 0 };
addr.sin_family = AF_INET;//协议版本
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//IP地址,inet_addr将字符串转成整数存储
addr.sin_port = htons(10086);//端口号,0~65535,一般用10000左右的端口
//4. 连接服务器
int r = connect(clientSocket, (sockaddr*)&addr, sizeof(addr));
if(-1 == r)
{
printf("连接服务器失败!\n");
//关闭socket
closesocket(clientSocket);
//清理协议
WSACleanup();
return -2;
}
char buff[1024];
while(1)
{
//清空buff
memset(buff, 0, 1024);
printf("请输入内容:");
scanf("%s", buff);
//发送内容至服务器
send(clientSocket, buff, strlen(buff), NULL);
}
getchar();
return 0;
}
服务器程序
#include <stdio.h>
#include <windows.h>//包含window封装的通信相关的结构及接口
//加载静态库,该库包含WSAStartup,WSAClean,socket等接口
#pragma comment(lib, "ws2_32.lib")
int main()
{
//1. 请求协议版本2.2
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);//wsaData存储返回的协议版本
if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("请求协议版本失败!\n");
return -1;
}
printf("请求协议版本成功!\n");
//2. 创建socket
//para1 —— 通信协议类型
//para2 —— 通信所用的数据类型,TCP使用的是数据流
//para3 —— 保护方式
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(SOCKET_ERROR == serverSocket)//-1
{
printf("创建socket失败\n!");
//清理协议
WSACleanup();
return -2;
}
printf("创建socket成功!\n");
//3. 创建协议地址族
SOCKADDR_IN addr = { 0 };
addr.sin_family = AF_INET;//协议版本
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//IP地址,inet_addr将字符串转成整数存储
addr.sin_port = htons(10086);//端口号,0~65535,一般用10000左右的端口
//4. 将socket和协议地址族绑定在一起
int r = bind(serverSocket, (sockaddr*)&addr, sizeof(addr));
if(-1 == r)
{
printf("绑定失败!\n");
//关闭socket
closesocket(serverSocket);
//清理协议
WSACleanup();
return -3;
}
//5. 监听socket(防止有多个客户端同时连接服务器,则创建队列进行排队,10表示队列长度)
r = listen(serverSocket, 10);
if(-1 == r)
{
printf("监听失败!\n");
//关闭socket
closesocket(serverSocket);
//清理协议
WSACleanup();
return -4;
}
printf("监听成功!\n");
//6. 等待客户连接(阻塞函数,尾生抱柱)
SOCKADDR_IN clientAddr = { 0 };//存储客户端的地址族
int len = sizeof(clientAddr);
SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &len);
if(SOCKET_ERROR == clientSocket)//若有客户请求,但请求失败
{
printf("服务器宕机!\n");
//关闭socket
closesocket(serverSocket);
//清理协议
WSACleanup();
return -5;
}
printf("有客户端连接了: %s\n", inet_ntoa(clientAddr.sin_addr));//inet_ntoa将整形IP转成字符串
//7. 通信
char buff[1024];
while(1)
{
//接收客户端发过来的消息
r = recv(clientSocket, buff, 1023, NULL);
if(r > 0)
{
buff[r] = 0;//字符串的结束符
//打印接收到的消息
printf(">>%s\n", buff);
}
}
getchar();
return 0;
}
Demo第二版:多客户端-服务器(广播)
- 服务器可以接收多个客户端的消息
- 服务器可以将每一个客户端发过来的消息转发给所有的客户端
- 将通信逻辑放在单独的线程中,以解决多客户端并发访问的情况
- 线程主要循环接收客户端消息并转发给所有客户端
- 客户端需要循环发送消息,也需要循环接收消息,所以接收消息也需要单独创建一个线程
客户端修改
//1. 将创建的客户端socket改成全局变量
SOCKET clientSocket;
//2. 将接收服务器消息的逻辑封装成函数
void ReceiveMsg()
{
char buff[1024] = { 0 };
int r = 0;
while(1)
{
r = recv(clientSocket, buff, 1023, NULL);
if(r > 0)
{
buff[r] = 0;
printf("%s\n", buff);
}
}
}
//3. 创建单独的接收消息线程,必须放在接收循环的前面,不然没机会创建了
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ReceiveMsg, NULL, NULL, NULL);
服务器端修改
//1. 将收到的客户端请求的socket存在数组里面,用于保存多个客户端的连接
SOCKET clientSocket[1024];
//2. 构建变量实时记录连接进来的客户端的编号及总数,记录连接客户端的个数
int count = 0;
//3. 将通信逻辑封装成函数,负责接收指定客户端的消息,并分发给所有客户端
//idx用于标识哪个客户端
void Communication(int idx)
{
//7. 通信
char buff[1024];
int r = 0;
while(1)
{
//接收客户端发过来的消息
r = recv(clientSocket[idx], buff, 1023, NULL);
if(r > 0)
{
buff[r] = 0;//字符串的结束符
//打印接收到的消息
printf(">>%d:%s\n", idx, buff);
//将收到的消息分发给所有的客户端
for(int i = 0; i < count; i++)
{
send(clientSocket[i], buff, strlen(buff), NULL);
}
}
}
}
//4. 循环接收客户端请求,并保存起来
//每接收一个客户端请求,则单独创建一个线程用于通信
//6. 等待客户连接(阻塞函数,尾生抱柱)
//循环接收客户端连接请求
SOCKADDR_IN clientAddr = { 0 };//存储客户端的地址族
int len = sizeof(clientAddr);
while(1)
{
clientSocket[count] = accept(serverSocket, (sockaddr*)&clientAddr, &len);
if(SOCKET_ERROR == clientSocket[count])//若有客户请求,但请求失败
{
printf("服务器宕机!\n");
//关闭socket
closesocket(serverSocket);
//清理协议
WSACleanup();
return -5;
}
count++;
printf("有客户端%d连接了: %s\n", count-1, inet_ntoa(clientAddr.sin_addr));//inet_ntoa将整形IP转成字符串
//一旦有客户端连接,则专门创建一个线程,用于服务器与该客户端的通信
CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)Communication,(LPVOID)(count-1), NULL,NULL);
}