当前位置:   article > 正文

用UDP套接字实现客户端和服务端通信_存在数据边界的udp套接字

存在数据边界的udp套接字

IP地址和port端口号

IP地址

数据有IP(公网)标识一台唯一的主机。

port端口号

为了更好的标识一台主机上服务进程的唯一性,我们采用端口号port,标识服务器进程,客户端进程的唯一性!

ip+端口号

IP地址(主机全网唯一性) + 该主机上的端口号,标识该服务器上进程的唯一性
ipA + portA, ipB + portB

网络通信的本质

概述

相当于一个服务端进程 与一个客户端进程 通过网络资源来进行通信

本质

1. 需要让不同的进程,先看到同一份资源 -- 网络

2. 通信不就是在做IO吗? -- 所以,我们所有的上网行为,无外乎两种: 
   a. 我要把我的数据发出去。
   b. 我要收到别人给我发的数据。

为什么不用PID做端口号?

a. 系统是系统,网络是网络,单独设置 -- 系统与网络解耦
b. 需要客户端每次都能找到服务器进程 -- 服务器的唯一性不能做任何改变 -- IP+port 不能随便改变 <-- 不能使用轻易会改变的值(pid每次进程启动会改变)
c. 不是所有的进程都要提供网络服务请求,但是所有的进程都需要pid

认识TCP协议

1. 传输层协议
2. 有连接
3. 可靠传输
4. 面向字节流

认识UDP协议

1. 传输层协议
2. 无连接
3. 不可靠传输
4. 面向数据报

TCP与UDP的可靠与不可靠

概述

可靠与不可靠是中性词。

可靠是有成本的。往往是比较复杂的,维护成本和编码成本比较高。

不可靠比较简单,维护成本和编码成本比较低。

应对应不同的场景

TCP:银行系统、搜索引擎等;
UDP:推送广告等;

网络字节序中的大小端问题

概念

由于在同一台主机上的数据一般不是大端就是小端,我们不需要特别的注意。但是当两台主机通过网络发送和接受数据的时候,这个问题就要考虑起来,因此我们规定网络中的数据都是大端!当前发送主机是小端,就需要先将数据转成大端,否则就忽略,直接发送即可。

大小端转换的接口

主机序列转网络序列:

  1. uint32_t htonl(unit32_t hostlong);
  2. uint16_t htons(uint16_t hostshort);

网络序列转主机序列:

  1. uint32_t ntohl(uint32_t netlong);
  2. uint16_t ntohs(uint16_t netshort);

认识socket套接字

概念

ip+port就是套接字

分类

1.  网络套接字
2.  原始套接字
3.  unix域间套接字 (只能本地通信)

本地通信和网络通信的判断

通过 struct sockaddr{} 
这一套结构体可以分别表示网络通信和本地通信。

根据第一个参数传入的是AF_INET还是AF_UNIX来内部判断

UDP的通信

socket接口

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. int socket(int domain,int type,int protocol);

 参数

domain

AF_UNIX: 域间通信(本地通信)
AF_INET: 网络通信 (也可以是PF_INET)


type:套接字提供能力类型

SOCK_DGRAM: 数据报套接字 -- (对应的UDP)
SOCK_STREAM: 流式套接字 -- (对应的TCP)

protocol:

未来我们想使用什么协议(指明是TCP协议还是UDP协议)
可以缺省为0 (前两个参数确定之后就可以确定是使用TCP还是UDP了不需要再传入第三个参数了)

返回值

文件描述符 

bind接口

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

意义

 让OS知道我们的IP地址和端口号

参数

 sockfd:

创建套接字的返回值

const struct sockaddr *addr:

含有 IP地址和端口号属性的结构体,我们通过该结构体将这两个属性传入进去

addrlen:

传入结构体的长度

返回值

绑定成功返回0
绑定失败返回-1

结构体 sockaddr_in

port

uint16_t sin_port;

ip

struct in_addr sin_addr;

IP地址的表达形式

“124.223.97.182” -- > 点分十进制风格的IP,字符串,可读性好

uint32_t ip:整数风格的IP - 网络通信使用

uint32_t ip=12345;

struct _ip
{
   unsigned char p1;
   unsigned char p2;
   unsigned char p3;
   unsigned char p4;
};

str_ip=
to_string(((struct _ip)&ip)->p1)+"."
to_string(((struct _ip)&ip)->p2)+"."
to_string(((struct _ip)&ip)->p3)+"."
to_string(((struct _ip)&ip)->p4);


系统接口可以直接对点分十进制和uint32_t类型的IP地址进行互相转换。

bzero接口

  1. #include<cstrings>
  2. void bzero(void *s, size_t n);

作用:初始化结构体类型的对象。


inet_addr接口 (用于ip地址)

  1. #include <sys/socket.h>
  2. #include <netinet/in.h>
  3. #include <arpa/inet.h>
  4. in_addr_t inet_addr(const char *cp);

内部完成两件事情

1. string -> uint32_t
2. htonl();

服务器ip的设置

一般情况下我们的服务器不指明一个特定的IP,一般会设置成INADDR_ANY。

任意地址bind,这样的话就不会错过其他不同的IP但是端口号与我们的服务器匹配的网络。 

  1. struct sockaddr_in local; // 定义一个sockaddr_in的结构体
  2. local.sin_addr.s_addr = INADDR_ANY; // 不指名特定的一个IP

 recvfrom接口

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. ssize_t recvfrom(int sockfd,void *buf,size_t len,int flags,struct sockaddr *src_addr, socklen_t *addrlen);

参数

flags

设置为0  为阻塞式读取

struct sockaddr *src_addr 

所传的结构体

socklen_t *addrlen

所传的结构体长度

返回值

读取成功返回读取到的字符数。
读取失败返回-1。

inet_ntoa 接口

  1. #include <sys/socket.h>
  2. #include <netinet/in.h>
  3. #include <arpa/inet.h>
  4. char *inet_ntoa(struct in_addr in);

作用

1. 将网络序列转换为主机序列 
2. 并且将int转为点分十进制

为什么客户端不用显示的bind?

因为写服务器的是一家公司,但是写客户端的是无数家公司。如果我们在客户端上绑定了特定的端口号,如果刚好这个端口号被别人占用了,那么就无法与服务器正常通信了。因此服务端是一定要我们手动去bind特定的端口号的,为了服务端端口号的唯一。客户端的端口号我们让OS自动形成就可以了。

sendto接口

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. ssize_t sendto(int sockfd, const void *buf,size_t len,int flags,const struct sockaddr *dest_addr,socklen_t addrlen);

参数

const void *buf

要发送的缓冲区

size_t len

缓冲区长度

UDP通信的代码

简单的通信

客户端发数据,服务端接收数据。

服务端代码

创建套接字

  1. // 1. 套接字的创建
  2. _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  3. if (_sockfd == -1)
  4. {
  5. cerr << "socket error :" << errno << " : " << strerror(errno) << endl;
  6. exit(SOCKET_ERR);
  7. }
  8. cout << "socket success : " << _sockfd << endl;

调用bind

  1. // 2. bind
  2. struct sockaddr_in local;
  3. bzero(&local, sizeof(local)); // 初始化local
  4. local.sin_family = AF_INET;
  5. // 1. 主机转网络 点分十进制转int
  6. local.sin_addr.s_addr = inet_addr(_serverip.c_str());
  7. // local.sin_addr.s_addr = INADDR_ANY;
  8. local.sin_port = htons(_serverport);
  9. int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
  10. if (n == -1)
  11. {
  12. cerr << "bind error: " << errno << " : " << strerror(errno) << endl;
  13. exit(BIND_ERR);
  14. }

调用recvfrom接受客户端的数据

  1. char buffer[NUM];
  2. for (;;)
  3. {
  4. struct sockaddr_in peer;
  5. socklen_t len = sizeof(peer);
  6. //
  7. ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
  8. if (s > 0)
  9. {
  10. // 读取数据
  11. buffer[s] = 0;
  12. uint16_t clientport = ntohs(peer.sin_port);
  13. string clientip = inet_ntoa(peer.sin_addr);
  14. string message = buffer;
  15. cout << clientip << " [" << clientport << "]# " << message << endl;
  16. }
  17. }

客户端代码

创建套接字

  1. // 1. 创建socket
  2. _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  3. if (_sockfd == -1)
  4. {
  5. cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
  6. exit(2);
  7. }
  8. cout << "socket success: " << _sockfd << endl;

调用sendto接口让信息发给服务端

  1. struct sockaddr_in server;
  2. memset(&server, 0, sizeof(server));
  3. server.sin_family = AF_INET;
  4. server.sin_addr.s_addr = inet_addr(_serverip.c_str());
  5. server.sin_port = htons(_serverport);
  6. string message;
  7. while (!_quit)
  8. {
  9. cout << "Please Enter# ";
  10. cin >> message;
  11. sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
  12. }

通信界面

完整代码  

makefile

  1. cc=g++
  2. .PHONY:all
  3. all:udpClient udpServer
  4. udpClient:udpClient.cc
  5. $(cc) -o $@ $^ -lpthread -std=c++11
  6. udpServer:udpServer.cc
  7. $(cc) -o $@ $^ -std=c++11
  8. .PHONY:clean
  9. clean:
  10. rm -f udpClient udpServer

udpClient.hpp

  1. #pragma once
  2. #include <iostream>
  3. #include <string>
  4. #include <cerrno>
  5. #include <cstring>
  6. #include <cstdlib>
  7. #include <strings.h>
  8. #include <netinet/in.h>
  9. #include <unistd.h>
  10. #include <sys/types.h>
  11. #include <sys/socket.h>
  12. #include <arpa/inet.h>
  13. namespace Client
  14. {
  15. using namespace std;
  16. class udpClient
  17. {
  18. public:
  19. udpClient(const string &serverip, const uint16_t &serverport)
  20. : _serverip(serverip), _serverport(serverport), _quit(false), _sockfd(-1)
  21. {
  22. }
  23. void initClient()
  24. {
  25. // 1. 创建socket
  26. _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  27. if (_sockfd == -1)
  28. {
  29. cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
  30. exit(2);
  31. }
  32. cout << "socket success: " << _sockfd << endl;
  33. // 2. client要不要bind[不需要的] , client要不要显示的bind,需不需要程序员自己bind? 不需要
  34. // 写服务器的一家公司,写客户端是无数家公司 -- 因此让OS自动形成端口进行bind! -- OS在什么时候,如何bind
  35. }
  36. void run()
  37. {
  38. struct sockaddr_in server;
  39. memset(&server, 0, sizeof(server));
  40. server.sin_family = AF_INET;
  41. server.sin_addr.s_addr = inet_addr(_serverip.c_str());
  42. server.sin_port = htons(_serverport);
  43. string message;
  44. while (!_quit)
  45. {
  46. cout << "Please Enter# ";
  47. cin >> message;
  48. sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
  49. }
  50. }
  51. ~udpClient()
  52. {
  53. }
  54. private:
  55. int _sockfd;
  56. string _serverip;
  57. uint16_t _serverport;
  58. bool _quit;
  59. };
  60. } // namespace Client

udpClient.cc

  1. #include "udpClient.hpp"
  2. #include <memory>
  3. using namespace Client;
  4. static void Usage(string proc)
  5. {
  6. cout << "\nUsage:\n\t" << proc << " local_ip local_port\n\n";
  7. }
  8. // ./udpClient server_ip server_port
  9. int main(int argc, char *argv[])
  10. {
  11. if (argc != 3)
  12. {
  13. Usage(argv[0]);
  14. exit(1);
  15. }
  16. string serverip = argv[1];
  17. uint16_t serverport = atoi(argv[2]); // 字符串转整数
  18. unique_ptr<udpClient> ucli(new udpClient(serverip, serverport));
  19. ucli->initClient();
  20. ucli->run();
  21. return 0;
  22. }

udpServer.hpp

  1. #include <iostream>
  2. #include <string>
  3. #include <cerrno>
  4. #include <cstring>
  5. #include <cstdlib>
  6. #include <strings.h>
  7. #include <functional>
  8. #include <netinet/in.h>
  9. #include <unistd.h>
  10. #include <sys/types.h>
  11. #include <sys/socket.h>
  12. #include <arpa/inet.h>
  13. using namespace std;
  14. namespace Server
  15. {
  16. enum
  17. {
  18. SOCKET_ERR = 1,
  19. BIND_ERR,
  20. OPEN_ERR
  21. };
  22. const int NUM = 1024;
  23. static const string defaultIp = "0.0.0.0";
  24. class udpServer
  25. {
  26. public:
  27. udpServer(const uint16_t &port, const string ip = defaultIp)
  28. : _serverport(port), _serverip(defaultIp), _sockfd(-1)
  29. {
  30. // cout << "拷贝构造" << endl;
  31. }
  32. void initServer()
  33. {
  34. // 1. 套接字的创建
  35. _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  36. if (_sockfd == -1)
  37. {
  38. cerr << "socket error :" << errno << " : " << strerror(errno) << endl;
  39. exit(SOCKET_ERR);
  40. }
  41. cout << "socket success : " << _sockfd << endl;
  42. // 2. bind
  43. struct sockaddr_in local;
  44. bzero(&local, sizeof(local)); // 初始化local
  45. local.sin_family = AF_INET;
  46. // 1. 主机转网络 点分十进制转int
  47. local.sin_addr.s_addr = inet_addr(_serverip.c_str());
  48. // local.sin_addr.s_addr = INADDR_ANY;
  49. local.sin_port = htons(_serverport);
  50. int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
  51. if (n == -1)
  52. {
  53. cerr << "bind error: " << errno << " : " << strerror(errno) << endl;
  54. exit(BIND_ERR);
  55. }
  56. }
  57. void start()
  58. {
  59. char buffer[NUM];
  60. for (;;)
  61. {
  62. struct sockaddr_in peer;
  63. socklen_t len = sizeof(peer);
  64. //
  65. ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
  66. if (s > 0)
  67. {
  68. // 读取数据
  69. buffer[s] = 0;
  70. uint16_t clientport = ntohs(peer.sin_port);
  71. string clientip = inet_ntoa(peer.sin_addr);
  72. string message = buffer;
  73. cout << clientip << " [" << clientport << "]# " << message << endl;
  74. }
  75. }
  76. }
  77. ~udpServer()
  78. {
  79. }
  80. private:
  81. int _sockfd;
  82. uint16_t _serverport;
  83. string _serverip;
  84. };
  85. } // udpServer

udpServer.cc

  1. #include "udpServer.hpp"
  2. #include <memory>
  3. using namespace Server;
  4. using namespace std;
  5. static void Usage(string proc)
  6. {
  7. cout << "\nUsage:\n\t" << proc << " local_port\n\n";
  8. }
  9. // ./udpClient server_ip server_port
  10. int main(int argc, char *argv[])
  11. {
  12. if (argc != 2)
  13. {
  14. Usage(argv[0]);
  15. exit(1);
  16. }
  17. uint16_t port = atoi(argv[1]); // 字符串转整数
  18. unique_ptr<udpServer> usvr(new udpServer(port));
  19. usvr->initServer();
  20. usvr->start();
  21. return 0;
  22. }

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

闽ICP备14008679号