3、网络编程——用UDP实现线程程序之间相互聊天

目录

一、用多线程实现线程两个程序相互聊天

1、服务器

2、服务器

二、 UDP实现多个程序与服务器相互聊天

1、要求

2、服务器

3、客户端

一、用多线程实现线程两个程序相互聊天

1、服务器

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>  // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>

int addr_len ;
int socket_fd = -1 ;            // 定义套接字描述符
struct sockaddr_in from_addr ;  // 对方的地址 (需要先的到客户端发来的消息我才能放消息出去)

void * send_msg (void * arg)
{
    int ret_val = -1 ; 
    char * msg_to_send = calloc(128 ,1) ;
    while (1)
    {
        fgets(msg_to_send , 128 , stdin);
        ret_val = sendto(socket_fd , msg_to_send , strlen(msg_to_send) , 0 , 
                    (struct sockaddr * )&from_addr, addr_len );
        if ( -1 == ret_val )
        {
            perror("send to error ");
            continue ;
        }
        else{
            printf("send succeed %d byte ! \n" , ret_val);
        }
    }
    
    return 0 ;
}


int main(int argc, char const *argv[])
{
    // 创建一个邮箱
    socket_fd =  socket(AF_INET, SOCK_DGRAM , 0 );
    if (-1 == socket_fd)
    {
        perror("socket error");
        return -1 ;
    }
    

    // 设置好IP+端口号等信息
    // struct sockaddr_in   // IPV4地址结构体
    // {
    //     u_short sin_family;// 地址族
    //     u_short sin_port;// 端口
    //     struct in_addr sin_addr;// IPV4 地址
    //     char sin_zero[8];
    // };

    struct sockaddr_in my_addr = {0} ;
    addr_len = sizeof(struct sockaddr_in);

    my_addr.sin_family = AF_INET ; // 网际协议 IPV4
    my_addr.sin_port = htons(65000); // 设置端口号为65000 并转换为网络字节序
    // my_addr.sin_addr.s_addr = inet_addr("192.168.102.2");  // 设置IP地址, 并转换为32位的网络地址
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置绑定当前系统所有的IP地址
    // (意思是任意一个地址有数据都可以接收) 

    // 把设置好的信息与信箱进行帮定
    int ret_val = bind(socket_fd , (struct sockaddr *)&my_addr,addr_len);
    if (-1 == ret_val)
    {
        perror("bind error");
        return -1 ;
    }
    else{
        printf("bind succeed!\n") ;
    }

    // 创建一个线程用于发送消息
    pthread_t tid ;
    ret_val = pthread_create(&tid , NULL , send_msg , NULL );
    if (ret_val != 0)
    {
        fprintf(stderr  , "create error :%s" , strerror(ret_val));
        close(socket_fd);
        return -1 ;
    }
    

    char * msg = calloc(128,1);
    

    // 坐等来信
    while(1)
    {
        bzero(msg , 128);
        printf("等待消息到达:\n");
        // ret_val = read(socket_fd , msg , 128); // 可以直接使用读取函数来读取数据,但是没办法得知数据来自哪里
        ret_val = recvfrom(socket_fd , msg , 128 , 0 , (struct sockaddr *)&from_addr, &addr_len );
        if (ret_val == -1 )
        {
            perror("recv error");
            continue ;
        }
        else{

            printf("recv succeed \n addr:%s \nport:%d \nmsg : %s \n" ,
                       inet_ntoa( from_addr.sin_addr ) ,  // 把二进制的网络字节序的IP地址转换点分十进制的字符串地址
                       ntohs(from_addr.sin_port) , // 把网络字节序的端口号转换位 本地字节序的短整型
                        msg ); 
        }

    }
    
    // 关闭套接字文件
    close(socket_fd);

    return 0;
}

2、服务器

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>  // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>

int addr_len ;
int socket_fd = -1 ;            // 定义套接字描述符
struct sockaddr_in from_addr ;  // 对方的地址 (需要先的到客户端发来的消息我才能放消息出去)
struct sockaddr_in dest_addr = {0} ;

void * send_msg (void * arg)
{

    // 配置 发送的目的地址
    addr_len = sizeof(struct sockaddr_in);

    dest_addr.sin_family = AF_INET ; // 网际协议 IPV4
    dest_addr.sin_port = htons(65000); // 设置端口号为65000 并转换为网络字节序
    dest_addr.sin_addr.s_addr = inet_addr("192.168.43.83");  // 设置服务器的IP地址

    int ret_val = -1 ; 
    char * msg_to_send = calloc(128 ,1) ;


    while (1)
    {
        fgets(msg_to_send , 128 , stdin);
        ret_val = sendto(socket_fd , msg_to_send , strlen(msg_to_send) , 0 , 
                    (struct sockaddr * )&dest_addr, addr_len );
        if ( -1 == ret_val )
        {
            perror("send to error ");
            continue ;
        }
        else{
            printf("send succeed %d byte ! \n" , ret_val);
        }
    }
    
    return 0 ;
}


int main(int argc, char const *argv[])
{
    // 创建一个邮箱
    socket_fd =  socket(AF_INET, SOCK_DGRAM , 0 );
    if (-1 == socket_fd)
    {
        perror("socket error");
        return -1 ;
    }
    

    // 设置好IP+端口号等信息
    // struct sockaddr_in   // IPV4地址结构体
    // {
    //     u_short sin_family;// 地址族
    //     u_short sin_port;// 端口
    //     struct in_addr sin_addr;// IPV4 地址
    //     char sin_zero[8];
    // };
   
    // 创建一个线程用于发送消息
    pthread_t tid ;
    int ret_val = pthread_create(&tid , NULL , send_msg , NULL );
    if (ret_val != 0)
    {
        fprintf(stderr  , "create error :%s" , strerror(ret_val));
        close(socket_fd);
        return -1 ;
    }
    
    char * msg = calloc(128,1);    

    // 坐等来信
    while(1)
    {
        bzero(msg , 128);
        printf("等待消息到达:\n");
        // ret_val = read(socket_fd , msg , 128); // 可以直接使用读取函数来读取数据,但是没办法得知数据来自哪里
        ret_val = recvfrom(socket_fd , msg , 128 , 0 , (struct sockaddr *)&dest_addr, &addr_len );
        if (ret_val == -1 )
        {
            perror("recv error");
            continue ;
        }
        else{

            printf("recv succeed \n addr:%s \nport:%d \nmsg : %s \n" ,
                       inet_ntoa( dest_addr.sin_addr ) ,  // 把二进制的网络字节序的IP地址转换点分十进制的字符串地址
                       ntohs(dest_addr.sin_port) , // 把网络字节序的端口号转换位 本地字节序的短整型
                        msg ); 
        }
    }  
    // 关闭套接字文件
    close(socket_fd);

    return 0;
}

二、 UDP实现多个程序与服务器相互聊天

1、要求

a、假设有多个客户端连接到同一个服务器,服务器可以保存多个连接的客服端的信息(对方IP以及端口号)

b、可以实现给指定某个客户端回消息(列出当前在线列表——选择一个用户——发送消息)

c、尝试自己实现群发消息(5个客户端在线,其中一个需要群发消息,服务器帮助该客户进行群发)

2、服务器

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>  // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>


int addr_len ;
int socket_fd = -1 ;            // 定义套接字描述符
struct sockaddr_in from_addr ;  // 对方的地址 (需要先的到客户端发来的消息我才能放消息出去)
struct sockaddr_in arr_addr[10] ; // 全局变量, 属于静态变量, 未初始化时则自动清空为0 
int num = 0 ; // 客户端信息数组下标

void * send_msg (void * arg)
{
    int ret_val = -1 ; 
    char * msg_to_send = calloc(128 ,1) ;
    while (1)
    {
        
        printf("请输入一个 # 显示当前在线列表:\n");
        while( getchar() != '#' );

        // 显示当前在线列表
        printf("**************************************\n");
        for (int i = 0; i < num ; i++)
        {
            printf("arr[%d]:IP:%s\tport:%d\t\n" , i ,
                    inet_ntoa( arr_addr[i].sin_addr ) ,  // 把二进制的网络字节序的IP地址转换点分十进制的字符串地址
                    ntohs(arr_addr[i].sin_port)  );// 把网络字节序的端口号转换位 本地字节序的短整型
        }
        printf("**************************************\n");
        printf("*********请选择一个客户端进行回信*********\n");

        // 用户输入数组的下标
        int i = 0 ;
        scanf("%d" , &i) ;
        while ( getchar()!= '\n');
     
        printf("*********请输入发送的消息内容*********\n");
        fgets(msg_to_send , 128 , stdin);
        ret_val = sendto(socket_fd , msg_to_send , strlen(msg_to_send) , 0 , 
                    (struct sockaddr * )&arr_addr[i], addr_len );
        if ( -1 == ret_val )
        {
            perror("send to error ");
            continue ;
        }
        else{
            printf("send succeed %d byte ! \n" , ret_val);
        }
    }
    
    return 0 ;
}


bool if_access( struct sockaddr_in new_addr )
{
    for (int i = 0; i < num ; i++)
    {
        //  如果内存内容一致则返回 0 
        if(memcmp(&arr_addr[i] , &new_addr , sizeof(struct sockaddr_in)))
        {
            continue ;
        }
        else
        {
            return true ; // 如果返回值为0 表示已经存在, 因此直接返回true 
        }
    }

    return false ;
}


int main(int argc, char const *argv[])
{
    // 创建一个邮箱
    socket_fd =  socket(AF_INET, SOCK_DGRAM , 0 );
    if (-1 == socket_fd)
    {
        perror("socket error");
        return -1 ;
    }
    

    // 设置好IP+端口号等信息
    // struct sockaddr_in   // IPV4地址结构体
    // {
    //     u_short sin_family;// 地址族
    //     u_short sin_port;// 端口
    //     struct in_addr sin_addr;// IPV4 地址
    //     char sin_zero[8];
    // };

    struct sockaddr_in my_addr = {0} ;
    addr_len = sizeof(struct sockaddr_in);

    my_addr.sin_family = AF_INET ; // 网际协议 IPV4
    my_addr.sin_port = htons(65000); // 设置端口号为65000 并转换为网络字节序
    // my_addr.sin_addr.s_addr = inet_addr("192.168.102.2");  // 设置IP地址, 并转换为32位的网络地址
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置绑定当前系统所有的IP地址
    // (意思是任意一个地址有数据都可以接收) 

    // 把设置好的信息与信箱进行帮定
    int ret_val = bind(socket_fd , (struct sockaddr *)&my_addr,addr_len);
    if (-1 == ret_val)
    {
        perror("bind error");
        return -1 ;
    }
    else{
        printf("bind succeed!\n") ;
    }

    // 创建一个线程用于发送消息
    pthread_t tid ;
    ret_val = pthread_create(&tid , NULL , send_msg , NULL );
    if (ret_val != 0)
    {
        fprintf(stderr  , "create error :%s" , strerror(ret_val));
        close(socket_fd);
        return -1 ;
    }
    

    char * msg = calloc(128,1);
    

    // 坐等来信
    while(1)
    {
        bzero(msg , 128);
        printf("等待消息到达:\n");
        // ret_val = read(socket_fd , msg , 128); // 可以直接使用读取函数来读取数据,但是没办法得知数据来自哪里
        ret_val = recvfrom(socket_fd , msg , 128 , 0 , (struct sockaddr *)&from_addr, &addr_len );
        if (ret_val == -1 )
        {
            perror("recv error");
            continue ;
        }
        else{

            printf("recv succeed \n addr:%s \nport:%d \nmsg : %s \n" ,
                       inet_ntoa( from_addr.sin_addr ) ,  // 把二进制的网络字节序的IP地址转换点分十进制的字符串地址
                       ntohs(from_addr.sin_port) , // 把网络字节序的端口号转换位 本地字节序的短整型
                        msg ); 
            
            if(!if_access(from_addr)  && num < 9 )
            {
                arr_addr[num] = from_addr ; // 把当前的客户端的保存到数组中
                num ++ ; // 数组下标+1 
            }

            for (int i = 0; i < num ; i++)
            {
                printf("arr[%d]:IP:%s\tport:%d\t\n" , i ,
                        inet_ntoa( arr_addr[i].sin_addr ) ,  // 把二进制的网络字节序的IP地址转换点分十进制的字符串地址
                        ntohs(arr_addr[i].sin_port)  );// 把网络字节序的端口号转换位 本地字节序的短整型
            }            
        }
    }    
    // 关闭套接字文件
    close(socket_fd);

    return 0;
}

3、客户端

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>  // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>

int addr_len ;
int socket_fd = -1 ;            // 定义套接字描述符
struct sockaddr_in from_addr ;  // 对方的地址 (需要先的到客户端发来的消息我才能放消息出去)
struct sockaddr_in dest_addr = {0} ;

void * send_msg (void * arg)
{

    // 配置 发送的目的地址
    addr_len = sizeof(struct sockaddr_in);

    dest_addr.sin_family = AF_INET ; // 网际协议 IPV4
    dest_addr.sin_port = htons(65000); // 设置端口号为65000 并转换为网络字节序
    dest_addr.sin_addr.s_addr = inet_addr("192.168.102.2");  // 设置服务器的IP地址

    int ret_val = -1 ; 
    char * msg_to_send = calloc(128 ,1) ;

    while (1)
    {
        fgets(msg_to_send , 128 , stdin);
        ret_val = sendto(socket_fd , msg_to_send , strlen(msg_to_send) , 0 , 
                    (struct sockaddr * )&dest_addr, addr_len );
        if ( -1 == ret_val )
        {
            perror("send to error ");
            continue ;
        }
        else{
            printf("send succeed %d byte ! \n" , ret_val);
        }
    }    
    return 0 ;
}


int main(int argc, char const *argv[])
{
    // 创建一个邮箱
    socket_fd =  socket(AF_INET, SOCK_DGRAM , 0 );
    if (-1 == socket_fd)
    {
        perror("socket error");
        return -1 ;
    }
    
    // 设置好IP+端口号等信息
    // struct sockaddr_in   // IPV4地址结构体
    // {
    //     u_short sin_family;// 地址族
    //     u_short sin_port;// 端口
    //     struct in_addr sin_addr;// IPV4 地址
    //     char sin_zero[8];
    // };
   
    // 创建一个线程用于发送消息
    pthread_t tid ;
    int ret_val = pthread_create(&tid , NULL , send_msg , NULL );
    if (ret_val != 0)
    {
        fprintf(stderr  , "create error :%s" , strerror(ret_val));
        close(socket_fd);
        return -1 ;
    }    

    char * msg = calloc(128,1);    

    // 坐等来信
    while(1)
    {
        bzero(msg , 128);
        printf("等待消息到达:\n");
        // ret_val = read(socket_fd , msg , 128); // 可以直接使用读取函数来读取数据,但是没办法得知数据来自哪里
        ret_val = recvfrom(socket_fd , msg , 128 , 0 , (struct sockaddr *)&dest_addr, &addr_len );
        if (ret_val == -1 )
        {
            perror("recv error");
            continue ;
        }
        else{

            printf("recv succeed \n addr:%s \nport:%d \nmsg : %s \n" ,
                       inet_ntoa( dest_addr.sin_addr ) ,  // 把二进制的网络字节序的IP地址转换点分十进制的字符串地址
                       ntohs(dest_addr.sin_port) , // 把网络字节序的端口号转换位 本地字节序的短整型
                        msg ); 
        }
    }    
    // 关闭套接字文件
    close(socket_fd);

    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值