【C-实践】一对一的远程通信(2.0)

概述


使用udp+select实现的C/S远程通信


功能


  • 服务器只能与单一的客户端进行信息交互
  • 如果服务器连接了多个客户端,由于是单一进程无法同时对多个客户端回复,但可以群发消息(接受时发送)


udp协议

概念

  • 是一种无连接的,不可靠的,基于数据报的协议
  • ip协议上增加了端口号,用于区分不同进程间的数据传输

TCP相比

  • 开销更小,速度更快,不需要三次握手确认连接,直接发送
  • 没有确认机制,可能导致数据包丢失、乱序或重复等问题

适用场景

  • 实时性要求高的应用(如在线视频,音频传输等)
  • 数据量小,传播频率高的应用(如DNS查询,SNMP简单网络管理协议等)
  • 需要广播或多播的应用


启动


启动服务器

1、在bin目录下生成可执行文件

w@Ubuntu20:bin $ gcc ../src/*.c -o server

2、启动服务器

w@Ubuntu20:bin $ ./server ../conf/server.conf

启动客户端

1、在客户端的目录下生成可执行文件

w@Ubuntu20:Chat2_client $ gcc client.c -o client

2、启动客户端(传入两个命令行参数:服务器ip地址,服务器port端口号)

w@Ubuntu20:Chat2_client $ ./client 192.168.160.129 2000


目录设计


服务器

w@Ubuntu20:Chat2_server $ tree
.
├── bin
│   └── server
├── conf
│   └── server.conf
├── include
│   └── udp.h
└── src
    ├── interact.c
    ├── main_server.c
    └── udp_init.c

4 directories, 6 files


配置文件 server.conf

存放ip地址和port端口,自行更改

192.168.160.129
2000


代码



服务器代码


udp.h

#ifndef __UDP_H__
#define __UDP_H__

//检查命令行参数个数
#define ARGS_CHECK(argc, num) { if (argc != num) {\
    fprintf(stderr, "Args error!\n"); return -1; }}

//检查系统调用返回值是否合法,非法报错退出
#define ERROR_CHECK(ret, num, msg) { if (ret == num) {\
    perror("msg");  return -1;  } }


//输入:配置文件(服务器的ip地址,端口号)
//输出:绑定了服务器ip和端口的的套接字
int udp_init(char *conf);

//与客户端的交互
int interact_cli(int sfd);

#endif


main_server.c

#include "../include/udp.h"
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) 
{
    //命令行参数:配置文件路径
    ARGS_CHECK(argc, 2);

    //得到一个绑定了服务器ip和端口的udp类型的套接字
    int sfd = udp_init(argv[1]);

    //与客户端交互
    interact_cli(sfd); 

    //关闭服务器套接字
    close(sfd);
    return 0;
}


udp_init.c

#include "../include/udp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <arpa/inet.h>

//输入:配置文件(服务器的ip地址,端口号)
//输出:绑定了服务器ip和端口的的套接字
int udp_init(char *conf)
{
    //从配置文件中读取服务器的ip和端口
    FILE* fp = fopen(conf, "r"); 
    char ip[128] = {0};
    char port[128] = {0};
    fscanf(fp, "%s%s", ip, port);
    printf("ip = %s, port = %s\n", ip, port);
    fclose(fp);
    
    //生成一个udp类型的套接字
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    ERROR_CHECK(sfd, -1, "ser_socket");

    //将套接口设置为可重用, 不用再等待重启时的TIME_WAIT时间
    int reuse = 1;
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

    //给套接字绑定服务端ip和port
    struct sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(struct sockaddr_in));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr(ip);
    serverAddr.sin_port = htons(atoi(port));

    int ret = bind(sfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    ERROR_CHECK(ret, -1, "ser_bind");

    return sfd;
}



interact.c

#include "../include/udp.h"
#include <stdio.h>
#include <string.h>

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

//接受所有客户端的信息,并显示在终端
//只能发消息给最后一个连接的客户端
int interact_cli(int sfd)
{
    //定义客户端结构体,用来接受客户端的ip和port
    struct sockaddr_in clientAddr;
    memset(&clientAddr, 0, sizeof(clientAddr));
    socklen_t cliLen = sizeof(clientAddr);

    int ret = 0;
    char buf[64] = {0};//读写缓冲区

    //需要先接收客户端信息,建立udp通信
    //同时获取客户端的ip和port
    ret = recvfrom(sfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&clientAddr, &cliLen);
    ERROR_CHECK(ret, -1, "recvfrom");
    printf("cli: %s\n", buf);

    //定义一个都是读操作的文件描述符的集合
    fd_set rdset;
    FD_ZERO(&rdset);

    //使用select监听所有读操作
    while (1) {
        //重置读集合,因为每次select会修改读集合(将没就绪的文件描述符置为0)
        FD_SET(STDIN_FILENO, &rdset);
        FD_SET(sfd, &rdset);

        ret = select(10, &rdset, NULL, NULL, NULL);
        ERROR_CHECK(ret, -1, "select");

        //服务器套接字就绪,有客户端的数据,读取并打印在终端
        if (FD_ISSET(sfd, &rdset)) {
            memset(buf, 0, sizeof(buf));
            ret = recvfrom(sfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&clientAddr, &cliLen);
            ERROR_CHECK(ret, -1, "recvfrom");
            printf("cli: %s\n", buf);
            
            //可以同时回复客户端一些信息
            //sendto
        }

        //标准输入就绪,读取并发送给最后一个连接的客户端
        if (FD_ISSET(STDIN_FILENO, &rdset)) {
            memset(buf, 0, sizeof(buf));
            read(STDIN_FILENO, buf, sizeof(buf) - 1);
            sendto(sfd, buf, strlen(buf) - 1, 0, (struct sockaddr*)&clientAddr, cliLen);
        }
    }

    return 0;
}





客户端代码


main_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>

//检查命令行参数个数                                             
#define ARGS_CHECK(argc, num) { if (argc != num) {\
    fprintf(stderr, "Args error!\n"); return -1; }}

//检查函数返回值是否合法,非法报错退出
#define ERROR_CHECK(ret, num, msg) { if (ret == num) {\
    perror("msg");  return -1;  } }


//与服务端交互
//输入:服务器套接字,服务器信息的结构体
int interact_ser(int sfd, struct sockaddr_in *serAddr)
{
    //定义一个都是读操作的文件描述符的集合
    fd_set rdset;
    FD_ZERO(&rdset);

    char buf[128] = {0};//读写缓冲区
    int ret = -1;
    socklen_t serLen = sizeof(struct sockaddr_in);//服务器信息结构体的长度

    //使用select监听所有读操作
    while (1) {
        //重置读集合,因为每次select会修改读集合(将没就绪的文件描述符置为0)
        FD_SET(STDIN_FILENO, &rdset);
        FD_SET(sfd, &rdset);

        //select轮询就绪的文件描述符
        ret = select(sfd + 1, &rdset, NULL, NULL, NULL);
        ERROR_CHECK(ret, -1, "select");

        //先给服务端发送数据,建立udp通信,再从服务端接受数据

        //标准输入就绪,读取并发送给服务端
        if (FD_ISSET(STDIN_FILENO, &rdset)) {
            memset(buf, 0, sizeof(buf));
            read(STDIN_FILENO, buf, sizeof(buf) - 1);
            sendto(sfd, buf, strlen(buf) - 1, 0, (struct sockaddr*)serAddr, serLen);
        }

        //服务器套接字就绪,读取并打印在终端
        if (FD_ISSET(sfd, &rdset)) {
            memset(buf, 0, sizeof(buf));
            ret = recvfrom(sfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)serAddr, &serLen);
            if (0 == ret) {
                //服务端已关闭
                printf("server exit\n");            
                return -1;
            }
            printf("ser: %s\n", buf);
        }
    }
}

int main(int argc, char *argv[])
{
    //参数:服务器的ip和端口
    ARGS_CHECK(argc, 3);

    //创建一个udp类型的套接字, 用于与服务端通信
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    ERROR_CHECK(sfd, -1, "cli_socket");

    //创建存储服务器ip和port的结构体
    struct sockaddr_in serAddr;
    memset(&serAddr, 0, sizeof(serAddr));
    serAddr.sin_family = AF_INET;
    serAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serAddr.sin_port = htons(atoi(argv[2]));


    //与服务器交互
    interact_ser(sfd, &serAddr);


    //关闭套接字,断开与服务器的连接
    close(sfd);
    /* printf("Connection closed!\n"); */
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值