Tcp多线程传输文件 客户机可查询文件列表

最近做了一个简单的程序,结合之前学过的东西组装起来的一个小demo
这个小项目是一个简单的Tcp传输文件,在Ubuntu16.04下实现,使用了pthread多线程,能够承受简单的多并发情况。(我还实现了一个小功能 客户机向服务器发送ls 能够列出服务器的文件)
这个程序分为服务器与客户端,编译之后,启动程序传入两个参数,一个是服务器要绑定的ip,一个是服务器要绑定的端口(服务器启动要用自己的ip哦)。下面就是查看本机ip,编译,启动程序的演示。
在这里插入图片描述

然后就是客户端,这个程序实现多线程高并发,客户端是不需要开线程的,所以写起来非常的简单。
在这里插入图片描述
大致功能如下:
在这里插入图片描述
在终端下的程序输入输出 交互逻辑不太好搞 我已经比较尽力了 多客户端我就懒得调试截图上传了。代码贴在下面,服务器记得带上pthread参数。

最近学习写了很多的程序 有空的话 我会选一些比较好的上传的。有问题记得评论 我看见了会及时回复的。

//这是服务器的代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>

typedef struct Accept
{
    struct sockaddr_in cid_addr;
    int cid;

} Accept_t;
int Socket(int domain, int type, int protocol);
void Bind(int sockfd, const char *ip, short Port);
void Listen(int sockfd, int backlog);
void Print_cid(const struct sockaddr_in *addr);
void Print_cid_quit(const struct sockaddr_in *addr);
void *start_routine(void *arg);

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        printf("参数错误\n");
        exit(-1);
    }
    int sid = Socket(AF_INET, SOCK_STREAM, 0); //服务端
    int cid = -1;                              //客户端
    Bind(sid, argv[1], atoi(argv[2]));
    Listen(sid, 5);
    struct sockaddr_in cid_addr;
    socklen_t len = sizeof(cid_addr);
    pthread_t newthread;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    Accept_t Accept;
    while(1)
    {
        cid = accept(sid, (struct sockaddr *)&cid_addr, &len);
        Accept.cid_addr=cid_addr;
        Accept.cid=cid;
        if (cid < 0)
        {
            perror("accept:");
            exit(-1);
        }
        Print_cid(&cid_addr);
        pthread_create(&newthread,&attr,start_routine,&Accept);
    }
    return 0;
}
/************************************************
 * int Socket(int domain, int type, int protocol)
 * 功能:建立socket套接字对象
 * 参数:
 *      domain:域:用作基于什么类型操作
 *          Name Purpose Man page
 *          AF_UNIX, AF_LOCAL 局部通信 unix(7) 重点
 *          AF_INET IPv4网络协议 ip(7) 重点
 *          AF_INET6 IPv6网络协议 ipv6(7) 重点
 *          AF_IPX IPX - Novell协议
 *          AF_NETLINK 内核用户界面设备 netlink(7)
 *          AF_X25 ITU-T X.25 / ISO-8208协议 x25(7)
 *          AF_AX25 业余无线电AX.25协议
 *          AF_ATMPVC 访问原始ATM pvc
 *          AF_APPLETALK 可路由协议组 ddp(7)
 *          AF_PACKET 低层包接口 packet(7)
 *          AF_ALG 内核加密API接口
 *      type:套接字类型
 *          SOCK_STREAM:流式套接字 唯一对应 TCP协议
 *          SOCK_DGRAM:数据报套接字 唯一对应 UDP协议
 *          SOCK_RAW:原始套接字
 *      protocol:根据套接字类型,一般默认填0,如果式原始套接字等则需要
 * 返回值:
 *      成功:返回特殊的文件描述符
 *      失败:-1,且errno存储错误类型
 * *********************************************/
int Socket(int domain, int type, int protocol)
{
    int sid = socket(domain, type, protocol);
    if (sid < 0)
    {
        perror("socket:");
        exit(-1);
    }
    return sid;
}
/************************************************
 * 函数名:void Bind(int sockfd, const char *ip, short Port)
 * 功能:为任务绑定ip和端口号
 * 参数:
 *      sockfd:通过socket函数拿到的文件特殊描述符
 *      addr:struct sockaddr的结构体变量的首地址
 *      addrlen:struct sockaddr的结构体变量的长度
 * 返回值:
 *      成功:0
 *      失败:-1,并且填写errno
 * *********************************************/
void Bind(int sockfd, const char *ip, short Port)
{
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(Port);
    addr.sin_addr.s_addr = inet_addr(ip);
    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        perror("bind:");
        exit(-1);
    }
}
/************************************************
 * 函数名:void Listen(int sockfd, int backlog)
 * 功能:监听套接字,将主动套接字变成被动套接字
 * 参数:
 *      sockfd:通过socket函数拿到的文件特殊描述符
 *      backlog:定义了队列的最大长度,最多客户端同时访问,一般填5
 *          如果一个连接请求当队列已满时到达,客户端可能会收到一个错误指示ECO
 *          NNREFUSED或,如果底层 协议支持重传时,请求可能会被忽略,以便稍
 *          后重试连接成功。
 * 返回值:
 *      成功:0
 *      失败:-1,并且填写errno
 * *********************************************/
void Listen(int sockfd, int backlog)
{
    if (listen(sockfd, backlog) == -1)
    {
        perror("listen:");
        exit(-1);
    }
}
/************************************************
 * 函数名:Print_cid(const struct sockaddr_in *addr)
 * 功能:打印套接字内容
 * 参数:
 *      addr:套接字地址
 * 返回值:
 *      无
 * *********************************************/
void Print_cid(const struct sockaddr_in *addr)
{
    printf("类型是%s\n", addr->sin_family == AF_INET ? "AFINET" : "AF_INET6");
    printf("ip地址为:%s\n端口号:%d\n", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
}
/************************************************
 * 函数名:void Print_cid_quit(const struct sockaddr_in *addr)
 * 功能:当客户机正常退出FTP服务器时 输出套接字信息
 * 参数:
 *      addr:套接字地址
 * 返回值:
 *      无
 * *********************************************/
void Print_cid_quit(const struct sockaddr_in *addr)
{
    printf("ip地址为:%s\t端口号:%d\t退出了传输 程序结束\n", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port)); //打印ip和端口号
}
/************************************************
 * 函数名:void *start_routine(void *arg)
 * 功能:服务器创建的线程 每当一个客户机建立会话时 都会新建一个线程
 * 参数:
 *      arg:创建建立连接的套接字
 * 返回值:
 *      无
 * *********************************************/
void *start_routine(void *arg)
{
    int fd,trafd;
    int flag=0;
    Accept_t *Accept=(Accept_t *)arg;
    struct sockaddr_in cid_addr =Accept->cid_addr;
    int cid=Accept->cid;
    char buf[1024]="";
    char filenameTemp[1024];
    char dirread[1024]="";
    int buf_len=0;
    while(1)
    {
        memset(buf,0,sizeof(buf));
        buf_len=read(cid,buf,sizeof(buf)-1);
        printf("接收到数据长度为%d的数据为%s\n", buf_len, buf);
        strcpy(filenameTemp,buf);
        filenameTemp[buf_len-1]='\0';
        if(strncmp("ls", buf, strlen("ls")) == 0)
        {
            memset(buf, 0, sizeof(buf));
            printf("执行打印目录列表命令:\n");
            system("ls >.DirTemp");
            fd = open(".DirTemp",O_RDONLY,0666);
            if(fd<=0)
            {
                printf("打开缓存目录文件失败\n");
            }
            
            read(fd,dirread,sizeof(dirread)-1);
            printf("当前目录下的文件:\n%s",dirread);
            send(cid, dirread, strlen(dirread), 0);
        }
        else if (strncmp("quit", buf, strlen("quit")) == 0 || buf_len == 0) //判断是否接收到quit指令,代表客户端主动断开连接
        {
            Print_cid_quit(&cid_addr);
            close(cid);
            exit(0);
            break;
        }
        else if((trafd=open(filenameTemp,O_RDONLY,0666))<=0)
        {
            printf("   !!!本地不存在[%s]这个文件 无法传输!!!\n",filenameTemp);
            send(cid, "文件不存在", strlen("文件不存在"), 0);
            continue;
        }
        else
        {
            printf("发送消息\n");
            send(cid, "传输文件", strlen("传输文件"), 0);
            printf("   本地目录检索到文件 开始传输文件\n");
            sleep(2);
            while(1)
            {
                memset(buf, 0, sizeof(buf));
                buf_len=read(trafd,buf,1024);
                printf("文件正在传输\n");
                write(cid,buf,buf_len);
                if(buf_len<1024)
                {
                    printf("传输完成\n");
                    flag=1;
                }
                if(flag==1)
                {
                    flag=0;
                    break;
                }
                sleep(1);
            }
        }
    }
}
//这是客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>

int Socket(int domain, int type, int protocol);
void Bind(int sockfd, const char *ip, short Port);
void Listen(int sockfd, int backlog);
void Print_cid(const struct sockaddr_in *addr);

int main(int argc, char *argv[])
{
    if (argc < 3)
    {
        printf("参数错误\n");
        return -1;
    }
    int cid = Socket(AF_INET, SOCK_STREAM, 0); //服务端
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(argv[2]));
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    if (connect(cid, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        printf("连接错误\n");
        return -1;
    }
    int trafd;
    char buf[1024] = "";
    char msg[1024];
    char filename[1024];
    int buf_len=0;
    while (1)
    {
        memset(buf, 0, sizeof(buf));
        printf("请输入要发送的信息:");
        fgets(buf, sizeof(buf)-1, stdin);
        strcpy(filename,buf);
        filename[strlen(filename)-1]='\0';
        send(cid, buf, strlen(buf), 0);
        if (strncmp("quit", buf, strlen("quit")) == 0 ) //判断是否接收到quit指令,代表客户端主动断开连接
        {
            exit(0);
            close(cid);
            break;
        }
        memset(buf, 0, sizeof(buf));
        recv(cid, buf, sizeof(buf), 0);
        strcpy(msg,buf);
        msg[strlen(buf)]='\0';
        if(strncmp("传输文件", msg, strlen("传输文件")) == 0)
        {
            printf("开始传输文件 <%s>\n",filename);
            trafd=open(filename,O_RDWR|O_CREAT,0666);
            sleep(1);
            while(1)
            {
                memset(buf, 0, 1024);
                printf("文件正在传输1\n");
                buf_len=read(cid, buf, 1024);
                printf("文件正在传输2\n");
                write(trafd,buf,buf_len);
                if(buf_len<1024)
                {
                    printf("传输完成\n");
                    break;
                }
            }
        }
        printf("收到服务器发来的信息:\n%s\n\n", buf);
    }
    close(cid);
    return 0;
}
/************************************************
 * int Socket(int domain, int type, int protocol)
 * 功能:建立socket套接字对象
 * 参数:
 *      domain:域:用作基于什么类型操作
 *          Name Purpose Man page
 *          AF_UNIX, AF_LOCAL 局部通信 unix(7) 重点
 *          AF_INET IPv4网络协议 ip(7) 重点
 *          AF_INET6 IPv6网络协议 ipv6(7) 重点
 *          AF_IPX IPX - Novell协议
 *          AF_NETLINK 内核用户界面设备 netlink(7)
 *          AF_X25 ITU-T X.25 / ISO-8208协议 x25(7)
 *          AF_AX25 业余无线电AX.25协议
 *          AF_ATMPVC 访问原始ATM pvc
 *          AF_APPLETALK 可路由协议组 ddp(7)
 *          AF_PACKET 低层包接口 packet(7)
 *          AF_ALG 内核加密API接口
 *      type:套接字类型
 *          SOCK_STREAM:流式套接字 唯一对应 TCP协议
 *          SOCK_DGRAM:数据报套接字 唯一对应 UDP协议
 *          SOCK_RAW:原始套接字
 *      protocol:根据套接字类型,一般默认填0,如果式原始套接字等则需要
 * 返回值:
 *      成功:返回特殊的文件描述符
 *      失败:-1,且errno存储错误类型
 * *********************************************/
int Socket(int domain, int type, int protocol)
{
    int sid = socket(domain, type, protocol);
    if (sid < 0)
    {
        perror("socket:");
        exit(-1);
    }
    return sid;
}
/************************************************
 * 函数名:void Bind(int sockfd, const char *ip, short Port)
 * 功能:为任务绑定ip和端口号
 * 参数:
 *      sockfd:通过socket函数拿到的文件特殊描述符
 *      addr:struct sockaddr的结构体变量的首地址
 *      addrlen:struct sockaddr的结构体变量的长度
 * 返回值:
 *      成功:0
 *      失败:-1,并且填写errno
 * *********************************************/
void Bind(int sockfd, const char *ip, short Port)
{
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(Port);
    addr.sin_addr.s_addr = inet_addr(ip);
    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        perror("bind:");
        exit(-1);
    }
}
/************************************************
 * 函数名:void Listen(int sockfd, int backlog)
 * 功能:监听套接字,将主动套接字变成被动套接字
 * 参数:
 *      sockfd:通过socket函数拿到的文件特殊描述符
 *      backlog:定义了队列的最大长度,最多客户端同时访问,一般填5
 *          如果一个连接请求当队列已满时到达,客户端可能会收到一个错误指示ECO
 *          NNREFUSED或,如果底层 协议支持重传时,请求可能会被忽略,以便稍
 *          后重试连接成功。
 * 返回值:
 *      成功:0
 *      失败:-1,并且填写errno
 * *********************************************/
void Listen(int sockfd, int backlog)
{
    if (listen(sockfd, backlog) == -1)
    {
        perror("listen:");
        exit(-1);
    }
}
/************************************************
 * 函数名:Print_cid(const struct sockaddr_in *addr)
 * 功能:打印套接字内容
 * 参数:
 *      addr:套接字地址
 * 返回值:
 *      无
 * *********************************************/
void Print_cid(const struct sockaddr_in *addr)
{
    printf("类型是%s\n", addr->sin_family == AF_INET ? "AFINET" : "AF_INET6");
    printf("ip地址为:%s\n端口号:%d\n", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值