一.前言
学计算机迟早会接触到通信。
而简单可用的代码来入门很重要。
但网上以及书本上给的的版本可能都不太易用。
本文主要涉及简单的客户端与服务端可用编码,旨在于注释中讲道理。(直接粘代码到VS里看吧)
二.引入
如果大家之前一点都没接触到网络通信的话,那么这里给大家介绍一下Socket:一个用于通信的接口。
c++简单的通信用它行,不用考虑什么其他的库优劣性,可以说是很省心了。
想要了解更多的话,本文不提供。你去搜《计算机网络》,你值得拥有。
三.开门见山铺代码
一点点引导:
通信肯定要大于等于两个个体才能通信。
在这里我们简单的分成了:服务器端 客户端
以下代码使用TCP的方法实现的,关于TCP其他文章有很多讲述,这里就不一一论述了。
服务端代码:
/*服务端
**SocketService.cpp
**2018/12/28
**Author:零医一零
**注:由于注重解释学习,所以请忽略该代码的格式
*/
#include<WINSOCK2.h>//windows socket的头文件 系统自带
#include<iostream>//这个都不知道我也没办法
using namespace std;//这句话我也很无奈啊
#pragma comment(lib,"ws2_32.lib")//用网络API函数的时候,就要用这条语句加载ws2_32.lib库(或者你自己去动态载入ws2_32.dll)
int main()
{
WSADATA wsaData;//用来存储被 WSAStartup 函数调用后返回的数据
WSAStartup(0x0202, &wsaData);
/*
必要性:WSAStartup必须是Windows Sockets中第一个使用的函数,成功调用后才能进一步使用Windows Sockets API函数
作用:指明Windows Sockets API的版本号及获得特定Windows Sockets实现的细节
用法:前参是一个WORD(双字节)型数值,指定了应用程序需要使用的Winsock规范的最高版本。
后参是WSADATA数据结构的指针,用来接收Windows Sockets实现的细节。
*/
//1.套接字(SOCKET神奇的翻译)的创建和关闭
SOCKET listenSocket; //定义套接字
listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//创建套接字
/*
作用:创建套接字时返回小整数作为描述符来标识它
用法:AF表示ADDRESS FAMILY 地址族,SOCK_STREAM表示能保证数据正确传送到对方的SOCKET,IPPROTO_TCP表示TCP协议
补充:当不使用socket创建的套接字时,应该调用closesocket函数将它关闭(int closesocket(SOCKET s); )
*/
//2.绑定套接字到指定的IP地址和端口号
SOCKADDR_IN local;
//用于建立listenSocket的本地关联sockaddr_in结构
local.sin_family = AF_INET;
//与socket函数中的af参数的含义相同,见上
local.sin_port = htons(1234);
//如果端口号等于0,执行时系统会自动分配唯一的端口号,其值在1024~5000之间。
local.sin_addr.s_addr = htonl(INADDR_ANY);//如果地址等于INADDR_ANY,会自动使用当前主机配置的所有IP地址;
/*也可以是local.S_un.S_addr.s_addr = htonl(INADDR_ANY);(因为winsock2.h中定义了#define s_addr S_un.S_addr)
sin_port字段和sin_addr字段分别指定套接字需要绑定的端口号和IP地址。放入这两个字段的数据的字节顺序必须是网络字节顺序
(因为网络字节顺序和Intel CPU的字节顺序刚好相反,所以必须首先使用htons函数进行转换)*/
bind(listenSocket, (struct sockaddr *) &local, sizeof(local));
/*
作用:bind函数用在没有建立连接的套接字上,它的作用是绑定面向连接的或者无连接的套接字。
套接字被socket函数创建以后,存在于指定的地址家族里,但它是未命名的。
bind函数通过安排一个本地名称到未命名的socket而建立此socket的本地关联。
用法:1.SOCKET 2.SOCKADDR_IN 3.SOCKADDR_IN的长度
*/
//3.设置套接字进入监听状态
listen(listenSocket, 1);
cout << "开启监听:\n";
/*
作用:设置套接字进入监听状态。
用法:套接字,监听队列中允许保持的尚未处理的最大连接数量
特性:函数执行成功后,套接字s进入了被动模式,到来的连接会被通知要排队等候接受处理。
在同一时间处理多个连接请求的服务器通常使用listen函数
如果一个连接请求到达,并且排队已满,客户端将接收到WSAECONNREFUSED错误。
*/
//4.接受连接请求
SOCKET clientSocket;//忘记了就看上面
SOCKADDR_IN client;//忘记了就看上面
int addrSize = sizeof(SOCKADDR_IN);
clientSocket = accept(listenSocket, (struct sockaddr *) &client, &addrSize);
/*
作用:取出未处理连接中的第一个连接,然后为这个连接创建新的套接字,返回它
特性:程序默认工作在阻塞模式下:如果没有未处理的连接存在,就会等待下去,直到有新连接发生来进行处理
用法:addrlen参数用于指定addr所指空间的大小,也用于返回地址的实际长度。如果是NULL,则没有关于远程地址的信息返回
*/
cout << "Accepted client:%s:%d\n"//反馈连接信息
<< inet_ntoa(client.sin_addr)//inet_ntoa将一个十进制网络字节序转换为点分十进制IP格式的字符串。
<< ntohs(client.sin_port); //ntohs将一个16位数由网络字节顺序转换为主机字节顺序
//5.与客户端通信
int const CLIENT_MSG_SIZE = 128;//接收缓冲区长度
char cacheMSG[CLIENT_MSG_SIZE]; //接收缓冲区的基地址
while (TRUE) {
int size = recv(clientSocket, cacheMSG, CLIENT_MSG_SIZE, 0);
/*
作用:recv()从接收缓冲区拷贝数据。成功时,返回拷贝的字节数,失败返回-1。
用法:前三个请找上述的参数定义。最后一个参数为0的意义:默认是普通接受数据模式
特性:阻塞模式下,recv将会阻塞到缓冲区里至少有一个字节,没有数据时处于休眠状态。
若非阻塞,则立即返回,有数据则返回拷贝的数据大小,否则返回错误-1。
*/
cacheMSG[size] = '\0'; //你懂得
cout << "Received:" << cacheMSG<< endl;
}
//6.结束收尾
closesocket(listenSocket);
closesocket(clientSocket);
WSACleanup();
return 0;
}
服务端部分的实现过程可简述为:
1.创建套接字并初始化
2.绑定套接字到指定地址
3.开启监听
4.接受连接请求
5.获取信息
客户端代码
/*客户端
**SocketClient.cpp
**2018/12/28
**Author:零医一零
**注:由于注重解释学习,所以请忽略该代码的格式
*/
#include <WINSOCK2.H>
#include<iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA wsaData;
WSAStartup(0x0202, &wsaData);//申请Windows Sockets API支援
SOCKET clientSocket;//创建并初始化套接字
clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN server;//填充服务器地址信息
server.sin_family = PF_INET;
server.sin_port = htons(1234);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
//过了一遍服务器的代码,上面肯定就看得懂了。不懂,就回头再看看吧。
//连接服务器
connect(clientSocket, (struct sockaddr *) &server, sizeof(server));
/*
作用:连接后可以用clientSocket来使用这个连接
用法:要记录的套接字,保存了远程地址信息的server,地址大小。
P.S:跟服务器里的recv()几乎相同。
*/
//发送信息
while (TRUE) {
char Msg[128];//定义发送缓存区
cout<<"Send:"; cin>>Msg;//输入要发送的字节
send(clientSocket, Msg, strlen(Msg), 0);
/*
作用:snd()从发送缓冲区拷贝数据。
用法:前三个请找上述的参数定义。最后一个参数为0的意义:默认是普通接受数据模式
*/
}
//结束客户端;
closesocket(clientSocket);
WSACleanup();
return 0;
}
客户端部分的实现过程可简述为:
1.创建套接字并初始化
2.绑定套接字到指定地址
4.发送连接请求
5.发送信息
四.结语
作者尽最大的能力去解释其中每一行代码的缘由,希望大家看完后,能够很清晰的了解如何实现c++通信程序。
但该代码实现的程序只是建立在本地的通信连接上。
也没有考虑到关闭,依靠while(true)来持续运行程序。
有兴趣的读者可以独自完善,来模拟一些简单通讯软件。