用UDP套接字实现客户端和服务端通信

news2024/9/27 9:24:17

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:推送广告等;

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

概念

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

大小端转换的接口

主机序列转网络序列:

uint32_t htonl(unit32_t hostlong);
uint16_t htons(uint16_t hostshort);

网络序列转主机序列:

uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

认识socket套接字

概念

ip+port就是套接字

分类

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

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

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

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

UDP的通信

socket接口

#include <sys/types.h>
#include <sys/socket.h>

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接口

#include <sys/types.h>
#include <sys/socket.h>

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接口

#include<cstrings>

void bzero(void *s, size_t n);

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


inet_addr接口 (用于ip地址)

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);

内部完成两件事情

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

服务器ip的设置

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

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

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

 recvfrom接口

#include <sys/types.h>
#include <sys/socket.h>

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 接口

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char *inet_ntoa(struct in_addr in);

作用

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

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

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

sendto接口

#include <sys/types.h>
#include <sys/socket.h>

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. 套接字的创建
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd == -1)
{
    cerr << "socket error :" << errno << " : " << strerror(errno) << endl;
    exit(SOCKET_ERR);
}
cout << "socket success : " << _sockfd << endl;

调用bind

// 2. bind
struct sockaddr_in local;
bzero(&local, sizeof(local)); // 初始化local

local.sin_family = AF_INET;

// 1. 主机转网络 点分十进制转int
local.sin_addr.s_addr = inet_addr(_serverip.c_str());
// local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons(_serverport);
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));

if (n == -1)
{
    cerr << "bind error: " << errno << " : " << strerror(errno) << endl;
    exit(BIND_ERR);
}

调用recvfrom接受客户端的数据

char buffer[NUM];
for (;;)
{
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);
    //
    ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);

    if (s > 0)
    {
        // 读取数据
        buffer[s] = 0;
        uint16_t clientport = ntohs(peer.sin_port);
        string clientip = inet_ntoa(peer.sin_addr);
        string message = buffer;

        cout << clientip << " [" << clientport << "]# " << message << endl;
    }
}

客户端代码

创建套接字


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

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

struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
server.sin_port = htons(_serverport);

string message;
while (!_quit)
{
    cout << "Please Enter# ";
    cin >> message;

    sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
}

通信界面

完整代码  

makefile

cc=g++

.PHONY:all
all:udpClient udpServer

udpClient:udpClient.cc
	$(cc) -o $@ $^ -lpthread -std=c++11
udpServer:udpServer.cc
	$(cc) -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f udpClient udpServer

udpClient.hpp

#pragma once

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>

#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

namespace Client
{
    using namespace std;

    class udpClient
    {
    public:
        udpClient(const string &serverip, const uint16_t &serverport)
            : _serverip(serverip), _serverport(serverport), _quit(false), _sockfd(-1)
        {
        }
        void initClient()
        {
            // 1. 创建socket
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
                exit(2);
            }
            cout << "socket success: " << _sockfd << endl;

            // 2. client要不要bind[不需要的]  , client要不要显示的bind,需不需要程序员自己bind? 不需要
            // 写服务器的一家公司,写客户端是无数家公司 -- 因此让OS自动形成端口进行bind! -- OS在什么时候,如何bind
        }
        void run()
        {
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_serverip.c_str());
            server.sin_port = htons(_serverport);

            string message;
            while (!_quit)
            {
                cout << "Please Enter# ";
                cin >> message;

                sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
            }
        }
        ~udpClient()
        {
        }

    private:
        int _sockfd;
        string _serverip;
        uint16_t _serverport;
        bool _quit;
    };
} // namespace Client

udpClient.cc

#include "udpClient.hpp"
#include <memory>

using namespace Client;

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_ip local_port\n\n";
}

// ./udpClient server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }

    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]); // 字符串转整数

    unique_ptr<udpClient> ucli(new udpClient(serverip, serverport));

    ucli->initClient();
    ucli->run();

    return 0;
}

udpServer.hpp

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <functional>

#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

using namespace std;

namespace Server
{
    enum
    {
        SOCKET_ERR = 1,
        BIND_ERR,
        OPEN_ERR
    };

    const int NUM = 1024;
    static const string defaultIp = "0.0.0.0";

    class udpServer
    {

    public:
        udpServer(const uint16_t &port, const string ip = defaultIp)
            : _serverport(port), _serverip(defaultIp), _sockfd(-1)
        {
            // cout << "拷贝构造" << endl;
        }

        void initServer()
        {
            // 1. 套接字的创建
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sockfd == -1)
            {
                cerr << "socket error :" << errno << " : " << strerror(errno) << endl;
                exit(SOCKET_ERR);
            }
            cout << "socket success : " << _sockfd << endl;

            // 2. bind
            struct sockaddr_in local;
            bzero(&local, sizeof(local)); // 初始化local

            local.sin_family = AF_INET;

            // 1. 主机转网络 点分十进制转int
            local.sin_addr.s_addr = inet_addr(_serverip.c_str());
            // local.sin_addr.s_addr = INADDR_ANY;
            local.sin_port = htons(_serverport);
            int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));

            if (n == -1)
            {
                cerr << "bind error: " << errno << " : " << strerror(errno) << endl;
                exit(BIND_ERR);
            }
        }
        void start()
        {
            char buffer[NUM];
            for (;;)
            {
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                //
                ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);

                if (s > 0)
                {
                    // 读取数据
                    buffer[s] = 0;
                    uint16_t clientport = ntohs(peer.sin_port);
                    string clientip = inet_ntoa(peer.sin_addr);
                    string message = buffer;

                    cout << clientip << " [" << clientport << "]# " << message << endl;
                }
            }
        }

        ~udpServer()
        {
        }

    private:
        int _sockfd;
        uint16_t _serverport;
        string _serverip;
    };
} // udpServer

udpServer.cc

#include "udpServer.hpp"
#include <memory>

using namespace Server;
using namespace std;

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}

// ./udpClient server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    uint16_t port = atoi(argv[1]); // 字符串转整数

    unique_ptr<udpServer> usvr(new udpServer(port));

    usvr->initServer();

    usvr->start();

    return 0;
}








本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/695956.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一年省七位数,得物自建HFDS在 Flink Checkpoint 场景下的应用实践

1 背景 随着Flink实例的迁移下云以及新增需求接入&#xff0c;自建Flink平台规模逐渐壮大&#xff0c;当前总计已超4万核运行在自建的K8S集群中&#xff0c;然而 Flink 任务数的增加&#xff0c;特别是大状态任务&#xff0c;每次Checkpoint 时会产生脉冲式带宽占用&#xff0…

统计项目代码行数工具cloc

Ubuntu用户 使用cloc在ubuntu内统计代码行数 安装cloc工具 sudo apt-get install cloc进入需要统计的目录内&#xff0c;然后执行 cloc .然后就会显示文件目录中的文件数(files)、空白行数(blank)、注释行数(comment)和代码行数(code)。 Windows 用户 也是使用cloc工具 …

leecode-下一排列

题目 题目 分析 妈呀&#xff0c;其实我直接调用函数&#xff0c;一行代码就通过了hhh&#xff0c;不过这种取巧的方式不可取&#xff0c;还是得老老实实的写。 首先需要明白什么叫下一排列&#xff1f; 比如输入&#xff1a; 1 5 8 4 7 6 5 3 1 答案就是&#xff1a; 1 5 …

什么是楼宇卫生间智慧厕所系统

楼宇卫生间智慧厕所系统是专为写字楼、办公楼、商场、集团大厦、工厂等应用场景所设计的解决方案。它利用全自动采集和监控智能化、无线数据传输功能&#xff0c;通过云平台管理软件和手机端应用&#xff0c;实现了公厕的智能化管理和使用者的便利。 随着城市化进程的加速&…

[Json]控制返回数据是否包含某个属性

控制返回数据是否包含某个属性 在我们返回给前端的Json格式的数据时&#xff0c;通常我们会定义一个类&#xff0c;里面定义几个成员变量用来定义返回给前端的具体内容&#xff0c;例如&#xff1a; package cn.tedu.csmall.commons.web;import io.swagger.annotations.ApiMo…

HDLbits--Exams/2013 q2bfsm

try1: module top_module (input clk,input resetn, // active-low synchronous resetinput x,input y,output f,output g ); parameter a0,b1,x12,x23,y14,y25,g16,g07;//b为resetn无效后的状态&#xff0c;在b状态使f保持一个周期 //b收到1后转移到x1&#xff0c;x1收到…

基于双机多线程的程序加速设计

摘要 不断提高程序的运行效率&#xff0c;而又不影响程序功能是程序员的不竭追求。本项目旨在利用并行技术进一步提高程序的效率。 程序设计中&#xff0c;主要实现了百万级数据的求和、求最大值以及排序功能。其中&#xff0c;排序功能使用快速排序算法和归并算法实现。共采用…

lenovo联想笔记本ThinkBook 14 Gen5+ IRH(21HW)原装Win11系统镜像原厂OEM恢复出厂状态

LENOVO联想笔记本电脑&#xff0c;ThinkBook 14 Gen5 IRH(21HW)&#xff0c;原厂Windows11原装OEM系统&#xff0c;恢复出厂时状态系统 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;16G或以上的U盘 文件格式&#x…

使用凌鲨查看mysql数据

MySQL是一种开源的关系型数据库管理系统&#xff0c;它被广泛应用于软件开发领域。它具有高可靠性、高性能、易于使用和可扩展性等优点&#xff0c;被许多大型企业和网站所采用。MySQL支持多种编程语言和操作系统&#xff0c;可以轻松地与其他应用程序集成。 继之前我们在凌鲨…

python如何将图片显示在网页上

from flask import Flask, render_template_string import base64 import cv2import osapp Flask(__name__)# 读取图像app.route(/)def index():# 读取图像文件并将其转换为Base64编码的字符串img_path 1.pngimg_data open(img_path, rb).read()img_base64 base64.b64encod…

vue中纯手写单选复选框样式(隐藏原生样式)

基于vue2项目&#xff0c;代码会全部在下面贴出&#xff0c;大家重点关注相关v-for循环实现及样式实现&#xff0c;先看效果&#xff1a; 先看单选 单选组件<easy-radio>&#xff1a; <template><div><div class"radio-item" v-for"(opt…

TLC能力加QLC价格:Solidigm D5-P5430评测

产品介绍 前段时间在Solidigm D5-P5316的帮助下&#xff0c;计算圆周率100万亿位数的世界纪录被刷新&#xff0c;新纪录的计算效率达到之前的三倍。我们一方面能够感受到SSD对高性能计算的影响&#xff0c;另一方面也看到QLC已经在数据中心中得到广泛采用。今天PCEVA评测的是使…

IVIEW常用问题解决

1 FormItem 里面绑定帮助框 导致字段不检验 <FormItem label"备货通知单" prop"noticeIdStr"><Input style"width: 200px;" :title"noticeIdStr"icon"ios-list-box-outline"on-click"showNotice" v-mod…

基于深度学习的细粒度漏洞检测框架VulDeeLocator

源自&#xff1a;IEEE Transactions on Dependable and Secure Computing 作者&#xff1a;Zhen Li, Deqing Zou, Shouhuai Xu, Zhaoxuan Chen, Yawei Zhu, Hai Jin. 背景与动机 设计与实现 图1 VulDeeLocator框架 图2 sSyVC和iSeVC的生成示例 图3 BRNN-vdl模型 实验结果 表…

【④MySQL函数】:让你的数据库操作更高效(一)

前言 ✨欢迎来到小K的MySQL专栏&#xff0c;本节将为大家带来MySQL字符串函数和数学函数的讲解✨ 目录 前言一、字符串函数二、数学函数三、总结 一、字符串函数 函数作用UPPER(列|字符串)将字符串每个字符转为大写LOWER(列|字符串)将字符串每个字符转为小写CONCAT(str1,str2,…

QSS QCalendarWidget

样式分布图&#xff1a; 知道了每个 widget 后&#xff0c;就可以像下面这样用 QSS 修改 QCalendarWidget 的样式了。 示例1&#xff1a; #qt_calendar_calendarview {background: white; }#qt_calendar_navigationbar {background: rgba(215, 215, 215, 255); }QToolButton …

功率放大器在脉冲技术中的应用

脉冲技术是指在时间上极短且能量很强的信号的处理和应用技术&#xff0c;它在通信、雷达、医学、能量研究等领域有着重要的应用。在这些应用过程中&#xff0c;功率放大器是非常重要的关键设备&#xff0c;因为它可以提供高功率脉冲信号&#xff0c;使得这些领域的应用能够顺利…

react antd动态样式实现

<Row><Col style{{ marginBottom: 30px }} ><a className"labelstyle" style{{ padding: fundType.length < 1 ? 0px : 5px, marginRight: fundType.length < 1 ? 0px : 10px }} >{fundType}</a><a className"labelstyle&q…

Gitlab 双重认证和访问令牌的使用

目录 引言 1、双重认证让项目只能使用访问令牌克隆 2、创建项目访问令牌 3、创建群组访问令牌 引言 双重认证可以提高用户账户的安全性&#xff0c;防止密码泄露&#xff0c;他人随意登录。 访问令牌就相当于项目或群组的访问密码&#xff0c;有了它就可以克隆项目。同时访…

作为一名python开发者,想要兼职接单,需要学那些技术?要达到什么水准?为什么要学这些技术?

作为一名Python开发者&#xff0c;学习并且兼职接单可以创造更多的机会和收入。要成为一名具有竞争力的兼职Python开发者&#xff0c;需要学习一系列的技术&#xff0c;并达到一定的水准。本文将详细分析兼职Python开发者需要学习的技术、所需达到的水平&#xff0c;以及为什么…