网络编程socket(上)(三)

简介: 网络编程socket(上)

3.3 客户端初始化

3.3.1 客户端创建套接字

将客户端封装成一个类,当定义出一个客户端对象后也需要对其进行初始化,而客户端在初始化时也要创建套接字,之后客户端发送数据或接收数据也就是对这个套接字进行操作


客户端创建套接字时选择的协议家族是AF_INET,需要的服务类型是SOCK_DGARM,当客户端被析构时可以选择关闭对应的套接字。与服务端不同的是,客户端在初始化时只需要创建套接即可,而不需进行绑定操作

class UdpClient
{
public:
    UdpClient(string server_ip,uint16_t server_port):_server_ip(server_ip),_server_port(server_port) {}
    bool InitClient();
    ~UdpClient() {}
private:
    int _socket_fd;
    string _server_ip;
    uint16_t _server_port;
};
bool UdpClient::InitClient() 
{
  _socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
  if (_socket_fd < 0){
    std::cerr << "socket create error" << std::endl;
    return false;
  }
  return true;
}
UdpClient::~UdpClient() { if(_socket_fd < 0) close(_socket_fd); }


3.3.2 客户端的绑定问题

由于是网络通信,通信双方都需要找到对方,因此服务端和客户端都需要有各自的IP地址和端口号,只不过服务端需要进行端口号的绑定,而客户端不需要


因为服务器就是为了给别人提供服务的,因此服务器必须要让别人知道自己的IP地址和端口号,IP地址一般对应的就是域名,而端口号一般没有显示指明过,因此服务端的端口号一定要是一个众所周知的端口号,并且选定后不能轻易改变,否则客户端是无法知道服务端的端口号的,这就是服务端要进行绑定的原因,只有绑定之后这个端口号才真正属于自己,因为一个端口只能被一个进程所绑定,服务器绑定一个端口就是为了独占这个端口


客户端在通信时虽然也需要端口号,但客户端一般是不进行绑定的,客户端访问服务端的时候,端口号只要是唯一的就行了,不需要和特定客户端进程强相关


若客户端绑定了某个端口号,那么以后这个端口号就只能给这一个客户端使用,就是这个客户端没有启动,这个端口号也无法分配给别人,并且若这个端口号被别人使用了,那么这个客户端就无法启动了。所以客户端的端口只要保证唯一性就行了。因此客户端端口可以动态的进行设置,客户端的端口号就不需要程序员设置,当调用类似于sendto()这样的接口时,操作系统会自动给当前客户端获取一个唯一的端口号


客户端每次启动时使用的端口号可能是变化的,此时只要端口号没有耗尽,客户端就可以正常启动


3.4 启动运行客户端

当客户端初始化完毕后就可以将客户端运行起来,由于客户端和服务端在功能上是相互补充的,既然服务器是在读取客户端发来的数据,那么客户端就应该向服务端发送数据


sendto函数


发送数据

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地址、端口号等

addrlen:传入dest_addr结构体的长度

返回值:写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置


由于UDP不是面向连接的,因此除了传入待发送的数据以外还需要指明对端网络相关的信息,包括IP地址和端口号等

由于sendto()提供的参数是 struct sockaddr* 类型的,在传参时需将struct sockaddr*类型强转

提供启动客户端接口


客户端要发送数据给服务端,可以让客户端获取用户输入,不断将用户输入的数据发送给服务端


客户端中存储的服务端的端口号此时是主机序列,需要调用htons()函数将其转为网络序列后再设置进struct sockaddr_in结构体。客户端中存储的服务端的IP地址是字符串IP,需要通过调用inet_addr()函数将其转为整数IP(同时转成网络序列)后再设置进struct sockaddr_in结构体

void UdpClient::Start()
{
    string message;
    struct sockaddr_in receive;
    memset(&receive, 0, sizeof(receive));
    receive.sin_port = htons(_server_port);
    receive.sin_family = AF_INET;
    receive.sin_addr.s_addr = inet_addr(_server_ip.c_str());
    while(true)
    {
        cout << "please Enter#";
        getline(cin, message);
        sendto(_socket_fd, message.c_str(), strlen(message.c_str()), 0, (struct sockaddr*)&receive, sizeof(receive));
    }
}

引入命令行参数


引入命令行参数,运行客户端时直接在后面跟上对应服务端的IP地址和端口号即可

int main(int argc, char* argv[])
{
    if(argc < 3) {
        cerr << "Usage " << argv[0] << " ip port " << endl;
        return 1;
    }
    string serve_ip = argv[1];
    uint16_t serve_port = atoi(argv[2]);
    UdpClient* client = new UdpClient(serve_ip, serve_port);
    client->InitClient();
    client->Start();
    return 0;
}

3.5  本地测试

服务端和客户端的代码都已经编写完成,可以先进行本地测试,此时服务器没有绑定外网,绑定的是本地环回。现在运行服务器时指明端口号为8080,再运行客户端,此时客户端要访问的服务器的IP地址就是本地环回,服务端的端口号就是8081

6b071bc04d72474da93fb5a19bdfc00b.png



使用netstat命令查看网络信息,可以看到服务端的端口是8081,客户端的端口是36577,客户端也已动态绑定成功了


afbb78b8ef9f4534b1ed6f9cf26c6b71.png


3.6 INADDR_ANY

进行网络测试,直接让服务端绑定公网IP,此时这个服务端就可以被外网访问


将服务端设置的本地环回改为博主的公网IP,此时重新运行服务端的时候会发现服务端绑定失败


ee106823ed1e47feb95aba875e119ae2.png


由于云服务器的IP地址是由对应的云厂商提供的,这个IP地址并不一定是真正的公网IP,这个IP地址是不能直接被绑定的,若需要让外网访问,此时需要bind 0。系统当中提供的一个INADDR_ANY(宏值),对应的值就是0


若需要让外网访问,那么进行绑定时就应该绑定INADDR_ANY,此时服务器才能够被外网访问


绑定INADDR_ANY的好处


当一个服务器的带宽足够大时,一台机器接收数据的能力就约束了这台机器的IO效率,因此一台服务器底层可能装有多张网卡,此时这台服务器就可能会有多个IP地址,但一台服务器上端口号为8081的服务只有一个。这台服务器在接收数据时,多张网卡在底层实际都收到了数据,可能这些数据也都想访问端口号为8081的服务


此时若服务端在绑定的时候是指明绑定的某一个IP地址,那么此时服务端在接收数据的时候就只能从绑定IP对应的网卡接收数据。而若服务端绑定的是INADDR_ANY,那么只要是发送给端口号为8081的服务的数据,系统都会可以将数据自底向上交给该服务端


52d8fe3ebf9d425fa34f99395abfc88e.png


因此服务端绑定INADDR_ANY这种方案是强烈推荐的,所有的服务器具体在操作时采用的也是这种方案


若你既想让外网访问你的服务器,但又想指向绑定某一个IP地址,那么就不能使用云服务器,此时可以选择使用虚拟机或者自定义安装的Linux操作系统,那个IP地址就是支持自定义绑定的,而云服务器是不支持的


更改代码

bool UdpServer::InitServer() {
    //创建套接字
    _socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(_socket_fd < 0) {
        cerr << "socket fail" << endl;
        return false;
    }
    cout << "socket create success , fd:" << _socket_fd << endl;
    //填充网络通信相关信息
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(_port);
    local.sin_addr.s_addr = INADDR_ANY;
    //绑定
    if(bind(_socket_fd, (struct sockaddr*)&local, sizeof(local)) < 0) {
        cerr << "bind fail" << endl;
        return false;
    }
    cout << "bind success" << endl;
    return true;
}


此时再重新编译运行服务器时就不会绑定失败了,并且此时再用netstat命令查看时会发现,该服务器的本地IP地址变成0.0.0.0,这意味着该UDP服务器可以在本地读取任何一张网卡里面的数据


b00fa090c54b48458728802bbf295405.png


3.7 回声功能

由于在进行网络测试的时候,当客户端发送数据给服务端时,服务端会将从客户端收到的数据进行打印,因此服务端是能够看到现象的。但客户端一直在向服务端发送数据,在客户端这边看不出服务端是否收到了自己发送的数据


服务端代码编写


可以将该服务器改成一个回声服务器。当服务端收到客户端发来的数据后,除了在服务端进行打印以外,服务端可以调用sendto函数将收到的数据重新发送给对应的客户端


服务端在调用sendto函数时需要传入客户端的网络属性信息,但服务端是知道客户端的网络属性信息的,因为服务端在此之前就已经通过recvfrom函数获取到了客户端的网络属性信息

void UdpServer::Start()
{
    char buffer[SIZE];
    while(true) 
    {
        struct sockaddr_in ping;
        socklen_t length =  sizeof(ping);
        ssize_t size = recvfrom(_socket_fd, buffer, SIZE - 1, 0, (struct sockaddr*)&ping, &length);
        if(size > 0) {
            buffer[size] = '\0';
            uint16_t port = ntohs(ping.sin_port);
            string ip = inet_ntoa(ping.sin_addr);
            cout << "[" << ip << ":" << port << "]#" << buffer << endl;
        }
        else {
            cerr << "recvfrom fail" << endl;
        }
        string echo_message = "server echo:";
    echo_message += buffer;
    sendto(_socket_fd, echo_message.c_str(), echo_message.size(), 0, (struct sockaddr*)&ping, length);
    } 
}


客户端代码编写


当客户端发完数据给服务端后,由于服务端还会将该数据重新发给客户端,因此客户端发完数据后还需要调recvfrom来读取服务端发来的响应数据,客户端接收到服务端的响应数据后,将数据原封不动的打印出来就行了


此时客户端发送给服务端的数据,除了在服务端会打印显示以外,服务端还会将数据再重新发回给客户端,此时客户端也会接收到响应数据然后将该数据进行打印

void UdpClient::Start()
{
    string message;
    struct sockaddr_in receive;
    memset(&receive, 0, sizeof(receive));
    receive.sin_port = htons(_server_port);
    receive.sin_family = AF_INET;
    receive.sin_addr.s_addr = inet_addr(_server_ip.c_str());
    while(true)
    {
        cout << "please Enter#";
        getline(cin, message);
        sendto(_socket_fd, message.c_str(), strlen(message.c_str()), 0, (struct sockaddr*)&receive, sizeof(receive));
        char buffer[SIZE];
    struct sockaddr_in tmp;
    socklen_t length = sizeof(tmp);
    ssize_t size = recvfrom(_socket_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&tmp, &length);
    if (size > 0) {
      buffer[size] = '\0';
      cout << buffer << endl;
    }
    }
}


3.8 网络测试

此时可以使用 sz命令 将该客户端可执行程序下载到本地机器,然后将该程序发送给你的朋友


当你的朋友收到这个客户端的可执行程序后,可以使用 rz命令 或拖拽的方式将这个可执行程序上传到他的云服务器上,然后使用 chmod命令 给该文件加上可执行权限


先将服务端启动,然后你的朋友将你的IP地址和端口号作为命令行参数运行客户端,就可以访问你的服务器了

ee05c42a98654440a96f0ba1eeed6da8.png

相关实践学习
2分钟自动化部署人生模拟器
本场景将带你借助云效流水线Flow实现人生模拟器小游戏的自动化部署
7天玩转云服务器
云服务器ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,可降低 IT 成本,提升运维效率。本课程手把手带你了解ECS、掌握基本操作、动手实操快照管理、镜像管理等。了解产品详情:&nbsp;https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
开发者 Python
Python Socket编程:不只是基础,更有进阶秘籍,让你的网络应用飞起来!
在数字时代,网络应用成为连接世界的桥梁。Python凭借简洁的语法和丰富的库支持,成为开发高效网络应用的首选。本文通过实时聊天室案例,介绍Python Socket编程的基础与进阶技巧。基础篇涵盖服务器和客户端的建立与数据交换;进阶篇则探讨多线程与异步IO优化方案,助力提升应用性能。通过本案例,你将掌握Socket编程的核心技能,推动网络应用飞得更高、更远。
55 1
|
20天前
|
Kubernetes 网络协议 Python
Python网络编程:从Socket到Web应用
在信息时代,网络编程是软件开发的重要组成部分。Python作为多用途编程语言,提供了从Socket编程到Web应用开发的强大支持。本文将从基础的Socket编程入手,逐步深入到复杂的Web应用开发,涵盖Flask、Django等框架的应用,以及异步Web编程和微服务架构。通过本文,读者将全面了解Python在网络编程领域的应用。
20 1
|
23天前
|
Java
[Java]Socket套接字(网络编程入门)
本文介绍了基于Java Socket实现的一对一和多对多聊天模式。一对一模式通过Server和Client类实现简单的消息收发;多对多模式则通过Server类维护客户端集合,并使用多线程实现实时消息广播。文章旨在帮助读者理解Socket的基本原理和应用。
19 1
|
29天前
|
消息中间件 监控 网络协议
Python中的Socket魔法:如何利用socket模块构建强大的网络通信
本文介绍了Python的`socket`模块,讲解了其基本概念、语法和使用方法。通过简单的TCP服务器和客户端示例,展示了如何创建、绑定、监听、接受连接及发送/接收数据。进一步探讨了多用户聊天室的实现,并介绍了非阻塞IO和多路复用技术以提高并发处理能力。最后,讨论了`socket`模块在现代网络编程中的应用及其与其他通信方式的关系。
|
1月前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【10月更文挑战第10天】网络协议定义了机器间通信的标准格式,确保信息准确无损地传输。主要分为两种模型:OSI七层模型与TCP/IP模型。
|
2月前
|
网络协议 Python
网络世界的建筑师:Python Socket编程基础与进阶,构建你的网络帝国!
在数字宇宙中,网络如同复杂脉络连接每个角落,Python Socket编程则是开启这一世界的钥匙。本文将引导你从基础概念入手,逐步掌握Socket编程,并通过实战示例构建TCP/UDP服务器与客户端。你将学会使用Python的socket模块进行网络通信,了解TCP与UDP的区别,并运用多线程与异步IO提升服务器性能。跟随本文指引,成为网络世界的建筑师,构建自己的网络帝国。
36 2
|
2月前
|
网络协议 Python
告别网络编程迷雾!Python Socket编程基础与实战,让你秒变网络达人!
在网络编程的世界里,Socket编程是连接数据与服务的关键桥梁。对于初学者,这往往是最棘手的部分。本文将用Python带你轻松入门Socket编程,从创建TCP服务器与客户端的基础搭建,到处理并发连接的实战技巧,逐步揭开网络编程的神秘面纱。通过具体的代码示例,我们将掌握Socket的基本概念与操作,让你成为网络编程的高手。无论是简单的数据传输还是复杂的并发处理,Python都能助你一臂之力。希望这篇文章成为你网络编程旅程的良好开端。
56 3
|
2月前
|
网络协议 开发者 Python
网络编程小白秒变大咖!Python Socket基础与进阶教程,轻松上手无压力!
在网络技术飞速发展的今天,掌握网络编程已成为开发者的重要技能。本文以Python为工具,带你从Socket编程基础逐步深入至进阶领域。首先介绍Socket的概念及TCP/UDP协议,接着演示如何用Python创建、绑定、监听Socket,实现数据收发;最后通过构建简单的聊天服务器,巩固所学知识。让初学者也能迅速上手,成为网络编程高手。
74 1
|
2月前
|
网络协议 安全 网络安全
震惊!Python Socket竟能如此玩转网络通信,基础到进阶全攻略!
【9月更文挑战第12天】在网络通信中,Socket编程是连接不同应用与服务的基石。本文通过问答形式,从基础到进阶全面解析Python Socket编程。涵盖Socket的重要性、创建TCP服务器与客户端、处理并发连接及进阶话题如非阻塞Socket、IO多路复用等,帮助读者深入了解并掌握网络通信的核心技术。
64 6
|
1月前
|
网络协议 测试技术 网络安全
Python编程-Socket网络编程
Python编程-Socket网络编程