服务器客户端 之 文件下载

约定协议:

  客户端发送请求:char buf[256]=文件名

  服务端回复:  int len=文件长度 + 文件信息

        若文件不存在返回 "-1" + "file not found\n"

        若文件读取错误返回 "-2" + "server error\n"

        若文件为目录     "-3" + "dirtory error\n"

-----------TCP协议 客户端流程--------------------

创建socket

      根据协议族,协议类型,协议编号,向操作系统申请一个socket 文件描述符;

      int socket(int domain, int type, int protocol);

连接socket

      把申请的 socket 描述符 和 (服务器的ip类型,服务器的ip地址,服务器的端口号)连接起来

      connect(int socket_fd, const struct sockaddr *addr, socklen_t addr_len);

通话

      while(1)

      {

        通过socket 文件描述符 给 服务器发请求

        write(socket, request, strlen(request));

        分析服务器的回应,保存数据

        readline(socket, request, request_maxlen);

      }

---------------TCP协议 服务器端流程----------------------------

创建socket

      根据协议族,协议类型,协议编号,向操作系统申请一个socket 文件描述符;

      int server_sockfd = socket(int domain, int type, int protocol);

绑定IP端口号到socket

      把本地的ip类型,ip地址,端口号 绑定到 socket上

      int bind(int server_sockfd, const struct sockaddr *addr, socklen_t addr_len);

监听socket

      监听socket上是否有连接过来,并指定最大连接数(本质是把本地端口号,ip地址和。。。)

      int listen(int server_sockfd, int backlog);

接受连接    

      不断接受连接,每接收一个连接,就有一对socket(C/S),记录来自客户端连接的ip地址,端口号,并对每个连接请求作出回应,

      struct sockaddr_in client_addr;

      while(1)

      {

          int addr_len = sizeof(client_addr);

          int client_sockfd = acctpt(int server_sockfd, const struct sockaddr *client_addr, &addr_len);

          readline(client_sockfd, buf, buf_maxsize);

          write(client_sockfd, “回应”, maxsize);

      }

---------------------------

/* server.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include "wrap.h"

static void my_send(int client_fd, int err, char *msg)
{
    int size = htonl(err);
    char buf[256];
    memcpy(buf, &size, sizeof(int));
    memcpy(buf+sizeof(int), msg, sizeof(buf)-sizeof(int));
    Writen(client_fd, buf, sizeof(buf));
}

void server_dialog(int connect_fd)
{
    int n;                
    char buf[256];        // 文件名最大长度
    int remains;          // 要传送的数据大小
    int fd;               // 本地文件描述符
     
    n = Readn(connect_fd, buf, 256); // 获取客户端请求-文件名
    printf("filename: %s", buf);

    if(access(buf, F_OK) == 0){
        remains = file_size(buf);
    }else{
        my_send(connect_fd, -1, "file not found\n");
        return;
    }

    printf("filesize: %u\n",remains);
    if(remains < 0){
        my_send(connect_fd, -3, "dir error\n");
        return;
    }

    remains = htonl(remains);
    if((fd = open(buf, O_RDONLY)) < 0){
        my_send(connect_fd, -2, "server error\n");
        return;
    }
    
    memcpy(buf, &remains, sizeof(int)); // 告诉客户端要接收的数据长度
    Writen(connect_fd, buf, sizeof(int));

    while((n = read(fd, buf, sizeof(buf))) > 0)
	{   
		write(connect_fd, buf, n);
		printf("send %d bytes\n", n);
	}

    close(connect_fd);
}

int server(char *ip, int port)
{
    // step1: create socket
    int listenfd = Socket(AF_INET, SOCK_STREAM, 0);
 
    // step2: bind port
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr)); 
    server_addr.sin_family = AF_INET;  // 协议族
    inet_pton(AF_INET, ip, &server_addr.sin_addr); // ip地址
    server_addr.sin_port = htons(port); // 端口号
    Bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
 
    // step3: listen 
    Listen(listenfd, 20);
 
    // step4: accept connect socket
    socklen_t client_addr_len;
    struct sockaddr_in client_addr;
    int connect_fd;
    while(1)
    {
        client_addr_len = sizeof(struct sockaddr_in);
        connect_fd = Accept(listenfd, (struct sockaddr *)&client_addr, &client_addr_len);
        server_dialog(connect_fd);
    }
    return 0;
}
 
int main(int argc, char **argv) 
{
    if(argc != 3){
        fprintf(stderr, "%s <ip> <port>\n", argv[0]);
        exit(1);
    }
    char *ip = argv[1];
    int port = strtol(argv[2], NULL, 10);
    return server(ip, port);
}

  

/* client.c */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include "wrap.h"

void error_exit(int server_fd, int err)
{
    char buf[256];
    read(server_fd, buf, sizeof(buf));
    fprintf(stderr, "%s\n",buf);
    exit(-1);
}

void client_dialog(int sockfd, char *file, char *dir)
{
    int n;             // 每次实际接收到的数据
    char buf[256];     // 数据缓存区
    int remains;       // 要接收的数据大小
    int fd;            // 保存到本地的文件的描述符

    if(access(dir, F_OK) < 0){
        mkdir(dir, 0755);
    }

    sprintf(buf, "%s/%s", dir, file);       // 要保存的文件路径
    fd = open(buf, O_WRONLY|O_CREAT|O_TRUNC, 0755);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }
    strcpy(buf,file);
    Writen(sockfd, buf, 256);

    Readn(sockfd, buf, sizeof(int)); // 读取文件长度 
    
    memcpy(&remains, buf, sizeof(int));
    remains = ntohl(remains);
    printf("------ filesize = %d\n", remains);
    if(remains<0)
        error_exit(sockfd, remains);

    while(remains > 0)
    {
        if(remains < sizeof(buf))
            n = Readn(sockfd, buf, remains);
        else
            n = Readn(sockfd, buf, sizeof(buf));

        printf("client remains = %d, n = %d\n", remains,n);

        if(n==0) // 对方关闭了
            break;
        remains -= Writen(fd, buf, n);
    }
    close(sockfd);
}

int download(char *ip, int port, char *file, char *dir)
{
    int sockfd, n, fd; 
    sockfd = Socket(AF_INET, SOCK_STREAM, 0); 

    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &servaddr.sin_addr);
    servaddr.sin_port = htons(port);
 
    Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

        client_dialog(sockfd, file, dir);
    return 0;
}


int help_exit(char *exename)
{
	printf("%s  <ip> <port> <path> <dir>\n", exename);
	printf( 
		   "if [dir] is empty, it downs <file> to current directory.\n"
           "Example:\n"
           "%s 192.168.7.203 8080 /001.jpg .\n"
           "%s 192.168.7.203 8080 /002.jpg ./pic/\n", exename, exename);
	exit(0);
}


int main(int argc, char *argv[])
{
	if(argc != 5)
	{
		help_exit(argv[0]);
	}

    return download(argv[1], // ip
            atoi(argv[2]),  // port
            argv[3],  // file
            argv[4]  // destination dirctory
            );

}
/* wrap.c */
#include "wrap.h"

void perr_exit(const char *s)
{
	perror(s);
	exit(1);
}

char *getip(const char *domain)
{	
	struct hostent *phost = gethostbyname(domain);
	if(phost == NULL)	
		perr_exit("gethostbyname");
	else
	{
		static char buf[32];
		char **net_addr = phost->h_addr_list;	
		inet_ntop(phost->h_addrtype, net_addr[0], buf, sizeof(buf));
		return buf;
	}
}
 
char *chomp(char *s) // 删除字符串后面的\n
{
    char *save = s;
    while(*s){
        if(*s == '\n'){
            *s = '\0';
            return save;
        }
        s++;
    }
    return save;
}
ssize_t file_size(char *name) 
{
    int fd = open(name, O_APPEND);
    if(fd < 0)
    {
        perror("open src file");
        exit(-1);
    }
    off_t off = lseek(fd, 0, SEEK_END); 
    close(fd);
    return (int)off;
}

int Socket(int family, int type, int protocol)
{
    // ipv4 对应的族为AF_INET, TCP协议的类型为SOCK_STREAM, 协议protocal一般为0
    int n;
    if((n = socket(family, type, protocol)) < 0)
        perr_exit("socket error");
    return n; // 返回的socket文件描述符
}

void Bind(int sockfd, const struct sockaddr *sa, socklen_t salen)
{
    if(bind(sockfd, sa, salen) < 0)
        perr_exit("bind error");
}
void Connect(int sockfd, struct sockaddr *sa, socklen_t salen)
{
	if((connect(sockfd, sa, salen)) < 0)
		perr_exit("connect error");
}

void Listen(int sockfd, int backlog) // 监听sockfd, 最大连接数为backlog
{
    if(listen(sockfd, backlog) < 0)
        perr_exit("listen error");
}

int Accept(int sockfd, struct sockaddr *sa, socklen_t *salenptr)
{
    int n;
again:
    if( (n = accept(sockfd, sa, salenptr)) < 0)
    {
        if( errno == ECONNABORTED  // 软件引起的连接中止 ; 服务器端拒绝连接ECONNREFUSED
            || errno == EINTR)     // 被信号打断
            goto again;
        else
            perr_exit("accept error");
    }
    return n; 

}
 
ssize_t Readn(int fd, void *buf, size_t n)
{
	size_t gain;
	ssize_t remains;
	char *curr;

	remains = n;
	curr = buf;
	
	while(remains > 0)
	{
		if((gain = read(fd, curr, remains)) < 0)
		{
			if(errno == EINTR)// interrupted by signal	
				gain = 0;
			else
				return -1;
		}
		else if(gain == 0) // the other side has been closed
		{
			return n - remains;
		}
			
		remains -= gain;	
		curr += gain;
	}

	return n;
}

ssize_t Writen(int fd, void *buf, size_t n)
{
	size_t send;
	ssize_t remains;
	char *curr;
	
	remains = n;
	curr = buf;

	while(remains > 0)
	{
		if( (send = write(fd, curr, remains)) < 0 )
		{
			if(send == EINTR)
				send = 0;
			else
				return -1;
		}
		remains -= send;
		curr += send;
	}

	return n;	
}


static ssize_t my_read(int fd, char *ptr)
{
	static int read_cnt;
	static char *read_ptr; 
	static char read_buf[128];

	if (read_cnt <= 0) {
	again:
		if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
			if (errno == EINTR)
				goto again;
			return -1; 
		} else if (read_cnt == 0) // the other side has been closed
			return 0; 
        printf("readline: received %d \n", read_cnt);
		read_ptr = read_buf; // 第1次读入时read_cnt == 100
	}

	read_cnt--;         // 第2次以后调用时 直接执行下面语句 100次,使得read_cnt<0;
	*ptr = *read_ptr++; 
	return 1;
}

ssize_t Readline(int fd, void *vptr, size_t maxlen) // 返回行的长度, 功能类似fgets函数
{
	ssize_t n, rc;
	char    c, *ptr;

	ptr = vptr;
	for (n = 1; n < maxlen; n++) {
		if ( (rc = my_read(fd, &c)) == 1) { 
			*ptr++ = c;  // 每次只读1个字符
			if (c  == '\n') // 整行
				break;
		} else if (rc == 0) { // the other side has been closed
			*ptr = 0;
			return n - 1;   // 当前已读的数据长度
		} else
			return -1;     // error
	}
	*ptr  = 0;
	return n;
}

 

/* wrap.h */
#ifndef _WRAP_H
#define _WRAP_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

void perr_exit(const char *s);
char *chomp(char *s);
char *getip(const char *domain);
int Socket(int family, int type, int protocol);
void Bind(int sockfd, const struct sockaddr *sa, socklen_t salen);
void Connect(int sockfd, struct sockaddr *sa, socklen_t salen);
int Accept(int sockfd, struct sockaddr *sa, socklen_t *salenptr);
ssize_t Readn(int fd, void *buf, size_t n);
ssize_t Writen(int fd, void *buf, size_t n);
static ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
#endif 
 

# Makefile
all:
	gcc wrap.c client.c -o client
	gcc wrap.c server.c -o server
clean:
	-rm server client *.~ *.out
 

readme.txt
直接make

启动一个shell

./server 《本机ip》 《8080》
比如:./server 192.168.144.193 8080


./client 《本机ip》  《8080》 《本地文件》 《aaa》
比如:./client 192.168.144.193 8080 ./readme.txt ./aaa
 
原文地址:https://www.cnblogs.com/mathzzz/p/2681084.html