C++实现一个简单的客户端与服务端的通信(笔记附代码)

这篇具有很好参考价值的文章主要介绍了C++实现一个简单的客户端与服务端的通信(笔记附代码)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

一、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<winsock.h>
#pragma comment(lib,"ws2_32.lib")

一、Socket的客户端与服务端的通讯原理

如图所示:图左(客户端创建过程) 图右(服务器创建过程

C++实现一个简单的客户端与服务端的通信(笔记附代码)
交互过程:图左(客户端创建过程) 图右(服务器创建过程)

二、各接口介绍

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 <iostream>
#include<winsock.h>
#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 <iostream>
#include<winsock.h>
#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");
}


效果展示:

C++实现一个简单的客户端与服务端的通信(笔记附代码)

完结撒花

当然、以上都是一些比较简单的实现、更加深层次我的也慢慢学习咯~比如像这个客户端和服务端发送数据、我们完全可以再发送数据和接收数据之前对数据进行一些处理、也就是说我们可以先加一个判断、先判断这个发过过来的数据是否符合我想接收的要求、如果不是我选择不往下接续接收信息。

以上只是个人学习的一些基础、有哪里不对的话 欢迎纠正哈C++实现一个简单的客户端与服务端的通信(笔记附代码)文章来源地址https://www.toymoban.com/news/detail-459984.html

到了这里,关于C++实现一个简单的客户端与服务端的通信(笔记附代码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • .NetCore gRpc 客户端与服务端的单工通信Demo

    方式一 使用vs 2022(也可以是其他版本)创建一个grpc的服务,如下这样 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uipEG9Xu-1687172462785)(C:UsersAdministratorAppDataRoamingTyporatypora-user-imagesimage-20230619183828284.png)] 简单方便,创建项目后的目录结构如下图

    2024年02月09日
    浏览(39)
  • C++实现客户端/服务端通信(一)

    存放协议族、端口和地址信息 sockaddr结构体为了统一地址结构的表示方法,统一接口函数,但是操作不方便,所以定义了等价的sockaddr_in结构体,其大小与sockaddr相同,可以强制转换为sockaddr 之所以搞两个结构体,可能是因为sockaddr可以用于IPV4,后续也可以用于IPV6,sockaddr_i

    2024年02月11日
    浏览(28)
  • Java实现WebSocket客户端和服务端(简单版)

    天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。 写在前面: WebSocket是一种在单个TCP连接上进行全双工通信的协议。 WebSocket通信协议于

    2024年02月08日
    浏览(35)
  • C#实现简单TCP服务器和客户端网络编程

    在C#中进行网络编程涉及许多类和命名空间,用于创建和管理网络连接、传输数据等。下面是一些主要涉及的类和命名空间: System.Net 命名空间: 这个命名空间提供了大部分网络编程所需的类,包括: IPAddress :用于表示IP地址。 IPEndPoint :表示IP地址和端口号的组合。 Socke

    2024年02月11日
    浏览(44)
  • C++实现WebSocket通信(服务端和客户端)

    天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。 这里单纯是个人总结,如需更官方更准确的websocket介绍可百度 websocket是一种即时通讯协

    2024年02月09日
    浏览(29)
  • Socket实例,实现多个客户端连接同一个服务端代码&TCP网络编程 ServerSocket和Socket实现多客户端聊天

    Java socket(套接字)通常也称作\\\"套接字\\\",用于描述ip地址和端口,是一个通信链的句柄。应用程序通常通过\\\"套接字\\\"向网络发出请求或者应答网络请求。 使用socket实现多个客户端和同一客户端通讯;首先客户端连接服务端发送一条消息,服务端接收到消息后进行处理,完成后再

    2024年02月12日
    浏览(46)
  • 「Java」Socket实现简单的客户端和服务端通讯 | 公网环境通讯

    💛前情提要💛 本章节是 番外篇 的 Socket 的相关知识~ 接下来我们即将进入一个全新的空间,对代码有一个全新的视角~ 以下的内容一定会让你对 Socket 有一个颠覆性的认识哦!!! 以下内容干货满满,跟上步伐吧~ 作者介绍: 🎓 作者: 热爱编程不起眼的小人物🐐 🔎 作者

    2023年04月16日
    浏览(33)
  • AI对话交互场景使用WebSocket建立H5客户端和服务端的信息实时双向通信

    WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许 服务端主动向客户端推送数据 。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。 初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HT

    2024年02月03日
    浏览(36)
  • Java分别用BIO、NIO实现简单的客户端服务器通信

    前言: Java I/O模型发展以及Netty网络模型的设计思想 Java BIO是Java平台上的BIO(Blocking I/O)模型,是Java中用于实现同步阻塞网络编程的一种方式。 在Java中,使用BIO模型需要通过Socket和ServerSocket类来完成网络连接和数据传输,但是由于BIO是同步阻塞的,所以会导致线程阻塞和资

    2024年02月09日
    浏览(28)
  • 【网络原理】使用Java基于UDP实现简单客户端与服务器通信

    我们用Java实现UDP数据报套接字编程,需要借用以下API来实现 网络编程, 本质上是要操作网卡. 但是网卡不方便直接操作. 在操作系统内核中, 使用了一种特殊的叫做 “socket” 这样的文件来抽象表示网卡. 因此进行网络通信, 势必需要先有一个 socket 对象. DatagramSocket 是UDP Socket,

    2024年03月11日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包