当前位置:   article > 正文

C++实现一个简单的客户端与服务端的通信(笔记附代码)_c 使用socket收发数据

c 使用socket收发数据

目录

前言

一、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。声明头文件为:

  1. #include<winsock.h>
  2. #pragma comment(lib,"ws2_32.lib")

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

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

交互过程:图左(客户端创建过程) 图右(服务器创建过程)

二、各接口介绍

1.WSAStartup:异步启动套接字命令

  1. 1、函数原型:
  2. int WSAAPI WSAStartup(
  3. WORD wVersionRequested,
  4. LPWSADATA lpWSAData
  5. );
  6. 2、参数:
  7. @WORD wVersionRequested:调用者可以使用的Windows套接字规范的最高版本。 高位字节指定次要版本号;
  8. 低位字节指定主要版本号。[这个版本我也不太清楚、但是大部分人用的是2.2
  9. 所以我也用的2.2]
  10. 但是可借鉴这篇文章:
  11. @LPWSADATA lpWSAData:指向WSADATA数据结构的指针,该数据结构将接收Windows套接字实现的详细信息
  12. 3、返回值:
  13. 返回0执行正确
  14. 否则失败
  15. 4、代码如下:
  16. WSADATA wsdata;
  17. if (WSAStartup(MAKEWORD(2, 2), &wsdata))
  18. {
  19.     std::cout << "init socket failed!" << std::endl;
  20.     WSACleanup();
  21.     return FALSE;
  22. }
 

2.Socket创建套接字

  1. 1、函数原型:
  2. SOCKET socket(int af,int type,int protocl);
  3. 参数:
  4. @int af:第一个参数(af)指定地址族,对于TCP/IP协议的套接字他有以下两个参数:
  5. AF_INET,PF_INET IPV4协议
  6. PF_INET6 IPV6协议
  7. 其他还有很多协议这里不做介绍
  8. @int type:用于设置套接字通信的类型,有流式套接字(SOCKET_STREAM)和数据包套接字
  9. (SOCK_DGRAM)
  10. SOCK_STREAM TCP连接,提供有序化的、可靠的、双向连接的字节流。支持带外数据传输
  11. SOCK_DGRAM UDP连接
  12. @int protocl:用于制定某个协议的特定类型,即type类型中的某个类型,通常一种协议只有一
  13. 种类型,该参数可以直接被设置为0;如果协议有多种类型,则需要指定协议类
  14. 2、返回值
  15. 如果没有错误发生,socket()返回一个与建立的套接字相关的描述符。
  16. 否则它返回值INVALID_SOCKET,错误码可通过调用WSAGetLastError()函数得到
  17. 3、代码范例:
  18. //定义一个套接字 作为服务端
  19. SOCKET s_server;
  20. s_server = socket(PF_INET, SOCK_STREAM, 0);
  21. if (s_server == INVALID_SOCKET)
  22. {
  23. std::cout << "create socket fail" << std::endl;
  24. WSACleanup();
  25. return FALSE;
  26. }

3.bind:绑定套接字

  1. 1、函数原型:
  2. int bind(
  3. SOCKET s,
  4. const sockaddr *addr,
  5. int namelen
  6. );
  7. @ SOCKET s:要绑定的套接字
  8. @const sockaddr *addr:指向要分配给绑定套接字的本地地址的sockaddr结构的指针
  9. struct sockaddr {
  10. ushort sa_family;
  11. char sa_data[14];
  12. };
  13. struct sockaddr_in {
  14. short sin_family; //指定协议家族 ipv4 or ipv6
  15. u_short sin_port; //指定端口号
  16. struct in_addr sin_addr; //指定地址 也可以是所有ip 参数为:INADDR_ANY
  17. char sin_zero[8]; //没有特殊意义不用管
  18. };
  19. struct in_addr {
  20. union {
  21. struct {
  22. u_char s_b1;
  23. u_char s_b2;
  24. u_char s_b3;
  25. u_char s_b4;
  26. } S_un_b;
  27. struct {
  28. u_short s_w1;
  29. u_short s_w2;
  30. } S_un_w;
  31. u_long S_addr; //ip地址
  32. } S_un;
  33. };
  34. @int namelen:sockaddr结构的指针的大小
  35. 2、返回值
  36. 如果没有错误发生,bind()返回0
  37. 否则返回值SOCKET_ERROR,错误码可通过调用WSAGetLastError()函数得到
  38. 3、代码范例:
  39. //填充服务端信息
  40. SOCKADDR_IN server_addr;
  41. server_addr.sin_family = AF_INET;
  42. server_addr.sin_port = htons(8224);
  43. server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
  44. //数据绑定服务器 s_server为服务端套接字
  45. if (bind(s_server, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR)
  46. {
  47. std::cout << "Binding Socket fail.......!" << std::endl;
  48. WSACleanup();
  49. return FALSE;
  50. }

4.listen:监听

  1. 1、函数原型:
  2. int PASCAL FAR listen (
  3. _In_ SOCKET s,
  4. _In_ int backlog);
  5. @SOCKET s:服务端的socket,也就是socket函数创建的
  6. @int backlog:挂起的连接队列的最大长度,由用户自主选择。但是我们知道我们电脑处理线程的时候用一时间只能处理n个线程、这里也是一样的原理,我们创建的服务器他可能不支持你定义的这个队列长度的用户
  7. 比如:一个洗手间 只能同时供4个人使用 而这个时候来了8个人 那么其他4个人则需要外面等待。
  8. 所以一般填写这个参数为SOMAXCONN
  9. 作用是让系统自动选择最合适的个数
  10. 不同的系统环境不一样,所以这个合适的数也不一样
  11. 2、返回值
  12. 成功返回0
  13. 失败返回SOCKET_ERROR
  14. 具体错误码:WSAGetLastError()得到
  15. 3、代码示例:
  16. //监听
  17. if (listen(s_server, 1) == SOCKET_ERROR)
  18. {
  19. std::cout << "Listening Socket fail........!" << std::endl;
  20. WSACleanup();
  21. return FALSE;
  22. }

5.accept:接受连接请求

  1. 1、函数原型:
  2. SOCKET WSAAPI accept (
  3. SOCKET s,
  4. struct sockaddr FAR* addr,
  5. int FAR* addrlen
  6. );
  7. @SOCKET s:该套接字在用作accept()函数的参数前必须先调用过listen()函数,此时它正处于监听连接的状态。
  8. @struct sockaddr FAR* addr:一个可选的指向缓冲区的指针,用来接收连接实体的地址,在通讯层使用。addr的确切格式由套接字创建时建立的地址族决定
  9. @int FAR* addrlen:一个可选的指向整数的指针,它调用时含有地址addr指向的空间的大小,返回时含有返回的地址的确切长度(字节数)。
  10. 2、返回值
  11. 如果没有错误发生,accept()返回一个SOCKET类型的值,表示接收到的套接字的描述符。
  12. 否则返回值INVALID_SOCKET,错误码可通过调用WSAGetLastError()函数得到。
  13. 2、代码范例:
  14. //接受连接请求
  15. sockaddr_in accept_addr; //用来记录请求连接的套接字信息
  16. int len = sizeof(SOCKADDR);
  17. s_accept = accept(s_server, (SOCKADDR*)&accept_addr, &len );
  18. if (s_accept == SOCKET_ERROR) {
  19. std::cout << "Error: accept failed !" << std::endl;
  20. WSACleanup();
  21. break;
  22. }

6. connet:发送连接请求

  1. 1、函数原型:
  2. int WSAAPI connect (
  3. SOCKET s,
  4. const struct sockaddr FAR* name,
  5. int namelen //socket address结构的字节数
  6. );
  7. @SOCKET s:发出连接请求的套接字的描述符
  8. @const struct sockaddr FAR* name:对等方的套接字的地址、就是你创建客户端的套接字时绑定ip和
  9. 端口号的套接字地址
  10. @namelen: 套接字类型大小
  11. 2、返回值
  12. 如果没有错误发生,connect()返回0
  13. 否则返回值SOCKET_ERROR,错误码可通过调用WSAGetLastError()函数得到。
  14. 3、代码范例:
  15. //填充服务端信息
  16. //SOCKADDR_IN server_addr;
  17. //server_addr.sin_family = AF_INET;
  18. //server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
  19. //server_addr.sin_port = htons(8224);
  20. //创建套接字
  21. //SOCKET client= socket(AF_INET, SOCK_STREAM, 0);
  22. if (connect(client, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
  23. std::cout << "Error: connect server failed !" << std::endl;
  24. WSACleanup();
  25. return -1;
  26. }

 7.send:发送数据

  1. 1、函数原型:
  2. int WSAAPI send(
  3. SOCKET s,
  4. const char FAR * buf,
  5. int len,
  6. int flags
  7. );
  8. @SOCKET s: 已连接的套接字描述符
  9. @ const char FAR * buf:指向存有发送数据的缓冲区的指针
  10. @int len:缓冲区buf中数据长度
  11. @ int flags:一般为0,为阻塞发送 即发送不成功会一直阻塞,直到被某个信号终端终止,或者直到发
  12. 送成功为止。
  13. 指定MSG_NOSIGNAL,表示当连接被关闭时不会产生SIGPIPE信号
  14. 指定MSG_DONTWAIT 表示非阻塞发
  15. 2、返回值
  16. 如果没有错误发生,send()返回总共发送的字节数(注意,这可能比len指示的长度小)。
  17. 否则它返回SOCKET_ERROR,错误码可通过调用WSAGetLastError()函数得到。
  18. 2、代码范例:
  19. char temp[1024] = { 0 };
  20. snprintf(temp, sizeof(temp), "%s", detectInfo);
  21. int sendLen = send(socket, (char*)temp, sizeof(temp), 0);
  22. if (sendLen < 0) {
  23. std::cout << "Error: send info to server failed !" << std::endl;
  24. return -1;
  25. }

8.recv:接收数据函数 

  1. 1、函数原型:
  2. int PASCAL FAR recv (
  3. SOCKET s,
  4. writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
  5. int len,
  6. int flags);
  7. @SOCKET s:已连接的套接字描述符。
  8. @char FAR * buf:指向接收输入数据缓冲区的指针
  9. @int len:buf参数所指缓冲区的长度
  10. @int flags:一般为0
  11. 2、返回值
  12. 如果没有错误发生,recv()返回收到的字节数。
  13. 如果连接被关闭,返回0
  14. 否则它返回SOCKET_ERROR,错误码可通过调用WSAGetLastError()函数得到。
  15. 3、代码示例:
  16. char recv_buf[8192] = { 0 };
  17. int recv_len = recv(socket, recv_buf, sizeof(recv_buf), 0);
  18. if (recv_len < 0) {
  19. std::cout << "Error: receive info from server failed !" << std::endl;
  20. return -1;
  21. }

9.closesocket, WSACleanup:释放socket

  1. 使用完后续如果不再使用 记住一定要释放相关资源 否则你懂的。
  2. //关闭套接字 参数:需要关闭的套接字描述符
  3. closesocket(s_server);
  4. //释放DLL资源
  5. WSACleanup();

三、代码块的简单实现(可直接使用)

        (1) 服务器

  1. // ServerTest.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
  2. //
  3. #include <iostream>
  4. #include<winsock.h>
  5. #pragma comment(lib,"ws2_32.lib")
  6. int main()
  7. {
  8. std::cout << "Hello World!\n";
  9. WSADATA wsdata;
  10. std::cout << "start up now ...." << std::endl;
  11. if (WSAStartup(MAKEWORD(2, 2), &wsdata))
  12. {
  13. std::cout << "init socket failed!" << std::endl;
  14. WSACleanup();
  15. return FALSE;
  16. }
  17. std::cout << "before create socket ...." << std::endl;
  18. SOCKET s_server, s_accept;
  19. s_server = socket(PF_INET, SOCK_STREAM, 0);
  20. if (s_server == INVALID_SOCKET)
  21. {
  22. std::cout << "create socket fail" << std::endl;
  23. WSACleanup();
  24. return FALSE;
  25. }
  26. std::cout << "create socket success...." << std::endl;
  27. //填充服务端信息
  28. SOCKADDR_IN server_addr;
  29. server_addr.sin_family = AF_INET;
  30. server_addr.sin_port = htons(8226);
  31. //当然这里也可以将 127.0.0.1改成你想要的ip或者 INADDR_ANY 为向所有ip发送信息
  32. server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
  33. //数据绑定服务器 s_server为服务端套接字
  34. std::cout << "before bind socket ...." << std::endl;
  35. if (bind(s_server, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR)
  36. {
  37. std::cout << "Binding Socket fail.......!" << std::endl;
  38. WSACleanup();
  39. return FALSE;
  40. }
  41. std::cout << "bind socket success...." << std::endl;
  42. std::cout << "before listen socket ...." << std::endl;
  43. if (listen(s_server, 1) == SOCKET_ERROR)
  44. {
  45. std::cout << "Listening Socket fail........!" << std::endl;
  46. WSACleanup();
  47. return FALSE;
  48. }
  49. std::cout << "listen socket success...." << std::endl;
  50. sockaddr_in accept_addr; //用来记录请求连接的套接字信息
  51. int len = sizeof(SOCKADDR);
  52. char recv_buf[8192] = { 0 };
  53. char send_buf[1024] = { 0 };
  54. while (true)
  55. {
  56. //接受连接请求
  57. std::cout << "wait accept...." << std::endl;
  58. s_accept = accept(s_server, (SOCKADDR*)&accept_addr, &len);
  59. if (s_accept == SOCKET_ERROR) {
  60. std::cout << "Error: accept failed !" << std::endl;
  61. WSACleanup();
  62. break;
  63. }
  64. std::cout << "建立连接成功...." << std::endl;
  65. while (true)
  66. {
  67. std::cout << "等待客户端数据中...." << std::endl;
  68. int recv_len = recv(s_accept, recv_buf, sizeof(recv_buf), 0);
  69. if (recv_len < 0) {
  70. std::cout << "Error: receive info from server failed !" << std::endl;
  71. return -1;
  72. }
  73. std::cout << "已收到数据,客户端的数据是:" << recv_buf << std::endl;
  74. std::cout << "给客户端返回数据中..." << std::endl;
  75. snprintf(send_buf, sizeof(send_buf), "%s", "我给你返回信息了");
  76. int sendLen = send(s_accept, (char*)send_buf, sizeof(send_buf), 0);
  77. if (sendLen < 0) {
  78. std::cout << "Error: send info to server failed !" << std::endl;
  79. return -1;
  80. }
  81. std::cout << "给客户端返回数据完毕" << std::endl;
  82. }
  83. }
  84. }

(2)客户端

  1. // ClientTest.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
  2. //
  3. #include <iostream>
  4. #include<winsock.h>
  5. #pragma comment(lib,"ws2_32.lib")
  6. int main()
  7. {
  8. std::cout << "Hello World!\n";
  9. WSADATA wsdata;
  10. if (WSAStartup(MAKEWORD(2, 2), &wsdata))
  11. {
  12. std::cout << "init socket failed!" << std::endl;
  13. WSACleanup();
  14. return FALSE;
  15. }
  16. //检测版本号
  17. if (LOBYTE(wsdata.wVersion) != 2 || HIBYTE(wsdata.wHighVersion) != 2) {
  18. std::cout << "Error: wsadata.wVersion != 2 ." << std::endl;
  19. WSACleanup();
  20. return -1;
  21. }
  22. SOCKET client;
  23. client = socket(PF_INET, SOCK_STREAM, 0);
  24. if (client == INVALID_SOCKET)
  25. {
  26. std::cout << "create socket fail" << std::endl;
  27. WSACleanup();
  28. return FALSE;
  29. }
  30. //填充服务端信息
  31. SOCKADDR_IN server_addr;
  32. server_addr.sin_family = AF_INET;
  33. server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
  34. server_addr.sin_port = htons(8226);
  35. //发送连接请求 请求连接服务器
  36. if (connect(client, (SOCKADDR*)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
  37. std::cout << "Error: connect server failed !" << std::endl;
  38. WSACleanup();
  39. return -1;
  40. }
  41. std::cout << "成功连接到服务器" << std::endl;
  42. //发送数据
  43. char temp[1024] = { 0 };
  44. std::cout << "开始发送数据....." << std::endl;
  45. snprintf(temp, sizeof(temp), "%s", "我是客户端 我发送信息给你了服务端");
  46. int sendLen = send(client, (char*)temp, sizeof(temp), 0);
  47. if (sendLen < 0) {
  48. std::cout << "Error: send info to server failed !" << std::endl;
  49. return -1;
  50. }
  51. std::cout << "发送数据成功、等待服务器响应....." << std::endl;
  52. char recv_buf[8192] = { 0 };
  53. int recv_len = recv(client, recv_buf, sizeof(recv_buf), 0);
  54. if (recv_len < 0) {
  55. std::cout << "Error: receive info from server failed !" << std::endl;
  56. return -1;
  57. }
  58. std::cout << "收到了服务器返回的信息 内容是:" << recv_buf << std::endl;
  59. system("pause");
  60. }

效果展示:

完结撒花

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

以上只是个人学习的一些基础、有哪里不对的话 欢迎纠正哈

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/252356
推荐阅读
相关标签
  

闽ICP备14008679号