网络编程--------基于UDP的服务器--客户端socket实例

更多linux知识点:linux目录索引


1. UDP协议的传输特点

  1. 不面向连接:udp传输时不需要建立连接,需指定我要跟谁进行数据传输
  2. 面向数据报:以数据包的形式进行传输
  3. 可靠传输:没有建立连接,那么在发数据和收数据时就有可能发生丢数据的情况

2. 客户端和服务器传输流程图

这里写图片描述

3. 实现代码

注:这里的接口使用在代码里都有注释,不在详细介绍

服务器端:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <arpa/inet.h>
#include <string.h>


//端口号的绑定,1025 - 65535 1-1024 是预留给操作系统的端口号
// ./srever IP地址 端口号(随意指定,不能被其他进程占用)
int main(int argc,char* argv[])
{

    /*以下代码相当于服务器启动时的准备工作*/
    if(argc != 3){//进程 ip地址, 端口号
        printf("Usage ./server [ip] [port]\n");
        return 1;//退出码,表示程序跑完,结果出错
    }
    int fd = socket(AF_INET,SOCK_DGRAM,0);//1.哪个协议族,面向数据报
    if(fd < 0){//文件描述符被占用,出错;一个进程的文件描述符最多为ulimit -a
        perror("socket");
        return 2;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;//协议族
    addr.sin_addr.s_addr = inet_addr(argv[1]);//将10进制IP地址转换成unint_32ip
    addr.sin_port = htons(atoi(argv[2]));//将端口号转成整数,在将其转成字节序

    //将文件描述符进行绑定,其实是将端口号绑定
    int ret = bind(fd,(struct sockaddr*)&addr,sizeof(addr));//指针强转
    if(ret < 0){//如果端口号被其他进程占用,会失败
        perror("bind");
        return 3;
    }

    while(1){//服务器一旦启动,不会进行退出,会一直运行下去,为了反复的从客户端接受数据,处理数据,向客户端返回数据
        struct sockaddr_in peer;//结构体,里面存着对应端的ip和端口号(这里指源ip和源端口号)
        socklen_t len = sizeof(peer);//相当于缓冲区的长度,输入输出型的参数(理论上没有变化)
        char buf[1024] = {0};

        ssize_t read_size = recvfrom(fd, buf, sizeof(buf)-1, 0,
        (struct sockaddr*)&peer,&len);//缓冲区的大小预留一个字节,用来结束字符串\0,sockaddr* src_addr是输出型参数,一旦从客户端返回,就可以拿到客户端的ip地址和端口号(源ip,源端口号)

        if(read_size < 0){//说明执行失败,当服务器执行失败后,尝试下一次的拿数据
            perror("recvfrom");
            continue;
        }
       buf[read_size] = '\0';//返回值表示读到多少字节
       //a -> ascii
       printf("client %s: %d  say: %s\n",
       inet_ntoa(peer.sin_addr), ntohs(peer.sin_port), buf);

       //将收到的数据发给哪个socket,发的数据在哪里,要发给哪个端口号,这里指刚才接收到的源ip和源端口号
       sendto(fd, buf, strlen(buf), 0, (struct sockaddr*)&peer,len);//将收到的数据返回给客户端
    }
    close(fd);//一般执行不到
    return 0;
}

客户端:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

//通过客户端连接哪个服务器
// ./client [127.0.0.1]  [9090]
//         环回ip,表示自己的ip地址?发送数据是我自己,接收方也是我自己

int main(int agrc,char* argv[]){
    if(agrc != 3){
        printf("Usage ./client [ip] [port]\n");
        return 1;
    }
    //创建一个socket,协议族,面向数据报
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd < 0){
        perror("socket");
        return 1;
    }

    struct sockaddr_in server_addr;//保存服务器端的ip地址和端口号
    server_addr.sin_family = AF_INET;//协议族
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);//将ip地址进行强转,unint_32
    server_addr.sin_port = htons(atoi(argv[2]));//将端口号强转为unint_16

    //如果客户端不绑定端口号,操作系统会在通信时自动分配一个端口号
    //一般不绑定端口号,由于客户端的机器装了哪些程序不确定
    //如果强行绑定端口号,就可能和客户端的其他程序冲突

    while(1){
        char buf[1024] = {0};
        ssize_t read_size = read(0, buf, sizeof(buf)-1);
        if(read_size < 0){//客户端从标准输入读取数据出错,可以进行退出,因为一个服务器可以对应多个客户端
            perror("read");
            return 2;
        }

        if(read_size == 0){//read返回值为0表示读到文件结束EOF
            printf("read done\n");
            return 0;//表示代码跑完,结果正确
        }
        buf[read_size] = '\0';

        //向服务器发数据
        sendto(fd, buf, strlen(buf), 0,
                (struct sockaddr*)&server_addr, sizeof(server_addr));

        char buf_output[1024] = {0};
        read_size = recvfrom(fd, buf_output, sizeof(buf_output)-1, 0,
                    NULL, NULL);//不关心服务器的端口号和ip,因为客户端一定知道从哪个服务器端返回的数据
        if(read_size < 0){
            perror("recvfrom");
            return 2;
        }
        buf_output[read_size] = '\0';
        printf("server respon: %s\n",buf_output);
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhangye3017/article/details/80787822