网络程序设计——(connectTCP和connectUDP的实现)2.利用TCP/UDP完成文件传输的设计和实现(下)

每个客户与服务器建立联系必须:

  1. 选择协议(UDP或TCP) 查找服务器的机器名
  2. 查找所期望的服务并将其映射到协议端口号
  3. 分配套接字并与之连接
  4. 将这部分工作进行封装,置于某个过程当中,只需一次编码。

我们想要实现以下抽象:

  • socket = connectTCP(machine, service);
  • socket = connectUDP(machine, service);

优点:高级操作,共享代码,减少出错

实现代码如下:

1.connect.c文件

int connectsock(const char *host, const char *service,
                const char *transport);

int connectTCP(const char *host, const char *service)
{
    return connectsock(host, service, "tcp");
}
int connectUDP(const char *host, const char *service)
{
    return connectsock(host, service, "udp");
}

2.connectsock.c文件:

//connectsock.c
//created by cck
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>//替换extern int errno;

#ifndef INADDR_NONE
#define INADDR_NONE     0xffffffff
#endif // INADDR_NONE



int errexit(const char *format, ...);

int connectsock(const char * host, const char *service, const char *transport)
{
    struct hostent  *phe;
    struct servent  *pse;
    struct protoent *ppe;
    struct sockaddr_in  sin;
    int     s, type;


    memset(&sin,0,sizeof(sin));
    sin.sin_family = AF_INET;
    /*map port*/
    if( pse = getservbyname(service, transport) )
        sin.sin_port = pse->s_port;
    else if ( (sin.sin_port=htons((unsigned short)atoi(service))) == 0)
		errexit("can't get \"%s\" service entry\n", service);
	/*map ip*/
    if ( phe = gethostbyname(host) )
		memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
    else if ( (sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE )
		errexit("can't get \"%s\" host entry\n", host);
    /*map protocol*/
    if ( (ppe = getprotobyname(transport)) == 0)
		errexit("can't get \"%s\" protocol entry\n", transport);
    /*use protocol to chose a socket type*/
    if(strcmp(transport,"udp") == 0)
        type = SOCK_DGRAM;
    else
        type = SOCK_STREAM;
    /*allocate a socket*/
    s = socket(PF_INET, type, ppe->p_proto);
    if (s < 0)
		errexit("can't create socket: %s\n", strerror(errno));
    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
        errexit("can't connect to %s.%s: %s\n", host, service,strerror(errno));
    return s;
}

3.errexit.c

//errexit.c
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

int errexit(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    exit(1);
}

    以上是connectTCP、connectUDP的实现函数,以下是使用其实现TCP文件传输的代码:

4.tcp_client2.c

//tcp_client2.c
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define BUFFER_SIZE 1024


void process_conn_client(int s)
{
    ssize_t size = 0;
    char buffer[BUFFER_SIZE];
    FILE *stream;

    int length = 0;
    char filepath[100] = {'\0'};
    size = read(s, buffer, BUFFER_SIZE);
    printf("%s",buffer);

    scanf("%s",filepath);
    write(s,filepath,100);

    if( (stream = fopen(filepath,"r")) == NULL) {
            printf("client:open file error!\n");
            return;
    }
    printf("sending!\n");
    while(1){
        size = fread(buffer,sizeof(char),BUFFER_SIZE,stream);
        if(size <= 0){
            break;
        }
        write(s,buffer,size);
    }
    printf("send finished!\n");
    fclose(stream);
}

int main(){
    int socket = connectTCP("localhost","8888");
    process_conn_client(socket);
    close(socket);
}

    抽象封装之后,main函数只需要3行代码即可搞定

5.tcp_server.c

使用上篇的代码:

#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>

#define PORT 8888
#define BUFFER_SIZE 1024

void get_filename(char *filepath,char *filename)
{
    /*解析文件名*/
    int i=0,k=0;
    for(i=strlen(filepath);i>=0;i--)
    {
        if(filepath[i]!='/')
        {
            k++;
        }
        else
            break;
    }
    strcpy(filename,filepath+(strlen(filepath)-k)+1);
}

void process_conn_server(int sd)
{
    ssize_t size = 0;
    char buffer[BUFFER_SIZE];
    FILE *stream;
    char filepath[100];

    strcpy(buffer,"please enter a path!\n");
    write(sd,buffer,BUFFER_SIZE);
    int length = 0;
    memset(filepath,'\0',sizeof(filepath));

    length = read(sd,filepath,100);
    if(length < 0){
        printf("recv error!\n");
    }
    else
    {
        char filename[100] = {'\0'};
        get_filename(filepath,filename);
        printf("server: filename:\n%s",filename);

        if( (stream=fopen(filename, "w")) == NULL){
            printf("server:open file error!\n");
            return;
        }
        while(1){/*读取文件并写入文件流*/
            size = read(sd, buffer, BUFFER_SIZE);
            printf("server:size:%d\n",size);
            if(size <= 0){
                break;
            }
            int write_len=fwrite(buffer, sizeof(char), size, stream);
        }
        printf("recv finished!\n");
        fclose(stream);
    }
}

int main(int argc, char *argv[]){
    int socksd,sockcd;
    struct sockaddr_in server,client;
    pid_t pid;
    if( (socksd = socket(AF_INET,SOCK_STREAM,0)) < 0)
    {
        printf("socket create error!\n");
        return -1;
    }
    printf("socket create success!\n");

    /*加入此代码是为了避免再次打开服务器程序出现bind error的错误*/
    int on = 1;
    int ret = setsockopt(socksd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    memset(&server,0,sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    server.sin_port = htons(PORT);
    if( (bind(socksd,(struct sockaddr*)&server,sizeof(server)) < 0))
    {
        printf("socket bind error!\n");
        return -1;
    }
    printf("socket bind success!\n");

    if( (listen(socksd,10)) < 0)
    {
        printf("socket listen error!\n");
        return -1;
    }
    printf("socket listen success!\n");

    printf("waiting...\n");
    /*显示核是sigchld信号*/
    if(signal(SIGCHLD, SIG_IGN) == SIG_ERR){
            perror("signal error");
            return EXIT_SUCCESS;
        }
    while(1){
        socklen_t addr_len = sizeof(struct sockaddr);
        if( (sockcd = accept(socksd, (struct sockaddr*)&client, &addr_len)) < 0)
        {
            //出错
            continue;
        }
        printf("server:accept\n");
        /*建立一个新进程来处理到来的连接*/
        pid = fork();
        if(pid == 0)
        {
            process_conn_server(sockcd);
            close(socksd);/*在子进程中关闭服务器的监听*/
            exit(0);
            return 0;
        }
        else{
            close(sockcd);/*在父进程中关闭客户端的监听*/
        }
    }
    return 0;
}

编译时可以用命令行将前4个文件一起编译,第五个单独编译,然后运行,也可以使用Makefile文件编译:

Makefile文件内代码如下:

tcp_server tcp_client2:tcp_server.o tcp_client2.o connect.o errexit.o connectsock.o
        gcc tcp_server.o -o tcp_server
        gcc tcp_client2.o connect.o errexit.o connectsock.o -o tcp_client2
tcp_server.o:tcp_server.c
        gcc -c tcp_server.c -o tcp_server.o
tcp_client2.o:tcp_client2.c
        gcc -c tcp_client2.c -o tcp_client2.o
connect.o:connect.c
        gcc -c connect.c -o connect.o
connectsock.o:connectsock.c
        gcc -c connectsock.c -o connectsock.o
errexit.o:errexit.c
        gcc -c errexit.c -o errexit.o
.PHONT:clean
clean:
        rm -rf *.o

    注:代码直接复制到Makefile文件中不能用,gcc、rm命令前面是一个制表符,不是空格!!!

运行结果    

    make编译过程如下:

    服务器端程序运行结果如下:

    客户端程序运行如下:

    可以看出,1.png文件传输成功

 

Ps.有关Makefile的编写规则,可以参考如下博客:

Linux下多个.c文件的编译和Makefile文件

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页