• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

网络编程套接字UDP协议的网络程序

武飞扬头像
…狂奔的蜗牛~
帮助1

认识TCP协议和UDP协议

网络协议栈是贯穿整个体系结构的,在应用层、操作系统层和驱动层各有一部分。当我们使用系统调用接口实现网络数据通信时,不得不面对的协议层就是传输层,而传输层最典型的两种协议就是TCP协议和UDP协议。

TCP协议

  • TCP协议叫做传输控制协议(Transmission Control Protocol),TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。

  • TCP协议是面向连接的,如果两台主机之间想要进行数据传输,那么必须要先建立连接,当连接建立成功后才能进行数据传输。其次,TCP协议是保证可靠的协议,数据在传输过程中如果出现了丢包、乱序等情况,TCP协议都有对应的解决方法。

UDP协议

  • UDP协议叫做用户数据报协议(User Datagram Protocol),UDP协议是一种无需建立连接的、不可靠的、面向数据报的传输层通信协议。

  • 使用UDP协议进行通信时无需建立连接,如果两台主机之间想要进行数据传输,那么直接将数据发送给对端主机就行了,但这也就意味着UDP协议是不可靠的,数据在传输过程中如果出现了丢包、乱序等情况,UDP协议本身是不知道的。

既然UDP协议是不可靠的,那为什么还要有UDP协议的存在?

TCP协议是一种可靠的传输协议,使用TCP协议能够在一定程度上保证数据传输时的可靠性,而UDP协议是一种不可靠的传输协议,UDP协议的存在有什么意义?

首先,TCP协议虽然是一种可靠的传输协议,但这一定意味着TCP协议在底层需要做更多的工作,因此TCP协议底层的实现是比较复杂的,我们不能只看到TCP协议面向连接可靠这一个特点,我们也要能看到TCP协议对应的缺点。

同样的,UDP协议虽然是一种不可靠的传输协议,但这一定意味着UDP协议在底层不需要做过多的工作,因此UDP协议底层的实现一定比TCP协议要简单,UDP协议虽然不可靠,但是它能够快速的将数据发送给对方,虽然在数据在传输的过程中可能会出错。

编写网络通信代码时具体采用TCP协议还是UDP协议,完全取决于上层的应用场景。如果应用场景严格要求数据在传输过程中的可靠性,此时我们就必须采用TCP协议,如果应用场景允许数据在传输出现少量丢包,那么我们肯定优先选择UDP协议,因为UDP协议足够简单。

注意:
一些优秀的网站在设计网络通信算法时,会同时采用TCP协议和UDP协议,当网络流畅时就使用UDP协议进行数据传输,而当网速不好时就使用TCP协议进行数据传输,此时就可以动态的调整后台数据通信的算法。

简单的UDP网络程序

  • UDP协议是无连接的,不可靠的网络协议。本章将介绍如何使用UDP协议进行程序设计,对UDP编程的基本框架进行介绍并给出程序设计的例子。

  • 使用UDP进行程序设计可以分为客户端和服务器端两部分。服务器端主要包含创建套接字、将套接字与地址结构进行绑定、读写数据、关闭套接字几个过程。客户端包括建立套接字、读写数据、关闭套接字几个过程。服务端和客户端两个流程之间的主要差别在于对地址的绑定(bind())函数,客户端可以不用进行地址和端口的绑定操作。

学新通

接下来我们实现一个简单的UDP网络程序。

服务端创建套接字

我们把服务器封装成一个类,当我们定义出一个服务器对象后需要马上初始化服务器,而初始化服务器需要做的第一件事就是创建套接字。

socket函数

创建套接字的函数叫做socket,该函数的函数原型如下:

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

参数说明:

  • domain:创建套接字的域或者叫做协议家族,也就是创建套接字的类型。该参数就相当于struct sockaddr结构的前16个位。如果是本地通信就设置为AF_UNIX,如果是网络通信就设置为AF_INET(IPv4)或AF_INET6(IPv6)。
  • type:创建套接字时所需的服务类型。其中最常见的服务类型是SOCK_STREAM和SOCK_DGRAM,如果是基于UDP的网络通信,我们采用的就是SOCK_DGRAM,叫做用户数据报服务,如果是基于TCP的网络通信,我们采用的就是SOCK_STREAM,叫做流式套接字,提供的是流式服务。
  • protocol:创建套接字的协议类别。你可以指明为TCP或UDP,但该字段一般直接设置为0就可以了,设置为0表示的就是默认,此时会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议。

返回值说明:

  • 套接字创建成功返回一个文件描述符,创建失败返回-1,同时错误码会被设置。

socket函数属于什么类型的接口?

网络协议栈是分层的,按照TCP/IP四层模型来说,自顶向下依次是应用层、传输层、网络层和数据链路层。而我们现在所写的代码都叫做用户级代码,也就是说我们是在应用层编写代码,因此我们调用的实际是下三层的接口,而传输层和网络层都是在操作系统内完成的,也就意味着我们在应用层调用的接口都叫做系统调用接口。

socket函数是被谁调用的?

socket这个函数是被程序调用的,但并不是被程序在编码上直接调用的,而是程序编码形成的可执行程序运行起来变成进程,当这个进程被CPU调度执行到socket函数时,然后才会执行创建套接字的代码,也就是说socket函数是被进程所调用的。

socket函数底层做了什么?

  • socket函数是被进程所调用的,而每一个进程在系统层面上都有一个进程地址空间PCB(task_struct)、文件描述符表(files_struct)以及对应打开的各种文件。而文件描述符表里面包含了一个数组fd_array,其中数组中的0、1、2下标依次对应的就是标准输入、标准输出以及标准错误。

  • 当我们调用socket函数创建套接字时,实际相当于我们打开了一个“网络文件”,打开后在内核层面上就形成了一个对应的struct file结构体,同时该结构体被连入到了该进程对应的文件双链表,并将该结构体的首地址填入到了fd_array数组当中下标为3的位置,此时fd_array数组中下标为3的指针就指向了这个打开的“网络文件”,最后3号文件描述符作为socket函数的返回值返回给了用户。

  • 其中每一个struct file结构体中包含的就是对应打开文件各种信息,比如文件的属性信息、操作方法以及文件缓冲区等。其中文件对应的属性在内核当中是由struct inode结构体来维护的,而文件对应的操作方法实际就是一堆的函数指针(比如read和write)在内核当中就是由struct file_operations结构体来维护的。而文件缓冲区对于打开的普通文件来说对应的一般是磁盘,但对于现在打开的“网络文件”来说,这里的文件缓冲区对应的就是网卡。

学新通

服务端创建套接字

当我们在进行初始化服务器创建套接字时,就是调用socket函数创建套接字,创建套接字时我们需要填入的协议家族就是AF_INET,因为我们要进行的是网络通信,而我们需要的服务类型就是SOCK_DGRAM,因为我们现在编写的UDP服务器是面向数据报的,而第三个参数之间设置为0即可。

#include<iostream>
#include<cerrno>
#include<sys/types.h>
#include<sys/socket.h>
int main()
{
    //1.创建套接字,打开网络文件
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        cout<<"socket create error:"<<errno<<endl;
        return 1;
    }

    //std::cout << "socket create success, sockfd: " << sock << std::endl;
    
    return 0;
}
学新通

我们可以做一个简单的测试,看看套接字是否创建成功。

std::cout << "socket create success, sockfd: " << sock << std::endl;

学新通
运行程序后可以看到套接字是创建成功的,对应获取到的文件描述符就是3,因为0、1、2默认被标准输入流、标准输出流和标准错误流占用了,此时最小的、未被利用的文件描述符就是3。


服务端绑定

接下来,作为一个服务器,要不要让客户知道,本服务器的地址(ip port)?
必须的!
所以服务器要做的第二件事就是绑定。

bind函数

绑定的函数叫做bind,该函数的函数原型如下:

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

参数说明:

  • sockfd:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符。
  • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址,包括协议家族、IP地址、端口号等。
  • addrlen:传入的addr结构体的长度。

返回值说明:

  • 绑定成功返回0,绑定失败返回-1,同时错误码会被设置。

绑定步骤如下:

  • 定义一个struct sockaddr_in结构体,将服务器网络相关的属性信息填充到该结构体当中,比如协议家族、IP地址、端口号等。
  • 填充服务器网络相关的属性信息时,协议家族对应就是AF_INET,端口号就是当前TCP服务器程序的端口号。在设置端口号时,需要调用htons函数将端口号由主机序列转为网络序列。
  • 在设置服务器的IP地址时,我们可以设置为本地环回127.0.0.1,表示本地通信。也可以设置为公网IP地址,表示网络通信。
  • 我们使用的是云服务器,那么在设置服务器的IP地址时,直接将IP地址设置为INADDR_ANY即可,此时服务器就可以从本地任何一张网卡当中读取数据。此外,由于INADDR_ANY本质就是0,因此在设置时不需要进行网络字节序的转换。

INADDR_ANY的思考

  • INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
  • 比如一台电脑有3块网卡,分别连接三个网络,那么这台电脑就有3个ip地址,如果绑定某个具体的ip地址,你的端口只能通过接你所设置的ip地址所在的网卡接收数据,其它两块网卡无法进行接收,如果三个网卡都想接收,那就需要绑定3个ip,也就等于需要管理3个套接字进行数据交换,这样岂不是很繁琐?
  • 所以就出现了INADDR_ANY,你只需绑定INADDR_ANY,管理一个套接字就行,此时不管数据是从哪个网卡过来的,只要是你绑定的端口号过来的数据,都可以接收到。
  • 填充完服务器网络相关的属性信息后,需要调用bind函数进行绑定。绑定实际就是将文件与网络关联起来,如果绑定失败也没必要进行后续操作了,直接终止程序即可。需要注意,由于bind函数提供的是通用参数类型,因此在传入结构体地址时还需要将struct sockaddr_in强转为struct sockaddr类型后再进行传入。
#include<iostream>
#include<string>
#include<cerrno>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

using namespace std;

const uint16_t port=8080;

//udp_server
int main()
{
    //1.创建套接字,打开网络文件
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        cout<<"socket create error:"<<errno<<endl;
        return 1;
    }

    std::cout << "socket create success, sockfd: " << sock << std::endl;
		
	//2.给该服务器绑定端口号和ip(特殊处理)

    struct sockaddr_in local;
    memset(&local,0,sizeof(local));//清空
    local.sin_family=AF_INET;
    local.sin_port=htons(port);//	此处的端口号是我们的计算机上的变量,是主机序列,所以要转为网络序列
    //接下来在绑定ip时,要明白的是:
    //a:需要将人识别的点分十进制, 字符串风格IP地址,转化为4字节整数IP
    //b:也要考虑大小端
    //in_addr_t inet_addr(const char* cp); ->此函数能完成上面ab两个工作

    //local.sin_addr.s_addr=inet_addr("101.42.90.32");//点分十进制,字符串风格    
    //注意:我们使用的云服务器,所以设置为INADDR_ANY即可
    local.sin_addr.s_addr=INADDR_ANY;

    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        cerr<<"bind error:"<<errno<<endl;
        return 2;
    }
	std::cout << "bind success" << std::endl;
    return 0;
}
学新通

定义好struct sockaddr_in结构体后,最好先用memset函数对该结构体进行清空,也可以用bzero函数进行清空(但是不建议使用)。bzero函数也可以对特定的一块内存区域进行清空,bzero函数的函数原型如下:

void bzero(void *s, size_t n);

服务端读取数据并运行

UDP服务器的初始化就只需要创建套接字和绑定就行了,当服务器初始化完毕后我们就可以启动服务器了。

服务器实际上就是在周而复始的为我们提供某种服务,服务器之所以称为服务器,是因为服务器运行起来后就永远不会退出,因此服务器实际执行的是一个死循环代码。由于UDP服务器是不面向连接的,因此只要UDP服务器启动后,就可以直接读取客户端发来的数据。

recvfrom函数

UDP服务器读取数据的函数叫做recvfrom,该函数的函数原型如下:

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地址、端口号等。
  • addrlen:调用时传入期望读取的src_addr结构体的长度,返回时代表实际读取到的src_addr结构体的长度,这是一个输入输出型参数。

返回值说明:

读取成功返回实际读取到的字节数,读取失败返回-1,同时错误码会被设置。

注意:

  • 由于UDP是不面向连接的,因此我们除了获取到数据以外还需要获取到对端网络相关的属性信息,包括IP地址和端口号等。
  • 在调用recvfrom读取数据时,必须将addrlen设置为你要读取的结构体对应的大小。
  • 由于recvfrom函数提供的参数也是struct sockaddr类型的,因此我们在传入结构体地址时需要将struct sockaddr_in类型进行强转。

现在服务端通过recvfrom函数读取客户端数据:

注意:我们默认认为通信的数据是双方在互发字符串,但是在网络通信过程中,只有报文大小,或者是字节流中字节的个数,没有C\C 字符串这样的概念,所以我们如果要将读取到的数据当作字符串看待,需要将读取到的数据的最后一个位置设置为’\0’。

// //3.提供服务
    bool quit = false;
#define NUM 1024
    char buffer[NUM];
    while (!quit)
    {
        struct sockaddr_in peer; //把客户端信息存到buffer
        socklen_t len = sizeof(peer);
        //注意:我们默认认为通信的数据是双方在互发字符串

        //接收信息(udp方式 )
        ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (cnt > 0)
        {
            buffer[cnt]=0;//0=='\0'
            
            cout << "client#" << buffer << endl;//在服务端显示客户端输入的内容
            //根据用户输入,构建一个新的返回字符串
            string echo_hello="hello";//作为返回内容返回给客户端
            sendto(sock, echo_hello.c_str(), echo_hello.size(), 0, (struct sockaddr *)&peer, len);//向客户端发送响应内容
        }
        else
        {
            //TODO
            std::cerr << "recvfrom error" << std::endl;
        }
    }
学新通

通过recvfrom读取到客户端数据存入buffer,并且通过sendto函数把echo_hello中内容作为响应发送给客户端。

注意:如果调用recvfrom函数读取数据失败,我们可以打印一条提示信息,但是不要让服务器退出,服务器不能因为读取某一个客户端的数据失败就退出。

sendto函数

发送数据的函数叫做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_in类型进行强转。

引入命令行参数

鉴于构造服务器时需要传入IP地址和端口号,我们这里可以引入命令行参数。此时当我们运行服务器时在后面跟上对应的IP地址和端口号即可。

由于云服务器的原因,后面实际不需要传入IP地址,因此在运行服务器的时候我们只需要传入端口号即可,

#include <iostream>
#include <string>
#include <cerrno>
#include<cstdio>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;

//const uint16_t port = 8080;
//引入命令行参数
string Usage(string proc)
{
    cout<<"Usage: "<<proc<<"port"<<endl;
}

// ./udp_server port
int main(int argc,char *argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        return -1;
    }
	//需要注意的是,agrv数组里面存储的是字符串,而端口号是一个整数,因此需要使用atoi函数将字符串转换成整数。
    uint16_t port=atoi(argv[1]);

    // 1.创建套接字,打开网络文件
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        cout << "socket create error:" << errno << endl;
        return 1;
    }

    std::cout << "socket create success, sockfd: " << sock << std::endl;

    // 2.给该服务器绑定端口号和ip(特殊处理)

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port); 
    local.sin_addr.s_addr = INADDR_ANY;

    if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
    {
        cerr << "bind error:" << errno << endl;
        return 2;
    }

    std::cout << "bind success" << std::endl;

    // //3.提供服务
    bool quit = false;
#define NUM 1024
    char buffer[NUM];
    while (!quit)
    {
        struct sockaddr_in peer; //客户端信息
        socklen_t len = sizeof(peer);
        //注意:我们默认认为通信的数据是双方在互发字符串

        //接收信息(udp方式 )
        ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (cnt > 0)
        {
            buffer[cnt]=0;//0=='\0'
           
            cout << "client#" << buffer << endl;
            
            //根据用户输入,构建一个新的返回字符串
            string echo_hello="hello";
            sendto(sock, echo_hello.c_str(), echo_hello.size(), 0, (struct sockaddr *)&peer, len);
        }
        else
        {
        	std::cerr << "recvfrom error" << std::endl;
            //TODO
        }
    }

    return 0;
}
学新通

运行服务器:
此时运行服务器,可以看到提示说需要带上port,我们带上端口号再次运行程序就可以看到套接字创建成功、绑定成功,现在服务器就在等待客户端向它发送数据。
学新通

虽然现在客户端代码还没有编写,但是我们可以通过netstat命令来查看当前网络的状态,这里我们可以选择携带nlup选项。

netstat常用选项说明:

-n:直接使用IP地址,而不通过域名服务器。
-l:显示监控中的服务器的Socket。
-t:显示TCP传输协议的连线状况。
-u:显示UDP传输协议的连线状况。
-p:显示正在使用Socket的程序识别码和程序名称。

学新通

此时就能查看到对应网络相关的信息,在这些信息中程序名称为./udp_server的那一行显示的就是我们运行的UDP服务器的网络信息。

其中netstat命令显示的信息中,Proto表示协议的类型,Recv-Q表示网络接收队列,Send-Q表示网络发送队列,Local Address表示本地地址,Foreign Address表示外部地址,State表示当前的状态,PID表示该进程的进程ID,Program name表示该进程的程序名称。

其中Foreign Address写成0.0.0.0:*表示任意IP地址、任意的端口号的程序都可以访问当前进程。

接下来就到了客户端的程序编写了~

客户端创建套接字

客户端创建套接字时选择的协议家族也是AF_INET,需要的服务类型也是SOCK_DGRAM。

 //1.创建套接字,打开网络文件
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        cerr<<"socket error:"<<errno<<endl;
        return 1;
    }

与服务端不同的是,客户端在初始化时只需要创建套接字就行了,而不需要进行显示的绑定操作。

客户端绑定问题

  • 首先,客户端必须也要有ip和port。
  • 但是,客户端不需要显示bind,一旦显示bind,就必须明确,client要和哪一个port关联。
  • client指明的端口号,在client端一定会有吗?,有可能被占用,导致client无法使用
  • server要的是port必须明确,而且不变,但client只要有就行!一般是由OS自动给你bind
  • 也就是说client正常发送数据的时候,OS会自动给你bind,采用随机端口的方式。因此,客户端每次启动时使用的端口号可能是变化的,此时只要我们的端口号没有被耗尽,客户端就永远可以启动。

启动客户端

增加服务端IP地址和端口号

作为一个客户端,它必须知道它要访问的服务端的IP地址和端口号,因此需要引入服务端的IP地址和端口号.

  • 需要注意的是,客户端中存储的服务端的端口号此时是主机序列,我们需要调用htons函数将其转为网络序列后再设置进struct sockaddr_in结构体。
  • 同时,客户端中存储的服务端的IP地址是字符串IP,我们需要通过调用inet_addr函数将其转为整数IP后再设置进struct sockaddr_in结构体。
  • 我们这里也可以引入命令行参数。当我们运行客户端时直接在后面跟上对应服务端的IP地址和端口号即可。
void Usage(string proc)
{
    cout<<"Usage:\n\t" <<proc<<"server_ip server_port"<<endl;
}

int main(int argc,char* argv[])
{

    if(argc!=3)
    {
        Usage(argv[0]);
        return 0;
    }
     //1.创建套接字,打开网络文件
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        cerr<<"socket error:"<<errno<<endl;
        return 1;
    }

    //2.使用服务

    //b.你要给谁发
    struct sockaddr_in server;
    server.sin_family=AF_INET;
    server.sin_port=htons(atoi(argv[2]));//argv数组里面存储的是字符串,而端口号是一个整数,因此需要使用atoi函数将字符串转换成整数
    server.sin_addr.s_addr=inet_addr(argv[1]);

	return 0;
}
学新通

启动客户端

完成了客户端的初始化,我们就应该考虑客户端如何向服务端发送数据,以供服务端进行服务响应。

  • 我们将message作为要向服务端发送的数据,并且通过 sendto进行发送。
  • 我们向服务端发送了数据,同时也需要接受服务端响应的数据,因此我们通过recvfrom接收服务端响应的数据并存入buffer,打印显示。
void Usage(string proc)
{
    cout<<"Usage:\n\t" <<proc<<"server_ip server_port"<<endl;
}

int main(int argc,char* argv[])
{

    if(argc!=3)
    {
        Usage(argv[0]);
        return 0;
    }
     //1.创建套接字,打开网络文件
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        cerr<<"socket error:"<<errno<<endl;
        return 1;
    }

    //2.使用服务

    //b.你要给谁发
    struct sockaddr_in server;
    server.sin_family=AF_INET;
    server.sin_port=htons(atoi(argv[2]));//argv数组里面存储的是字符串,而端口号是一个整数,因此需要使用atoi函数将字符串转换成整数
    server.sin_addr.s_addr=inet_addr(argv[1]);
	
	
	while (1)
    {
        // a.你的数据从哪里来
        string message;
        cout << "输入# ";
        cin >> message;

        // cout<<"MyShell $ ";
        // char line[1024];
        // fgets(line,sizeof(line),stdin);

        // b.你要给谁发
        sendto(sock, message.c_str(), sizeof(message), 0, (struct sockaddr *)&server, sizeof(server));

        //此处tmp就是一个占位符
        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        char buffer[1024];
        ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&tmp, &len);
        if (cnt > 0)
        {
            //在网络通信过程中,只有报文大小,或者是字节流中字节的个数,没有C\C  字符串这样的概念(虽然我们后续可能会经常遇到类似的情况)
            buffer[cnt] = 0;
            cout << buffer << endl;
        }
        else
        {
            // TODO
        }
    }
	return 0;
}

学新通

至此,服务端和客户端代码基本完成。

测试程序

现在服务端和客户端的代码都已经编写完毕,我们可以进行测试。现在我们运行服务器时指明端口号为8081,再运行客户端,此时客户端要访问的服务器的IP地址是本地环回127.0.0.1,服务端的端口号就是8081。
学新通


客户端运行之后提示我们进行输入,当我们在客户端输入数据后,客户端将数据发送给服务端,此时服务端再将收到的数据打印输出,同时,我们在服务端的窗口也看到服务端返回的数据,即hello。
学新通


此时我们再用netstat命令查看网络信息,可以看到服务端的端口是8081,客户端随机分配的端口是37950。这里客户端能被netstat命令查看到,说明客户端也已经动态绑定成功了,这就是我们所谓的网络通信。
学新通

– the End –

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfcfkg
系列文章
更多 icon
同类精品
更多 icon
继续加载