C++实现一个简单的客户端与服务端的通信(笔记附代码)-创新互联
目录
成都创新互联公司是一家专注于成都网站设计、网站建设与策划设计,名山网站建设哪家好?成都创新互联公司做网站,专注于网站建设10多年,网设计领域的专业建站公司;建站业务涵盖:名山等地区。名山做网站价格咨询:18982081108
前言
一、Socket的客户端与服务端的通讯原理
二、各接口介绍
1.WSAStartup:异步启动套接字命令
2.Socket创建套接字
3.bind:绑定套接字
4.listen:监听
5.accept:接受连接请求
6. connet:发送连接请求
7.send:发送数据
8.recv:接收数据函数
9.closesocket, WSACleanup:释放socket
三、代码块的简单实现(可直接使用)
(1) 服务器
(2)客户端
效果展示:
完结撒花
前言
首先、在敲代码之前我们必须先要了解服务端与客户端之前的通讯原理,否则我们敲代码将会无从下手、了解原理后也会对我们的敲代码的效率达到事半功倍的效果!
在c++里面有一个我们可以通过直接调用一些api来实现、前提是我们要声明其头文件并且连接到其提供的动态链接库!
在Windows系统中的刚好有这么一个、即Winsock。声明头文件为:
#include#pragma comment(lib,"ws2_32.lib")
一、Socket的客户端与服务端的通讯原理如图所示:图左(客户端创建过程) 图右(服务器创建过程)
二、各接口介绍 1.WSAStartup:异步启动套接字命令1、函数原型:
int WSAAPI WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
2、参数:
@WORD wVersionRequested:调用者可以使用的Windows套接字规范的最高版本。 高位字节指定次要版本号;
低位字节指定主要版本号。[这个版本我也不太清楚、但是大部分人用的是2.2、
所以我也用的2.2]
但是可借鉴这篇文章:
@LPWSADATA lpWSAData:指向WSADATA数据结构的指针,该数据结构将接收Windows套接字实现的详细信息
3、返回值:
返回0执行正确
否则失败
4、代码如下:
WSADATA wsdata;
if (WSAStartup(MAKEWORD(2, 2), &wsdata))
{
std::cout<< "init socket failed!"<< std::endl;
WSACleanup();
return FALSE;
}
2.Socket创建套接字1、函数原型:
SOCKET socket(int af,int type,int protocl);
参数:
@int af:第一个参数(af)指定地址族,对于TCP/IP协议的套接字他有以下两个参数:
AF_INET,PF_INET IPV4协议
PF_INET6 IPV6协议
其他还有很多协议这里不做介绍
@int type:用于设置套接字通信的类型,有流式套接字(SOCKET_STREAM)和数据包套接字
(SOCK_DGRAM)
SOCK_STREAM TCP连接,提供有序化的、可靠的、双向连接的字节流。支持带外数据传输
SOCK_DGRAM UDP连接
@int protocl:用于制定某个协议的特定类型,即type类型中的某个类型,通常一种协议只有一
种类型,该参数可以直接被设置为0;如果协议有多种类型,则需要指定协议类
型
2、返回值
如果没有错误发生,socket()返回一个与建立的套接字相关的描述符。
否则它返回值INVALID_SOCKET,错误码可通过调用WSAGetLastError()函数得到
3、代码范例:
//定义一个套接字 作为服务端
SOCKET s_server;
s_server = socket(PF_INET, SOCK_STREAM, 0);
if (s_server == INVALID_SOCKET)
{
std::cout<< "create socket fail"<< std::endl;
WSACleanup();
return FALSE;
}
3.bind:绑定套接字1、函数原型:
int bind(
SOCKET s,
const sockaddr *addr,
int namelen
);
@ SOCKET s:要绑定的套接字
@const sockaddr *addr:指向要分配给绑定套接字的本地地址的sockaddr结构的指针
struct sockaddr {
ushort sa_family;
char sa_data[14];
};
struct sockaddr_in {
short sin_family; //指定协议家族 ipv4 or ipv6
u_short sin_port; //指定端口号
struct in_addr sin_addr; //指定地址 也可以是所有ip 参数为:INADDR_ANY
char sin_zero[8]; //没有特殊意义不用管
};
struct in_addr {
union {
struct {
u_char s_b1;
u_char s_b2;
u_char s_b3;
u_char s_b4;
} S_un_b;
struct {
u_short s_w1;
u_short s_w2;
} S_un_w;
u_long S_addr; //ip地址
} S_un;
};
@int namelen:sockaddr结构的指针的大小
2、返回值
如果没有错误发生,bind()返回0。
否则返回值SOCKET_ERROR,错误码可通过调用WSAGetLastError()函数得到
3、代码范例:
//填充服务端信息
SOCKADDR_IN server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8224);
server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//数据绑定服务器 s_server为服务端套接字
if (bind(s_server, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
std::cout<< "Binding Socket fail.......!"<< std::endl;
WSACleanup();
return FALSE;
}
4.listen:监听
1、函数原型:
int PASCAL FAR listen (
_In_ SOCKET s,
_In_ int backlog);
@SOCKET s:服务端的socket,也就是socket函数创建的
@int backlog:挂起的连接队列的大长度,由用户自主选择。但是我们知道我们电脑处理线程的时候用一时间只能处理n个线程、这里也是一样的原理,我们创建的服务器他可能不支持你定义的这个队列长度的用户
比如:一个洗手间 只能同时供4个人使用 而这个时候来了8个人 那么其他4个人则需要外面等待。
所以一般填写这个参数为SOMAXCONN
作用是让系统自动选择最合适的个数
不同的系统环境不一样,所以这个合适的数也不一样
2、返回值
成功返回0
失败返回SOCKET_ERROR
具体错误码:WSAGetLastError()得到
3、代码示例:
//监听
if (listen(s_server, 1) == SOCKET_ERROR)
{
std::cout<< "Listening Socket fail........!"<< std::endl;
WSACleanup();
return FALSE;
}
5.accept:接受连接请求1、函数原型:
SOCKET WSAAPI accept (
SOCKET s,
struct sockaddr FAR* addr,
int FAR* addrlen
);
@SOCKET s:该套接字在用作accept()函数的参数前必须先调用过listen()函数,此时它正处于监听连接的状态。
@struct sockaddr FAR* addr:一个可选的指向缓冲区的指针,用来接收连接实体的地址,在通讯层使用。addr的确切格式由套接字创建时建立的地址族决定
@int FAR* addrlen:一个可选的指向整数的指针,它调用时含有地址addr指向的空间的大小,返回时含有返回的地址的确切长度(字节数)。
2、返回值
如果没有错误发生,accept()返回一个SOCKET类型的值,表示接收到的套接字的描述符。
否则返回值INVALID_SOCKET,错误码可通过调用WSAGetLastError()函数得到。
2、代码范例:
//接受连接请求
sockaddr_in accept_addr; //用来记录请求连接的套接字信息
int len = sizeof(SOCKADDR);
s_accept = accept(s_server, (SOCKADDR*)&accept_addr, &len );
if (s_accept == SOCKET_ERROR) {
std::cout<< "Error: accept failed !"<< std::endl;
WSACleanup();
break;
}
6. connet:发送连接请求1、函数原型:
int WSAAPI connect (
SOCKET s,
const struct sockaddr FAR* name,
int namelen //socket address结构的字节数
);
@SOCKET s:发出连接请求的套接字的描述符
@const struct sockaddr FAR* name:对等方的套接字的地址、就是你创建客户端的套接字时绑定ip和
端口号的套接字地址
@namelen: 套接字类型大小
2、返回值
如果没有错误发生,connect()返回0。
否则返回值SOCKET_ERROR,错误码可通过调用WSAGetLastError()函数得到。
3、代码范例:
//填充服务端信息
//SOCKADDR_IN server_addr;
//server_addr.sin_family = AF_INET;
//server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//server_addr.sin_port = htons(8224);
//创建套接字
//SOCKET client= socket(AF_INET, SOCK_STREAM, 0);
if (connect(client, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
std::cout<< "Error: connect server failed !"<< std::endl;
WSACleanup();
return -1;
}
7.send:发送数据1、函数原型:
int WSAAPI send(
SOCKET s,
const char FAR * buf,
int len,
int flags
);
@SOCKET s: 已连接的套接字描述符
@ const char FAR * buf:指向存有发送数据的缓冲区的指针
@int len:缓冲区buf中数据长度
@ int flags:一般为0,为阻塞发送 即发送不成功会一直阻塞,直到被某个信号终端终止,或者直到发
送成功为止。
指定MSG_NOSIGNAL,表示当连接被关闭时不会产生SIGPIPE信号
指定MSG_DONTWAIT 表示非阻塞发
2、返回值
如果没有错误发生,send()返回总共发送的字节数(注意,这可能比len指示的长度小)。
否则它返回SOCKET_ERROR,错误码可通过调用WSAGetLastError()函数得到。
2、代码范例:
char temp[1024] = { 0 };
snprintf(temp, sizeof(temp), "%s", detectInfo);
int sendLen = send(socket, (char*)temp, sizeof(temp), 0);
if (sendLen< 0) {
std::cout<< "Error: send info to server failed !"<< std::endl;
return -1;
}
8.recv:接收数据函数 1、函数原型:
int PASCAL FAR recv (
SOCKET s,
writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
int len,
int flags);
@SOCKET s:已连接的套接字描述符。
@char FAR * buf:指向接收输入数据缓冲区的指针
@int len:buf参数所指缓冲区的长度
@int flags:一般为0
2、返回值
如果没有错误发生,recv()返回收到的字节数。
如果连接被关闭,返回0。
否则它返回SOCKET_ERROR,错误码可通过调用WSAGetLastError()函数得到。
3、代码示例:
char recv_buf[8192] = { 0 };
int recv_len = recv(socket, recv_buf, sizeof(recv_buf), 0);
if (recv_len< 0) {
std::cout<< "Error: receive info from server failed !"<< std::endl;
return -1;
}
9.closesocket, WSACleanup:释放socket使用完后续如果不再使用 记住一定要释放相关资源 否则你懂的。
//关闭套接字 参数:需要关闭的套接字描述符
closesocket(s_server);
//释放DLL资源
WSACleanup();
三、代码块的简单实现(可直接使用)
(1) 服务器// ServerTest.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include#include#pragma comment(lib,"ws2_32.lib")
int main()
{
std::cout<< "Hello World!\n";
WSADATA wsdata;
std::cout<< "start up now ...."<< std::endl;
if (WSAStartup(MAKEWORD(2, 2), &wsdata))
{
std::cout<< "init socket failed!"<< std::endl;
WSACleanup();
return FALSE;
}
std::cout<< "before create socket ...."<< std::endl;
SOCKET s_server, s_accept;
s_server = socket(PF_INET, SOCK_STREAM, 0);
if (s_server == INVALID_SOCKET)
{
std::cout<< "create socket fail"<< std::endl;
WSACleanup();
return FALSE;
}
std::cout<< "create socket success...."<< std::endl;
//填充服务端信息
SOCKADDR_IN server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8226);
//当然这里也可以将 127.0.0.1改成你想要的ip或者 INADDR_ANY 为向所有ip发送信息
server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//数据绑定服务器 s_server为服务端套接字
std::cout<< "before bind socket ...."<< std::endl;
if (bind(s_server, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
std::cout<< "Binding Socket fail.......!"<< std::endl;
WSACleanup();
return FALSE;
}
std::cout<< "bind socket success...."<< std::endl;
std::cout<< "before listen socket ...."<< std::endl;
if (listen(s_server, 1) == SOCKET_ERROR)
{
std::cout<< "Listening Socket fail........!"<< std::endl;
WSACleanup();
return FALSE;
}
std::cout<< "listen socket success...."<< std::endl;
sockaddr_in accept_addr; //用来记录请求连接的套接字信息
int len = sizeof(SOCKADDR);
char recv_buf[8192] = { 0 };
char send_buf[1024] = { 0 };
while (true)
{
//接受连接请求
std::cout<< "wait accept...."<< std::endl;
s_accept = accept(s_server, (SOCKADDR*)&accept_addr, &len);
if (s_accept == SOCKET_ERROR) {
std::cout<< "Error: accept failed !"<< std::endl;
WSACleanup();
break;
}
std::cout<< "建立连接成功...."<< std::endl;
while (true)
{
std::cout<< "等待客户端数据中...."<< std::endl;
int recv_len = recv(s_accept, recv_buf, sizeof(recv_buf), 0);
if (recv_len< 0) {
std::cout<< "Error: receive info from server failed !"<< std::endl;
return -1;
}
std::cout<< "已收到数据,客户端的数据是:"<< recv_buf<< std::endl;
std::cout<< "给客户端返回数据中..."<< std::endl;
snprintf(send_buf, sizeof(send_buf), "%s", "我给你返回信息了");
int sendLen = send(s_accept, (char*)send_buf, sizeof(send_buf), 0);
if (sendLen< 0) {
std::cout<< "Error: send info to server failed !"<< std::endl;
return -1;
}
std::cout<< "给客户端返回数据完毕"<< std::endl;
}
}
}
(2)客户端// ClientTest.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include#include#pragma comment(lib,"ws2_32.lib")
int main()
{
std::cout<< "Hello World!\n";
WSADATA wsdata;
if (WSAStartup(MAKEWORD(2, 2), &wsdata))
{
std::cout<< "init socket failed!"<< std::endl;
WSACleanup();
return FALSE;
}
//检测版本号
if (LOBYTE(wsdata.wVersion) != 2 || HIBYTE(wsdata.wHighVersion) != 2) {
std::cout<< "Error: wsadata.wVersion != 2 ."<< std::endl;
WSACleanup();
return -1;
}
SOCKET client;
client = socket(PF_INET, SOCK_STREAM, 0);
if (client == INVALID_SOCKET)
{
std::cout<< "create socket fail"<< std::endl;
WSACleanup();
return FALSE;
}
//填充服务端信息
SOCKADDR_IN server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(8226);
//发送连接请求 请求连接服务器
if (connect(client, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
std::cout<< "Error: connect server failed !"<< std::endl;
WSACleanup();
return -1;
}
std::cout<< "成功连接到服务器"<< std::endl;
//发送数据
char temp[1024] = { 0 };
std::cout<< "开始发送数据....."<< std::endl;
snprintf(temp, sizeof(temp), "%s", "我是客户端 我发送信息给你了服务端");
int sendLen = send(client, (char*)temp, sizeof(temp), 0);
if (sendLen< 0) {
std::cout<< "Error: send info to server failed !"<< std::endl;
return -1;
}
std::cout<< "发送数据成功、等待服务器响应....."<< std::endl;
char recv_buf[8192] = { 0 };
int recv_len = recv(client, recv_buf, sizeof(recv_buf), 0);
if (recv_len< 0) {
std::cout<< "Error: receive info from server failed !"<< std::endl;
return -1;
}
std::cout<< "收到了服务器返回的信息 内容是:"<< recv_buf<< std::endl;
system("pause");
}
效果展示:完结撒花当然、以上都是一些比较简单的实现、更加深层次我的也慢慢学习咯~比如像这个客户端和服务端发送数据、我们完全可以再发送数据和接收数据之前对数据进行一些处理、也就是说我们可以先加一个判断、先判断这个发过过来的数据是否符合我想接收的要求、如果不是我选择不往下接续接收信息。
以上只是个人学习的一些基础、有哪里不对的话 欢迎纠正哈
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
网站标题:C++实现一个简单的客户端与服务端的通信(笔记附代码)-创新互联
浏览地址:http://hbruida.cn/article/dhgggg.html