UDP服务器—实现数据通信

目录

前言

1.接口介绍

2.编写服务器

3.编写客户端

4.测试

总结


前言

在这篇文章中为大家介绍如何通过编码实现数据通信,实现思路是根据前面介绍的网络编程函数编写一个服务端和客户端,实现客户端和服务端双方通信

1.接口介绍

创建套接字

cpp 复制代码
 #include <sys/types.h>         
 #include <sys/socket.h>

 int socket(int domain, int type, int protocol);

domain:网络通信采用AF_INET

type:提供的服务类型,包含TCP流式服务和UDP数据包服务

实现UDP服务器参数设置为SOCK_DGRAM

protocol:采用的协议,一般设置为0,前面的两个参数决定了第三个参数

创建套接字的本质是告诉操作系统要进行网络通信,然后由操作系统在底层维护一个文件缓冲区,创建成功返回该文件描述符,后续数据通信,上层选择将数据放到该文件缓冲区中或者从文件缓冲区中去读数据

返回值:打开的文件描述符

绑定端口号

cpp 复制代码
  #include <sys/types.h>        
  #include <sys/socket.h>

  int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

sockfd : 创建套接字的返回值

struct sockaddr* addr:使用时填充该结构体的字段

a.协议字段

b.端口号

端口号在进行填充的时候需要先进行转换为网络字节序

cpp 复制代码
#include <arpa/inet.h>

uint16_t htons(uint16_t hostshort);

获取对方传输过来的端口号时需要将网络字节序转化为本机

cpp 复制代码
#include <arpa/inet.h>

uint16_t ntohs(uint16_t netshort);

c.IP地址

一般在服务器的实现中IP地址是不需要绑定的

在客户端中发送数据需要填充服务端的IP地址,因为在用户层IP地址是字符串类型,在网络上传输时IP地址是整型数据,所以需要进行转换

cpp 复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);

需要获取对方网络中传输过来的IP地址需要将整型转换为字符串类型

cpp 复制代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char *inet_ntoa(struct in_addr in);

注:因为是网络通信,所以在填充好字段之后需要在传参的时候强转为sockaddr_in

addrlen:结构体的大小

接受数据

cpp 复制代码
#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);

sockfd:文件描述符

buf:用户缓冲区

len:用户缓冲区的大小

flags:设置为0表示阻塞式调用

src_addr:输出型参数,用来获取对方的IP地址和port

addrlen:结构体的大小

发送数据

cpp 复制代码
 #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);

sockfd:文件描述符

buf:用户缓冲区

len:用户缓冲区的大小

flags:设置为0表示阻塞式调用

dest_addr:填充需要发送对端主机的信息,包含IP地址和port端口号

addrlen:结构体的大小

有了上面这些的这些接口,下面我们就可以正式编写一个客户端和一个服务端了

2.编写服务器

形成makefile

Erlang 复制代码
.PHONY:all
all:udpServer udpClient

udpServer:udpServer.cc
	g++ -o $@ $^ -std=c++11
udpClient:udpClient.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf udpServer udpClient

编写服务器:udpServer.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

namespace Server
{
    using namespace std;
    const static string defaultIP = "0.0.0.0";
    enum {USAGE_ERR = 1, SOCKET_ERR, BIND_ERR};
    class udpServer
    {
    public:
        udpServer(uint16_t port, const string &ip = defaultIP) 
        :_port(port),_ip(ip),_sockfd(-1)
        {}
        void initServer()
        {
            //1.创建socket
            _sockfd = socket(AF_INET,SOCK_DGRAM,0);
            if(_sockfd == -1)
            {
                cerr<<"socket error:" << errno << strerror(errno) << endl;
                exit(SOCKET_ERR);
            }
            //2.绑定port和ip
            struct sockaddr_in local;
            bzero(&local,sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = htonl(INADDR_ANY);
            int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
            if(n == -1)
            {
                cerr<<"bind error:" << errno << strerror(errno) << endl;
                exit(BIND_ERR);
            }
        }
        void startServer()
        {
            char buffer[1024];
            for(;;)
            {
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                //peer len:输入,输出型参数:用来保存客户端的ip和port
                ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
                //打印发送来的数据
                if(s)
                {
                    buffer[s] = { 0 };
                    //inet_ntoa:网络字节序->int  int->点分十进制
                    string clientIp = inet_ntoa(peer.sin_addr);
                    //ntohl:网络字节序->int
                    uint16_t clientPort = ntohs(peer.sin_port);
                    string message = buffer;
                    cout << clientIp << "[" << clientPort << "]" << message << endl;
                }
            }
        }
        ~udpServer()
        {}
    private:
        uint16_t _port;
        string _ip;
        int _sockfd;
    };
}

启动服务器:udpServer.cc

cpp 复制代码
#include "udpServer.hpp"
#include <memory>
using namespace Server;
static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    unique_ptr<udpServer> usvr(new udpServer(port));
    usvr->initServer();
    usvr->startServer();
    return 0;
}

3.编写客户端

编写客户端:udpClient.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

namespace Client
{
    using namespace std;   
    class udpClient
    {
    public:
        udpClient(const string& serverIp,const uint16_t serverPort)
        :_serverIp(serverIp),_serverPort(serverPort),_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);
            }
            //client一定需要bind,服务器只有一个,但是客户端有许多个,所以服务端需要显示的绑定端口号
            //客户端一般不需要自己显示的绑定,而是由os自动形成端口号进行绑定
        }
        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(1)
            {
                cout << "请输入:";
                cin >> message;
                //发送数据到客户端
                sendto(_sockfd,message.c_str(),message.size(),0,(const struct sockaddr*)&server,
                sizeof(server));
            }
        }
    private:
        string _serverIp;
        int _sockfd;
        uint16_t _serverPort;
    };
}

启动客户端:udpClient.cc

cpp 复制代码
#include"udpClient.hpp"
#include<memory>
using namespace Client;
static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " server_ip server_port\n\n";
}
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> uct(new udpClient(serverip,serverport));
    uct->initClient();
    uct->run();
    return 0;
}

4.测试

说明:因为本次是在一台主机上,所以为了做测试将IP地址绑定为"127.0.0.1",该IP地址的特点是专门用来在本主机上做测试,称为本地环回,后续在不同主机上通信的时候只需要将IP地址改为对方主机的IP地址即可。

此时我们就可以看到当客户端向服务器发送数据时,服务器收到了数据,并且将数据回显出来

总结

通过上面的编码,使用UDP协议简单实现了一个服务器,可以用来进行数据通信了,是不是感觉很神奇呀,看到这里你就会发现,原来网络通信也并不复杂,和系统内部进程间通信有异曲同工之妙关于网络通信的更多细节,后面再一一为大家介绍,我们下次再见!

相关推荐
照书抄代码5 分钟前
Linux中C++ gdb调试命令
linux·运维·服务器
the_nov30 分钟前
2.Linux的权限理解
linux·运维·服务器
5:001 小时前
Linux:(五种IO模型)
linux·运维·服务器
鲸屿1951 小时前
Shell基础
linux·运维·服务器
用手码出世界4 小时前
【Linux】进程间通信、匿名管道、进程池
linux·运维·服务器
垂金烟柳4 小时前
CentOS 7上配置SQL Server链接其他SQL Server服务器
服务器·数据库·sqlserver
HHONGQI1234 小时前
Linux 基础入门操作 前言 linux操作指令介绍
linux·运维·服务器
电星托马斯4 小时前
Linux如何设置bash为默认shell
linux·运维·服务器·笔记·程序人生·bash·个人开发
那些乐趣4 小时前
已经使用中的clickhouse更改数据目录
java·服务器·clickhouse
LUCIAZZZ5 小时前
计算机网络-TCP的重传机制
java·网络·网络协议·tcp/ip·计算机网络·操作系统·springboot