【网络编程】网络编程套接字(二)简单的UDP网络程序

news/2024/5/18 12:07:48 标签: 网络, udp, 网络协议

文章目录

  • 服务器编程
    • 1.创建服务端套接字
    • 2.绑定服务端套接字
    • 3.服务端启动
  • 客户端编程
    • 1.创建客户端套接字
    • 2.绑定客户端套接字
  • 服务器和客户端测试

服务器编程

1.创建服务端套接字

使用socket函数调用可以创建套接字的文件描述符,与前边的文件类似,socket函数的返回值是文件描述符,其实套接字是被进程创建的,例如在服务器端的某一个进程需要与客户端的一个进程进行网络通信就要创建套接字,进程的管理是通过进程控制块PCB来实现的,创建套接字就是创建了一个struct_files结构体,而在PCB:task_struct中有一个struct files_struct* files的指针,指针指向一个表,这个表中有一部分是进程的文件描述符表:fd_array,在该表的某一位置写入该结构体的指针,就是创建了套接字。
在这里插入图片描述
当我们创建一个套接字时,其实就是进程打开了一个网络文件,而打开文件就会创建文件结构体,并将地址填入PCB的文件描述符表中,所以socket函数的返回值就是文件描述符,只打开一个文件,文件描述符就是3,前边分别打开了标准输入,标准输出,标准错误,并且这些文件结构体是通过双向链表的结构保存的。

在这里插入图片描述
每一个文件结构体struct_file中都包含该文件的属性,该文件的缓冲区,该文件的操作方法,例如如何打开和关闭文件,因为在系统中,我们可以把一切硬件看作文件,只是给予不同的操作方法就可以操作文件,而对于普通文件来说,缓冲区被刷新后对应的地方就是硬盘,而对于网络编程套接字来说,缓冲区刷新后,把数据发送到网卡。

socket函数

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

在这里插入图片描述

参数
domain:套接字的域,也就是套接字的类型,例如常用的两个域:
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
type:套接字创建时的服务类型,例如:
SOCK_STREAM 表示流协议
SOCK_DGRAM 表示数据报协议
protocol:通常传入0,可以根据前边的参数,推出使用UDP协议还是TCP协议

返回值:返回值就是创建套接字成功后返回的文件描述符,错误返回-1.

2.绑定服务端套接字

在创建好套接字之后,本质上就是在系统层面打开了一个文件结构体,但是此时系统并不清楚打开的文件是网络文件还是普通文件,所以就要将套接字与IP和端口号进行绑定,在绑定套接字时,必须先传入一个sockaddr_in类型的结构体,并且必须使用IP,端口号,协议家族进行填充,此时就可以将套接字与网络进行绑定。

bind函数

// 绑定端口号 (TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address,
 socklen_t address_len);

参数
socket:前边创建套接字时生成的对应文件描述符
address:sockaddr_in或者sockaddr_un的地址,选择对应网络通信还是域间通信
len:传入sockaddr结构体的长度

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

但是还有一些要注意的地方

  • bind函数的第二个参数是struct sockaddr* ,所以在传参时,必须进行强转。
  • 在对sockaddr_in结构体进行填充时,端口号从主机发送到网络,必须使用htons或者htonl函数将端口号转为大端模式。
  • 在填充时,IP地址为了让用户观察,所以设置为点分十进制,但是发送到网络之后,确是一个四字节的整数,所以必须要进行转换,使用inet_addr接口可以将点分十进制转换为四字节整数,而使用inet_ntoa可以将四字节整数转换为点分十进制,并且这两个接口也有前边处理大小端问题的作用。
    在这里插入图片描述
    sockaddr_in的结构
    在这里插入图片描述
    在这里插入图片描述
    在sockaddr_in结构体中主要有IP地址,端口号,已经协议家族,在绑定时,套接字就是根据sockaddr_in 中填充的这些数据来绑定。

server.hpp

class udp_server
{
public:
    udp_server(const uint16_t& port, const std::string& ip = "0.0.0.0")
        : _sockfd(-1), _ip(ip), _port(port)
    {}
    void server_init()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
         std::cout << "socket success: " << _sockfd << std::endl;
        // 将套接字与网络绑定
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_addr.s_addr = inet_addr(_ip.c_str());
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        if (bind(_sockfd, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
    }
 private:
    int _sockfd;
    std::string _ip;
    uint16_t _port;
};

server.cc

 #include<iostream>
#include"udp_server.hpp"
#include<memory>
#include <cstdlib>

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(1);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<udp_server> svr(new udp_server(port));
    svr->server_init();
    svr->start();
    return 0;
}

3.服务端启动

由于服务器必须不间断的提供服务,所以必须不断循环等待客户端发送消息,首先要对sockaddr_in结构体进行填充,通过recvfrom接口接收数据时,同时也可以接收到客户端的IP地址和端口号,因为处理完毕要发送的时候就可以知道要给哪一个IP的哪个进程。

recvfrom接口
在这里插入图片描述

参数
sockfd:创建套接字生成的文件描述符
buf:要接收内容的缓冲区
len:接收内容的字节数
flags:读取方式,一般设置为0,阻塞读取
src_addr:对端网络的相关属性,例如协议家族,IP地址,端口号
addrlen:期望读取对端网络结构体的长度,这是一个输入输出型参数

返回值
读取成功返回0,失败返回-1

sendto接口
在这里插入图片描述

参数
sockfd:套接字的文件描述符
buf:要发送的数据
len:期望发送数据的字节数
flags:写入的方式,通常为0,表示阻塞写入
dest_addr:目标网络的属性,包含通信协议,IP地址,端口地址
addrlen:传入sockaddr结构体的大小\

返回值
成功发送返回0,发送错误返回-1

    void start()
    {
        char buffer[SIZE];
        for (;;)
        {
            struct sockaddr_in peer;
            socklen_t size = sizeof(peer);  
             bzero(&peer, sizeof(peer));
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, &size);
            if (s > 0)
            {
                buffer[s] = 0;
                uint16_t client_port = ntohs(peer.sin_port);
                std::string client_ip = inet_ntoa(peer.sin_addr);

                printf("[%s:%d]# %s\n", client_ip.c_str(), client_port, buffer);
            }

            sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, size);
        }
    }

客户端编程

1.创建客户端套接字

客户端套接字的创建与服务端的创建类似,只是在创建套接字之后,不需要进行绑定端口号和IP地址,因为服务器是给为了给别人提供服务,所以必须让别人知道IP地址和端口号,而且不能轻易更改,但是客户端的端口号只要是唯一的就可以了,不需要进行绑定,如何客户端只是绑定唯一的一个端口号,那么当一个进程没有启动,那么这个端口也不能给别人使用,如果这个歌这个端口被别人使用了,此时这个进程也就不能启动了,所以不进行绑定,而是在进程启动时分配端口号,可以提高利用率。

client.cc

#include<iostream>
#include"udp_client.hpp"

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }
	std::string server_ip = argv[1];
	int server_port = atoi(argv[2]);
	udp_client* clt = new udp_client(server_port,server_ip);
	clt->client_init();
	clt->start();
	return 0;
}

client.hpp

#pragma once
#include <iostream>
#include <string>
#include <string.h>
#include <cstdlib>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>
#include "log.hpp"

#define SIZE 1024
class udp_client
{
public:
    udp_client(uint16_t port, std::string ip)
        : _sockfd(-1), _port(port), _ip(ip)
    {
    }
    bool client_init()
    {
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        return true;
    }
    ~udp_client()
    {
        if (_sockfd > 0)
            close(_sockfd);
    }

private:
    int _sockfd;
    uint16_t _port;
    std::string _ip;
};

2.绑定客户端套接字

首先要对sockaddr_in结构体进行填充,填充时要注意前边的几点,但是在将字符串风格的IP地址转换为整形风格的IP时,首先要将string类型转为C语言风格的字符串。

    void start()
    {
        struct sockaddr_in peer;
        memset(&peer, '\0', sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(_port);
        peer.sin_addr.s_addr = inet_addr(_ip.c_str());
        std::string msg;
        for (;;)
        {
            char buffer[SIZE];
            std::cout << "please input#";
            std::getline(std::cin, msg);
            
            sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr *)&peer, sizeof(peer));

            struct sockaddr_in temp;
            socklen_t size = sizeof(temp);
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr *)&temp, &size);
            if (s > 0)
            {
                buffer[s] = 0;
                std::cout << "server echo# " << buffer << std::endl;
            }
        }
    }

服务器和客户端测试

直接运行,说明书提示,服务器必须提供端口号,客户端必须提供服务器的端口号和IP
在这里插入图片描述
在这里插入图片描述
当我们正确的写上命令行参数时,就可以进行通信。我们可以先进行本地测试,此时服务器没有绑定外网,绑定的是本地环回。现在我们运行服务器时指明端口号为8081,再运行客户端,此时客户端要访问的服务器的IP地址就是本地环回127.0.0.1,服务端的端口号就是8081。
在这里插入图片描述
同时,我们可以使用netstat指令来获得网络信息。
在这里插入图片描述
在进行本地测试之后,也可以将自己的公网IP和端口号告诉朋友,可以让朋友试一下是否可以进行网络通信:
在这里插入图片描述


http://www.niftyadmin.cn/n/1245754.html

相关文章

jvm内存配置参数_Java 从小白到大牛,JVM 不得不知的一些参数和配置

神秘的 JVM 吗有的同学虽然写了一段时间 Java 了&#xff0c;但是对于 JVM 却不太关注。有的同学说&#xff0c;参数都是团队规定好的&#xff0c;部署的时候也不用我动手&#xff0c;关注它有什么用&#xff0c;而且&#xff0c;JVM 这东西&#xff0c;听上去就感觉很神秘很高…

kaggle账号_kaggle | 入门教程

为萌新提供一份最直接的图文教程&#xff0c;介绍kaggle的一些基本操作&#xff0c;让没有任何经验的人也可以快速上手。1Join a competition首先注册kaggle账号&#xff0c;登录后点Compete之后可以看到Competitions如下。随便点一个进入&#xff0c;可以看到比赛的详细信息&a…

Python3.7 爬虫介绍---urllib 实现下载网页的三种方式

from: http://www.runoob.com/w3cnote/python-spider-intro.html 一、什么是爬虫 爬虫&#xff1a;一段自动抓取互联网信息的程序&#xff0c;从互联网上抓取对于我们有价值的信息。 二、Python爬虫架构 Python 爬虫架构主要由五个部分组成&#xff0c;分别是调度器、URL管…

python中的单下划线和双下划线_python变量——单下划线和双下划线的区别

一、变量的定义在Python中&#xff0c;有以下几种方式来定义变量&#xff1a;xx&#xff1a;公有变量_xx&#xff1a;前置单下划线&#xff0c;私有化属性或方法&#xff0c;一般来讲&#xff0c;变量名_xx被看作是“私有 的”&#xff0c;在模块或类外不可以使用。当变量是私有…

Python3爬虫 抓取网页的html 保存

1. Python3爬虫 保存抓取网页的html REF: https://blog.csdn.net/u014453898/article/details/73459938 2017年06月19日 12:15:05 ZJE_ANDY 阅读数&#xff1a;1602 版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/u014…

c++求阴影部分面积_求司机内心阴影面积

01瞧宝宝这一头飘逸的长发&#xff01;02耗子吃的比猫都肥&#xff01;(要你这废猫有何用)03怪不得前面留了一个洞&#xff0c;这司机真有才!04开车发生争执&#xff0c;正确的解决方法可以借鉴一下...这天&#xff0c;皮特进机场候机厅&#xff0c;坐在3号门的座位上等待登机。…

Python beautiful soup解析html获得数据

1. 用beautiful soup 解析网页的HTML的信息 https://blog.csdn.net/i_chaoren/article/details/63282877 1.1 BeautifulSoup的安装及介绍 官方给出的几点介绍&#xff1a; Beautiful Soup提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具…

叙述使用python操作mysql数据库的步骤_Python操作MySQL数据库的三种方法总结

1. MySQLdb 的使用(1) 什么是MySQLdb&#xff1f;MySQLdb 是用于 Python 连接 MySQL 数据库的接口&#xff0c;它实现了 Python 数据库 API 规范 V2.0&#xff0c;基于 MySQL C API 上建立的。$ tar zxvf MySQL-python-*.tar.gz$ cd MySQL-python-*$ python setup.py build$ py…