19级LINUX 网络编程-由网络传输文件

// FileDownloadThroughServer.cpp : Defines the entry point for the console application.
/*----------------------------------------------------------------------------------------------------------
功能:
    建立C/S结构的文件下载系统。每个客户端可以提供不定数目的文件用于共享,也可以下载别人共享出来的文件。
    文件存在于各个客户端上,而不在服务器上。
    要允许一个客户端同时下载多个文件,也要允许同时被多个别的客户端下载。
思路:
    1.服务器至少应该开放3个TCP端口供客户端连接。
        (1)第一个用来接收客户端发来的共享文件名以及向客户端发送别人想要下载他的文件的通知,不妨把这个端口叫做通知端口。
        (2)第二个用来向客户端每隔10秒发送最新共享文件列表,把它叫做刷新端口。
        (3)第三个用来提供文件传输的中转服务,叫做传输端口。
    2.本系统是围绕着共享文件而工作的,为了方便起见,应该把一个共享文件的全部信息封装成“共享文件结构体”,成员变量
      应该包括文件名、文件所在客户端的地址、服务器对应于这个客户端的通知套接字。
    3.通知端口和传输端口应该设计各自的长度固定的头部(可设计成结构体),叫做“通知头部”和“传输头部”。
      其中“传输头部”又有下载和上传两种。这样这两个端口的recv()函数就可以指定固定的接收长度了。
    4.服务器和客户端都要大量使用多线程。
    5.服务器可以用c++stl的multimap来保存共享文件列表,multimap的第一个字段是服务器上面对应于文件所在客户端的通知套接字,
      第二字段是共享文件结构体。这样设计一来是为了在客户端退出的时候能最方便地从multimap中删除这个客户端的所有共享文件信息,
      二来是为了在用户指定要下载哪个文件时不用通过搜索就知道该向哪个客户端发送通知。由于这个数据结构在客户端登录和
      退出时都要被修改,所以必须有并发控制。
    6.客户端的数据结构可以简单地用c++stl的vector来保存服务器发来的最新共享文件列表。vector中每个元素都是
      共享文件结构体。为防止用户选择文件时正好在刷新共享文件列表,也需要有并发控制。但应知道,这里的并发控制可以
      防止程序的运行出错,但并不能杜绝下载到非指定文件的可能性。
    7.系统的工作流程大致如下:服务器在3个端口上等待,客户端首先连接到通知端口,在这条连接上发送自己的共享文件名,
      然后同样在这条连接上准备接收“通知头部”。接着客户端每隔10秒连接到刷新端口并接收最新共享文件列表。
      客户端还需要用一个线程来准备读入用户的键盘输入,当用户有输入时,客户端连接到传输端口并发送一个表示下载的
      “传输头部”(其中最重要的就是一个共享文件结构体),然后在这条连接上准备接收文件数据。服务器收到这个
      “传输头部”后从中得到文件所在客户端的套接字,并构造一个“通知头部”(包括想下载文件的客户端的套接字和想下载的
      文件名)发送到这个套接字上。被下载方在通知端口上收到“通知头部”后,也连接到传输端口,先发送一个表示上传
      的“传输头部”(其中最重要的就是下载方的套接字),以便让服务器知道该把文件转发给谁,接着就可以开始上传文件了。
考查:
    1.结合源代码和注释,搞懂这个系统的结构和流程,掌握套接字编程和多线程编程的相关技术,准备回答问题。
    
    2.已知:getsockname()函数可以获得套接字所使用的地址。自行查阅这个函数的原型,并在源程序基础上添加代码,
      使用户试图下载自己的文件时能阻止并提示“这个文件是你自己的”。
      
    3.按照本源程序的方案,分析在大负荷的情况下系统会有什么表现?应怎么改进?动手改源程序或者准备回答问题。
    
    4.本源程序除了未进行差错处理和用户指定下载一个前10秒之内退出系统的客户端上的文件时会得不到正确提示
      这些小问题外,故意留有一个真正的bug,此bug如果被激发,不仅会导致系统在一切正常并且被下载方没有退出时
      也会下载不成功,而且服务器有不确定的动作。试进行debug。
----------------------------------------------------------------------------------------------------------*/
#include "stdafx.h"
#include "stdio.h"
#include <map>
#include <vector>
#include "Winsock2.h"
#pragma comment(lib, "ws2_32.lib")
//宏定义
//---------------------------------------------------------------------------------------------------------
#define SERVER_NOTIFY_PORT        1025   //通知端口
#define SERVER_REFRESH_PORT        1026    //刷新端口
#define SERVER_TRANSFER_PORT    1027    //传输端口
#define COMMAND_DOWNLOAD        0        //“传输头部”中表示下载的命令
#define COMMAND_UPLOAD            1        //“传输头部”中表示上传的命令
//---------------------------------------------------------------------------------------------------------
//结构体定义
//---------------------------------------------------------------------------------------------------------
#pragma pack(4)  //设置结构体按照4字节对齐
struct SHARED_FILE                        //共享文件结构体,封装一个共享文件的所有信息。服务器端和客户端都要用到
{
    char                filename[100];    //文件名
    struct sockaddr_in    client_addr;    //文件所在的客户端的网络地址
    SOCKET                notify_sock;    //服务器上面对应于这个客户端的通知套接字
};
struct TRANSFER_HEADER                    //“传输头部”,由客户端发往服务器,准备开始一次下载或上传
{
    int cmd;                            //命令,指明本次传输是为了下载还是上传,0-下载,1-上传
    union  
    {
        SHARED_FILE    shared_file;        //如果是下载,需要告诉服务器想下载的共享文件结构体
        SOCKET        sock;                //如果是上传,需要告诉服务器想下载这个文件的客户端的套接字
    };
};
struct NOTIFY_HEADER                    //“通知头部”,由服务器发往客户端,通知他有人想下载他的某个文件
{
    SOCKET    sock;                        //告诉客户端是谁想下载他的文件
    char    filename[100];                //告诉客户端想下载他的哪个文件
};
#pragma pack()  //取消结构体的字节对齐
//---------------------------------------------------------------------------------------------------------
 
//全局变量
//---------------------------------------------------------------------------------------------------------
//服务器端的核心数据结构(多映射),用来保存所有共享文件的信息。映射的第一个字段是服务器上面对应于客户端的通知套接字,第二个字段是客户端的一个共享文件结构体
std::multimap<SOCKET, SHARED_FILE>        g_shared_files;            
//客户端的两个核心数据结构(数组),第一个用来暂存服务器发来的最新共享文件列表,第二个是第一个的拷贝
std::vector<SHARED_FILE>                g_files_list;
std::vector<SHARED_FILE>                g_files_list2;
unsigned int                            g_serverip;                //服务器的IP,网络字节序表示    
HANDLE                                    g_server_semaphore;        //服务器使用的信号量,用于控制对g_shared_files的并发访问
HANDLE                                    g_client_semaphore;        //客户端使用的信号量,用于控制对g_files_list2的并发访问
//---------------------------------------------------------------------------------------------------------
 
int mysend(SOCKET sock, char *buf, int len, int flags)
{
    int sent = 0, remain = len;
    while(remain > 0)
    {
        int n = send(sock, buf+sent, remain, flags);
        if(n == -1)  //出错的最大可能是对方关闭了套接字
            break;
        remain -= n;
        sent += n;
    }
    return sent;
}
 
int myrecv(SOCKET sock, char *buf, int len, int flags)
{
    int received = 0, remain = len;
    while(remain > 0)
    {
        int n = recv(sock, buf+received, remain, flags);
        if(n == 0 || n == -1)  //0是对方调用closesocket(),-1是对方直接退出
            break;
        remain -= n;
        received += n;
    }
    return received;
}
 
//-------------------------------------------------------------------------------------------------------
//功能:服务器用于判断某个客户端是否下线
//参数:服务器上对应于某个客户端的通知套接字
//原理:由于通知端口的TCP连接是一直保持的,所以服务器在这个端口上调用recv()将会一直阻塞,直到客户端下线
//创建者:server_notify_thread()
DWORD WINAPI server_quit_thread(LPVOID lpParam)
{
    SOCKET comm_sock = (SOCKET)lpParam;
 
    while(1)
    {
        char c;
        int ret = recv(comm_sock, &c, 1, 0);
        if(ret == 0 || ret == -1)
            break;
    }
    printf("有一个客户端退出了\n");
 
    //删除g_shared_files中的信息
    WaitForSingleObject(g_server_semaphore, INFINITE);  //获取信号量
    g_shared_files.erase(comm_sock);
    ReleaseSemaphore(g_server_semaphore, 1, NULL);  //释放信号量
 
    return 0;
}
//-------------------------------------------------------------------------------------------------------
 
//-------------------------------------------------------------------------------------------------------
//功能:服务器把最新共享文件列表发给客户端
//参数:服务器在刷新端口上的监听套接字
//原理:服务器在刷新端口上等待连接,建立连接后通过通信套接字把最新共享文件列表发给客户端
//创建者:main()
DWORD WINAPI server_refresh_thread(LPVOID lpParam) 
{ 
    SOCKET server_refresh_sock = (SOCKET)lpParam;
 
    sockaddr_in client_addr;
    int size = sizeof(client_addr);
    SOCKET comm_sock = accept(server_refresh_sock, (struct sockaddr *)&client_addr, &size);
    //创建线程,用来等待下一个连接请求
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)server_refresh_thread, (void *)server_refresh_sock, 0, NULL);
 
    //向客户端发送共享文件列表。为防止遍历的过程中有客户端登录或退出,要进行并发控制
    WaitForSingleObject(g_server_semaphore, INFINITE);  //获取信号量
    std::multimap<SOCKET, SHARED_FILE>::const_iterator it;
    for(it=g_shared_files.begin(); it != g_shared_files.end(); it++)
        mysend(comm_sock, (char *)&(it->second), sizeof(SHARED_FILE), 0);
    ReleaseSemaphore(g_server_semaphore, 1, NULL);  //释放信号量
 
    //最后发一个空文件名告诉客户端已发完
    SHARED_FILE sf;
    sf.filename[0] = '\0';
    mysend(comm_sock, (char *)&sf, sizeof(SHARED_FILE), 0);
 
    closesocket(comm_sock);
    
    return 0;
}
//-------------------------------------------------------------------------------------------------------
 
//-------------------------------------------------------------------------------------------------------
//功能:服务器接受一个客户端上线
//参数:服务器在通知端口上的监听套接字
//原理:服务器在通知端口上等待连接,建立连接后接收客户端发来的共享文件名,并构造结构体存入多映射
//创建者:main()
DWORD WINAPI server_notify_thread(LPVOID lpParam)
{
    SOCKET server_notify_sock = (SOCKET)lpParam;
    
    sockaddr_in client_addr;
    int size = sizeof(client_addr);
    SOCKET comm_sock = accept(server_notify_sock, (struct sockaddr *)&client_addr, &size);
    if(comm_sock == INVALID_SOCKET)
    {
        printf("accept() error!\n");
        exit(0);
    }
    printf("有一个客户端上线了\n");
     
    //创建线程,用来等待下一个连接请求
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)server_notify_thread, (void *)server_notify_sock, 0, NULL);
    //接收客户端发来的共享文件名,构造成共享文件结构体并加入g_shared_files保存
    char buf[100];
    SHARED_FILE sf;
    sf.client_addr    = client_addr;
    sf.notify_sock    = comm_sock;
    while(1)
    {
        myrecv(comm_sock, buf, 100, 0);
        if(buf[0] == '\0')  //空文件名,说明客户端已发完所有共享文件名
            break;
        strcpy(sf.filename, buf);
 
        WaitForSingleObject(g_server_semaphore, INFINITE);  //获取信号量  p操作 
        g_shared_files.insert(std::make_pair(comm_sock, sf));
        ReleaseSemaphore(g_server_semaphore, 1, NULL);  //释放信号量     v操作 
    }
    //创建线程,用来接收这个客户端退出的通知,以便及时从g_shared_files中删除信息
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)server_quit_thread, (void *)comm_sock, 0, NULL);
    //注意:这个线程虽然退出但是这条连接并未关闭,它将一直维持,直到客户端下线
    return 0;
}
//-------------------------------------------------------------------------------------------------------
 
//-------------------------------------------------------------------------------------------------------
//功能:服务器为文件传输做中转服务
//参数:服务器在传输端口上的监听套接字
//原理:服务器在传输端口上等待连接,建立连接后接收一个“传输头部”,并根据下载还是上传做相应中转
//创建者:main()
DWORD WINAPI server_transfer_thread(LPVOID lpParam)
{
    SOCKET server_transfer_sock = (SOCKET)lpParam;
 
    sockaddr_in client_addr;
    int size = sizeof(client_addr);
    SOCKET comm_sock = accept(server_transfer_sock, (struct sockaddr *)&client_addr, &size);
 
    //创建线程,用来等待下一个连接请求
    CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)server_transfer_thread, (void *)server_transfer_sock, 0, NULL);
 
    //接收一个“传输头部”
    TRANSFER_HEADER th;
    myrecv(comm_sock, (char *)&th, sizeof(TRANSFER_HEADER), 0);
    if(th.cmd == COMMAND_DOWNLOAD)  //如果这个连接是为了下载文件
    {
        //构造一个“通知头部”准备发给被下载方
        NOTIFY_HEADER nh;
        nh.sock = comm_sock;  //将下载方的套接字告诉被下载方,以便将来他上传文件时回传这个套接字
        strcpy(nh.filename, th.shared_file.filename);  //将文件名告诉被下载方
 
        //从“传输头部”的共享文件结构体中取出被下载方对应的通知套接字
        SOCKET client_notify_sock = th.shared_file.notify_sock;
 
        //向这个通知套接字发送“通知头部”
        int ret = mysend(client_notify_sock, (char *)&nh, sizeof(NOTIFY_HEADER), 0);
        if(ret != sizeof(NOTIFY_HEADER))  //说明被下载方已退出
            closesocket(comm_sock);
    }
    else if(th.cmd == COMMAND_UPLOAD)  //如果这个连接是为了上传文件
    {
        //从“传输头部”中取出下载方对应的套接字
        SOCKET download_client_sock = th.sock;
 
        //循环接收数据并转发给下载方
        char buf[1024];
        while(1)
        {
            int i = recv(comm_sock, buf, 1024, 0);  //不需要调用myrecv(),因为不强求收满1024个字节
            if(i > 0)
                mysend(download_client_sock, buf, i, 0);  //但转发一定要保证发完i个字节
            else
            {
                closesocket(download_client_sock);
                break;
            }
        }
    }
 
    return 0;
}
//-------------------------------------------------------------------------------------------------------
 
//-------------------------------------------------------------------------------------------------------
//功能:客户端每隔10秒向服务器请求最新共享文件列表
//参数:NULL
//原理:客户端每隔10秒创建一个套接字,连接到服务器的刷新端口,然后循环接收数据,每次接收一个共享文件结构体,接收完成后断开连接并进行屏幕显示
//创建者:main()
DWORD WINAPI client_refresh_thread(LPVOID lpParam)
{
    sockaddr_in server_addr;
    server_addr.sin_family        = AF_INET;
    server_addr.sin_addr.s_addr = g_serverip;
    server_addr.sin_port        = htons(SERVER_REFRESH_PORT);
        
    while(1)
    {
        //创建流套接字并连接到服务器的刷新端口
        SOCKET refresh_sock = socket(AF_INET, SOCK_STREAM, 0);
        connect(refresh_sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
        
        //接收最新共享文件列表
        while(1)
        {
            SHARED_FILE sf;
            myrecv(refresh_sock, (char *)&sf, sizeof(SHARED_FILE), 0);  //接收一个SHARED_FILE结构体
            if(sf.filename[0] != '\0')  //如果不是空文件名
                g_files_list.push_back(sf);  //加入g_files_list保存
            else  //空文件名,表示服务器本次发送共享文件列表已经完毕
                break;
        }
 
        closesocket(refresh_sock);
        
        //为防止此时用户正在选择g_files_list2中的某个文件,进行并发控制
        WaitForSingleObject(g_client_semaphore, INFINITE);  //获取信号量
        g_files_list2.clear();
        g_files_list2 = g_files_list;
        ReleaseSemaphore(g_client_semaphore, 1, NULL);  //释放信号量
 
        g_files_list.clear();  //清空g_files_list,准备下一次接收共享文件列表
 
        printf("最新共享文件列表:\n");
        std::vector<SHARED_FILE>::const_iterator it;
        int i = 1;
        for(it=g_files_list2.begin(); it != g_files_list2.end(); it++,i++)
        {
            printf("%d - %s:%d上的%s", i, inet_ntoa(it->client_addr.sin_addr), ntohs(it->client_addr.sin_port), it->filename);
            printf("\n");
        }
        printf("请输入文件的序号进行下载(0-退出):\n");
        Sleep(10000);
    }
 
    return 0;
}
//-------------------------------------------------------------------------------------------------------
 
//-------------------------------------------------------------------------------------------------------
//功能:客户端进行文件上传
//参数:指向“通知头部”的指针
//原理:客户端从“通知头部”中得知是谁想下载他的文件以及想下载哪个文件,然后构造出表示上传的“传输头部”,连接到服务器的传输端口,首先发送“传输头部”然后发送文件
//创建者:client_notify_thread()
DWORD WINAPI client_upload_thread(LPVOID lpParam)
{
    NOTIFY_HEADER *pnh = (NOTIFY_HEADER *)lpParam;
    
    //创建套接字并连接到服务器的SERVER_TRANSFER_PORT端口
    SOCKET client_upload_sock = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in server_addr;
    server_addr.sin_family        = AF_INET;
    server_addr.sin_addr.s_addr = g_serverip;
    server_addr.sin_port        = htons(SERVER_TRANSFER_PORT);
    connect(client_upload_sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
 
    //构造一个表示上传的“传输头部”并发送给服务器
    TRANSFER_HEADER th;
    th.cmd    = COMMAND_UPLOAD;  //指明这个连接是为了上传
    th.sock    = pnh->sock;  //指明想下载这个文件的客户端套接字
    mysend(client_upload_sock, (char *)&th, sizeof(TRANSFER_HEADER), 0);
 
    FILE *fp = fopen(pnh->filename, "rb");
    if(fp != NULL)
        printf("开始上传文件%s\n", pnh->filename);
    else
    {
        printf("找不到文件%s,上传失败\n", pnh->filename);
        closesocket(client_upload_sock);
        delete pnh;
        return 0;
    }
    
    //循环读取文件数据并上传
    char buf[1024];
    while(1)
    {
        int i = fread(buf, 1, 1024, fp);
        mysend(client_upload_sock, buf, i, 0);
        if(i < 1024)  //fread()没读满1024个字节表示是最后一次读文件了
            break;
    }
    fclose(fp);
    closesocket(client_upload_sock);
    printf("文件%s上传完成\n", pnh->filename);
 
    delete pnh;
    return 0;
}
//-------------------------------------------------------------------------------------------------------
 
//-------------------------------------------------------------------------------------------------------
//功能:客户端响应服务器发来的通知
//参数:客户端已经同服务器的通知端口建立连接的通知套接字
//原理:客户端在已经同服务器的通知端口建立了连接的通知套接字上等待接收“通知头部”,收到后以这个“通知头部”为参数启动client_upload_thread线程进行文件的上传
//创建者:main()
DWORD WINAPI client_notify_thread(LPVOID lpParam)
{
    SOCKET client_notify_sock = (SOCKET)lpParam;
 
    while(1)
    {
        //接收一个“通知头部”
        NOTIFY_HEADER nh;
        myrecv(client_notify_sock, (char *)&nh, sizeof(NOTIFY_HEADER), 0);
        printf("收到一个下载%s的通知\n", nh.filename);
        
        NOTIFY_HEADER *pnh = new NOTIFY_HEADER;  //不能直接把nh的指针传给新线程,因为nh在本线程的栈中,随时可能被覆盖
        *pnh = nh;
        //创建线程,用来上传文件
        CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)client_upload_thread, (void *)pnh, 0, NULL);
    }
    
    return 0;
}
//-------------------------------------------------------------------------------------------------------
 
//-------------------------------------------------------------------------------------------------------
//功能:客户端进行指定文件的下载
//参数:指向想下载的共享文件结构体的指针
//原理:客户端根据想下载的共享文件结构体构造出表示下载的“传输头部”,连接到服务器的传输端口并发送这个“传输头部”,然后循环等待接收文件数据并存盘
//创建者:client_userinput_thread()
DWORD WINAPI client_download_thread(LPVOID lpParam)
{
    SHARED_FILE *psf = (SHARED_FILE *)lpParam;
 
    //创建套接字并连接到服务器的SERVER_TRANSFER_PORT端口
    SOCKET client_download_sock = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in server_addr;
    server_addr.sin_family        = AF_INET;
    server_addr.sin_addr.s_addr = g_serverip;
    server_addr.sin_port        = htons(SERVER_TRANSFER_PORT);
    connect(client_download_sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
 
    //构造一个表示下载的“传输头部”并发送给服务器
    TRANSFER_HEADER th;
    th.cmd            = COMMAND_DOWNLOAD;  //指明这个连接是为了下载
    th.shared_file    = *psf;  //指明想下载哪一个文件
    delete psf;
    mysend(client_download_sock, (char *)&th, sizeof(TRANSFER_HEADER), 0);
    
    printf("开始下载%s:%d的%s\n", inet_ntoa(th.shared_file.client_addr.sin_addr), ntohs(th.shared_file.client_addr.sin_port), th.shared_file.filename);
    
    FILE *fp = fopen(th.shared_file.filename, "wb");
    //循环接收数据并写入文件
    char buf[1024];
    while(1)
    {
        int i = recv(client_download_sock, buf, 1024, 0);  //不需要调用myrecv()
        if(i > 0)
            fwrite(buf, 1, i, fp);
        else
            break;
    }
    fclose(fp);
    closesocket(client_download_sock);
    printf("%s:%d的%s下载完毕\n", inet_ntoa(th.shared_file.client_addr.sin_addr), ntohs(th.shared_file.client_addr.sin_port), th.shared_file.filename);
    
    return 0;
}
//-------------------------------------------------------------------------------------------------------
 
//-------------------------------------------------------------------------------------------------------
//功能:客户端接收并处理用户的键盘输入
//参数:NULL
//原理:客户端等待用户的键盘输入,收到输入后从共享文件结构体数组中取出相应的结构体,并以此为参数启动client_download_thread线程进行文件的下载
//创建者:main()
DWORD WINAPI client_userinput_thread(LPVOID lpParam)
{
    int number;
    while(1)
    {
        scanf("%d", &number);
        //客户端退出的方式是用户按下0
        if(number == 0)
        {
            CloseHandle(g_client_semaphore);
            exit(0);
        }
 
        //为防止此时正在刷新g_files_list2,进行并发控制
        WaitForSingleObject(g_client_semaphore, INFINITE);  //获取信号量
        if(number > g_files_list2.size())
            printf("请输入正确的序号\n");
        else
        {
            SHARED_FILE *psf = new SHARED_FILE;
            *psf = g_files_list2[number - 1];
            sockaddr_in clientaddr;
            int size=sizeof(clientaddr);
            getsockname((SOCKET)lpParam,(struct sockaddr *)&clientaddr,&size);
            printf("ip=%s,p_ip=%s\n",inet_ntoa(clientaddr.sin_addr), inet_ntoa(psf->client_addr.sin_addr));
            printf("port=%d,p_port=%d\n",ntohs(clientaddr.sin_port),ntohs(psf->client_addr.sin_port));
            if( strcmp( inet_ntoa(psf->client_addr.sin_addr),inet_ntoa(clientaddr.sin_addr))==0&&ntohs(psf->client_addr.sin_port)==ntohs(clientaddr.sin_port))
                printf("这个文件是你自己的.\n");

else {
                CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)client_download_thread, (void *)psf, 0, NULL);        
            }
        }
        ReleaseSemaphore(g_client_semaphore, 1, NULL);  //释放信号量
    }
    
    return 0;
}
//-------------------------------------------------------------------------------------------------------
/*
argc表示文件运行时以空格为间隔的所有参数个数.
argv[]数组中依次存放所有字串.
*/
int main(int argc, char* argv[])
{
    if(argc == 1)
    {
        printf(    "使用方法 : \nfilename -server\n或\nfilename -client 服务器IP [共享文件名1] ...\n");
        return 0;
    }
 
    //下面4行进行Windows网络环境的初始化。Linux中不需要
    WORD wVersionRequested;
    WSADATA wsaData;
    wVersionRequested = MAKEWORD(2, 2);
    WSAStartup(wVersionRequested, &wsaData);
     
    if(argc == 2 && strcmp(argv[1], "-server") == 0)   //启动服务端 
    {
        //创建信号量,初值为1,用来控制对g_shared_files的并发修改
        g_server_semaphore = CreateSemaphore(NULL, 1, 1, NULL);
        
        //创建通知端口的监听套接字
        SOCKET server_notify_sock = socket(AF_INET, SOCK_STREAM, 0);
        if(server_notify_sock == INVALID_SOCKET)  //Linux下失败返回-1
        {
            printf("socket() error!\n");
            exit(0);
        }
        sockaddr_in server_addr;    // sockaddr_in -> 网络环境下套接字的地址形式 
        server_addr.sin_family        = AF_INET;
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        server_addr.sin_port        = htons(SERVER_NOTIFY_PORT);
        if(-1 == bind(server_notify_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)))  //bind和listen函数 创建失败 都是返回-1; 创建成功 都是返回0; 
        {
            printf("bind() error!\n");
            exit(0);
        }
        if(-1 == listen(server_notify_sock, 5))
        {
            printf("listen() error!\n");
            exit(0);
        }
        //创建线程,用来在通知端口上接受连接请求
        CreateThread(NULL, 0, server_notify_thread, (void *)server_notify_sock, 0, NULL);
 
        //创建刷新端口的监听套接字
        SOCKET server_refresh_sock = socket(AF_INET, SOCK_STREAM, 0);
        server_addr.sin_port        = htons(SERVER_REFRESH_PORT);
        bind(server_refresh_sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
        listen(server_refresh_sock, 5);
        //创建线程,用来在刷新端口上接受连接请求
        CreateThread(NULL, 0, server_refresh_thread, (void *)server_refresh_sock, 0, NULL);
 
        //创建传输端口的监听套接字
        SOCKET server_transfer_sock = socket(AF_INET, SOCK_STREAM, 0);
        server_addr.sin_port        = htons(SERVER_TRANSFER_PORT);
        bind(server_transfer_sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
        listen(server_transfer_sock, 5);
        //创建线程,用来在传输端口上接受连接请求
        CreateThread(NULL, 0, server_transfer_thread, (void *)server_transfer_sock, 0, NULL);
 
        printf("服务器在3个端口开始监听连接请求\n");
 
        //消息循环,目的是不让主线程退出。Linux中换成while(1) pause();
        MSG msg;
        while(GetMessage(&msg, 0, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    } 
    else if(argc >= 3 && strcmp(argv[1], "-client") == 0)   //启动客户端 
    {
        //创建信号量,初值为1,用来控制对g_files_list2的并发读写
        g_client_semaphore = CreateSemaphore(NULL, 1, 1, NULL);
        
        g_serverip = inet_addr(argv[2]);
        
        //创建用于连接到服务器通知端口的套接字
        SOCKET client_notify_sock = socket(AF_INET, SOCK_STREAM, 0);
        sockaddr_in server_addr;
        server_addr.sin_family        = AF_INET;
        server_addr.sin_addr.s_addr = g_serverip;
        server_addr.sin_port        = htons(SERVER_NOTIFY_PORT);
        connect(client_notify_sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
        printf("客户端请求连接成功\n");
        //发送共享文件名,文件名长度与服务器约定为100字节
        char buf[100];
        int i;
        for(i=3; i<argc; i++)
        {
            strcpy(buf, argv[i]);
            mysend(client_notify_sock, buf, 100, 0);
        }
        //最后发送一个空文件名告诉服务器共享文件名已发完
        buf[0] = '\0';
        mysend(client_notify_sock, buf, 100, 0);
        printf("客户端发送共享文件名成功\n");
        //创建线程,用来接收服务器发来的“通知头部”
        CreateThread(NULL, 0, client_notify_thread, (void *)client_notify_sock, 0, NULL);
 
    
        //创建线程,每隔10秒向服务器请求最新的共享文件列表
        CreateThread(NULL, 0, client_refresh_thread, NULL, 0, NULL);
 
        //创建线程,用来接收用户的键盘输入
        CreateThread(NULL, 0, client_userinput_thread, (void *)client_notify_sock, 0, NULL);
 
        //消息循环,目的是不让主线程退出。Linux中换成while(1) pause();
        MSG msg;
        while(GetMessage(&msg, 0, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    else
        printf("启动参数不正确\n");
    
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值