3. ネットワーク プログラミング - UDP を使用してスレッド プログラム間のチャットを実現する

目次

1. マルチスレッドを使用してスレッド化を実装する 2 つのプログラムが互いにチャットします

1. サーバー

2.サーバー

2. UDP により、複数のプログラムがサーバーとチャットできるようになります

1. 要件

2.サーバー

3. クライアント

1. マルチスレッドを使用してスレッド化を実装する 2 つのプログラムが互いにチャットします

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;
}

2. UDP により、複数のプログラムがサーバーとチャットできるようになります

1. 要件

a. 同一サーバーに複数のクライアントが接続されていると仮定すると、サーバーは接続された複数の接客端末の情報(相手のIPとポート番号)を保存することができます。

b. 指定したクライアントにメッセージを返すことができます (現在のオンライン リストを一覧表示 - ユーザーを選択 - メッセージを送信)

c. 自分でグループ メッセージ送信を実装してみてください (5 つのクライアントがオンラインで、そのうちの 1 つはグループ メッセージを送信する必要があり、サーバーはクライアントがグループ メッセージを送信するのを支援します)。

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;
}

 

おすすめ

転載: blog.csdn.net/weixin_45981798/article/details/129903525