linux下c/c++实例之十socket简单应用

一、简介

      通过socket扫描本机打开的tcp端口号,模拟用户名、密码登录服务器的过程、socket文件传输及模仿http服务器。

二、详解

1、Linux下tcp端口扫描

(1)scanport.c:

// 端口扫描程序,只支持扫描TCP端口
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// 定义一个端口区间信息
typedef struct _port_segment {
    struct in_addr       dest_ip;	// 目标IP
    unsigned short int	min_port;	// 起始端口
    unsigned short int	max_port;	// 最大端口
} port_segment;

/*自定义的错误处理函数*/
void my_err(const char * err_string, int line)
{
    fprintf(stderr, "line:%d  ", line);
    perror(err_string);
    exit(1);
}

/*
 * 描  述:扫描某一IP地址上的某一个端口的函数
 * 返回值:-1  出错
 *	   0   目标端口未打开
 *	   1   目标端口已打开
 */
int do_scan(struct sockaddr_in serv_addr)
{
    int		conn_fd;
    int		ret;
    // 创建一个TCP套接字
    conn_fd = socket(AF_INET, SOCK_STREAM,0);
    if (conn_fd < 0) {
        my_err("socket", __LINE__);
    }
    // 向服务器端发送连接请求
    if ((ret = connect(conn_fd, (struct sockaddr *)&serv_addr, sizeof (struct sockaddr))) < 0 ) {
        if (errno == ECONNREFUSED) {	// 目标端口未打开
            close(conn_fd);
            return 0;
        } else {	// 其他错误
            close(conn_fd);
            return -1;
        }
    } else if (ret == 0){
        printf("port %d found in %s\n", ntohs(serv_addr.sin_port),
               inet_ntoa(serv_addr.sin_addr));
        close(conn_fd);
        return 1;
    }

    return -1;	// 实际执行不到这里,只是为了消除编译程序时产生的警告
}

// 执行扫描的线程,扫描某一区间的端口
void * scaner(void *arg)
{
    unsigned short int	i;
    struct sockaddr_in	serv_addr;
    port_segment		portinfo;	  // 端口信息

    // 读取端口区间信息
    memcpy(&portinfo, arg, sizeof(struct _port_segment));

    // 初始化服务器端地址结构
    memset(&serv_addr, 0, sizeof (struct sockaddr_in));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = portinfo.dest_ip.s_addr;
    //printf("******serv_addr.sin_addr.s_addr=%s\n", serv_addr.sin_addr.s_addr);
    //printf("*******portinfo.min_port=%d, portinfo.max_port=%d\n", portinfo.min_port, portinfo.max_port);
    for (i=portinfo.min_port; i<=portinfo.max_port; i++) {
        serv_addr.sin_port = htons(i);
        if (do_scan(serv_addr) < 0) {
            continue;	// 出错则退出进程
        }
    }
    return NULL;
}

/*
 * 命令行参数:-m 最大端口, -a 目标主机的IP地址,  -n 最大线程数
 */
int main(int argc, char **argv)
{
    pthread_t*	thread;			// 指向所有的线程ID
    int		max_port;		// 最大端口号
    int		thread_num;		// 最大线程数
    int		seg_len;		// 端口区间长度
    struct in_addr dest_ip;	        // 目标主机IP
    int		i;

    // 检查参数个数
    if (argc != 7) {
        printf("Usage: [-m] [max_port] [-a] [serv_address] [-n] [thread_number]\n");
        exit(1);
    }

    // 解析命令行参数
    for (i=1; i<argc; i++) {
        if (strcmp("-m", argv[i]) == 0) {
            max_port = atoi(argv[i+1]);   // 将字符串转化为对应的整数
            if (max_port < 0 || max_port >= 65535) {   //65535会导致死循环
                printf("Usage:invalid max dest port\n");
                exit(1);
            }
            continue;
        }

        if (strcmp("-a", argv[i]) == 0) {
            if (inet_aton(argv[i+1], &dest_ip) == 0) {
                printf("Usage:invalid dest ip address\n");
                exit(1);
            }
            continue;
        }

        if (strcmp("-n", argv[i]) == 0) {
            thread_num = atoi(argv[i+1]);
            if (thread_num <= 0) {
                printf("Usage:invalid thread_number\n");
                exit(1);
            }
            continue;
        }
    }
    // 如果输入的最大端口号小于线程数,则将线程数设为最大端口号
    if (max_port < thread_num) {
        thread_num = max_port;
    }

    seg_len = max_port / thread_num;

    if ( (max_port%thread_num) != 0 ) {
        thread_num += 1;
    }
    //printf("max_port=%d, seg_len=%d, thread_num = %d\n", max_port, seg_len, thread_num);

    // 分配存储所有线程ID的内存空间
    thread = (pthread_t*)malloc(thread_num*sizeof(pthread_t));

    // 创建线程,根据最大端口号和线程数分配每个线程扫描的端口区间
    port_segment *portinfo = (port_segment*)malloc(thread_num * sizeof(port_segment));
    for (i=0; i<thread_num; i++) {
        portinfo[i].dest_ip = dest_ip;
        portinfo[i].min_port = i*seg_len + 1;
        //printf("portinfo.min_port=%d, seg_len=%d, i = %d\n", portinfo[i].min_port, seg_len, i);

        if (i == thread_num - 1) {
            portinfo[i].max_port = max_port;
        } else {
            portinfo[i].max_port = portinfo[i].min_port + seg_len - 1;
        }
        // 创建线程
        //if (pthread_create(&thread[i], NULL, scaner, (void *)(portinfo + i)) != 0) {
        if (pthread_create(&thread[i], NULL, scaner, (void *)(&portinfo[i])) != 0) {
            my_err("pthread_create", __LINE__);
        }
    }
    for (i=0; i<thread_num; i++) {
        // 主线程等待子线程结束
        pthread_join(thread[i], NULL);
    }
    free(portinfo);
    return 0;
}

(2)编译运行

gcc -o scanport scanport.c  -lpthread
 ./scanport -m 65534 -a 127.0.0.1 -n 1

       可以通过netstat查看网络相关信息,netstat -tnlp或 netstat -tnlpc比较打开的tcp端口号。

2、模拟用户名、密码登录

(1)服务器server.c:
// Client/Server模型的服务器端
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>

#define	BUFSIZE	1024

#define	SERV_PORT		4507	// 服务器端的端口
#define	LISTENQ			12	// 连接请求队列的最大长度

#define	INVALID_USERINFO	'n'	// 用户信息无效
#define	VALID_USERINFO		'y'	// 用户信息有效

#define	USERNAME		0	// 接收到的是用户名
#define	PASSWORD		1	// 接收到的是密码

struct userinfo {  	// 保存用户名和密码的结构体
    char username[32];
    char password[32];
};
struct userinfo users[ ] = {
{"linux", "unix"},
{"4507", "4508"},
{"clh", "clh"},
{"xl", "xl"},
{" "," "}	   	// 以只含一个空格的字符串作为数组的结束标志
};
/*自定义的错误处理函数*/
void my_err(const char * err_string, int line)
{
    fprintf(stderr, "line:%d  ", line);
    perror(err_string);
    exit(1);
}

/*
* 函数名: my_recv
* 描 述 : 从套接字上读取一次数据(以'\n'为结束标志)
* 参 数 : conn_fd 	-- 从该连接套接字上接收数据
*	  data_buf 	-- 读取到的数据保存在此缓冲中
*	  len 		-- data_buf所指向的空间长度
* 返回值: 出错返回-1, 服务器端已关闭连接则返回0, 成功返回读取的字节数
*/
int my_recv(int conn_fd, char *data_buf, int len)
{
    static  char	recv_buf[BUFSIZE]; 	// 自定义缓冲区,BUFSIZE定义在my_recv.h中
    static  char	*pread;			// 指向下一次读取数据的位置
    static  int	len_remain = 0;	 	// 自定义缓冲区中剩余字节数
    int			i;

    // 如果自定义缓冲区中没有数据,则从套接字读取数据
    if (len_remain <= 0) {
        if ((len_remain =recv(conn_fd, recv_buf, sizeof (recv_buf), 0)) < 0) {
            my_err("recv", __LINE__);
        } else if (len_remain == 0) {	// 目的计算机端的socket连接关闭
            return 0;
        }
        pread = recv_buf;	// 重新初始化pread指针
    }

    // 从自定义缓冲区中读取一次数据
    for (i=0; *pread != '\n'; i++) {
        if (i > len) {	// 防止指针越界
            return -1;
        }
        data_buf[i] = *pread++;
        len_remain--;
    }

    // 去除结束标志
    len_remain--;
    pread++;

    return i;	// 读取成功
}
// 查找用户名是否存在,存在返回该用户名的下标,不存在则返回-1,出错返回-2
int find_name(const char *name)
{
    int i;

    if (name == NULL) {
        printf("in find_name, NULL pointer");
        return -2;
    }
    for (i=0; users[i].username[0] != ' ';i++) {
        if (strncmp(users[i].username, name, strlen(users[i].username)) == 0) {
            return i;
        }
    }

    return -1;
}

// 发送数据
void send_data(int conn_fd, const char *string)
{
    if (send(conn_fd, string, strlen(string), 0) < 0) {
        my_err("send", __LINE__);  // my_err函数在my_recv.h中声明
    }
}

int main()
{
    int			sock_fd, conn_fd;
    int			optval;
    int			flag_recv = USERNAME; // 标识接收到的是用户还是密码
    int			ret;
    int			name_num;
    pid_t			pid;
    socklen_t		cli_len;
    struct sockaddr_in	cli_addr, serv_addr;
    char			recv_buf[128];

    // 创建一个TCP套接字
    sock_fd = socket(AF_INET, SOCK_STREAM,0);
    if (sock_fd < 0) {
        my_err("socket", __LINE__);
    }

    // 设置该套接字使之可以重新绑定端口
    optval = 1;
    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR,
                   (void *)&optval, sizeof(int)) < 0) {
        my_err("setsockopt", __LINE__);
    }

    // 初始化服务器端地址结构
    memset(&serv_addr, 0, sizeof (struct sockaddr_in));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 将套接字绑定到本地端口
    if (bind(sock_fd, (struct sockaddr *)&serv_addr,
             sizeof (struct sockaddr_in)) < 0) {
        my_err("bind", __LINE__);
    }

    // 将套接字转化为监听套接字
    if (listen(sock_fd, LISTENQ) < 0) {
        my_err("listen", __LINE__);
    }

    cli_len = sizeof (struct sockaddr_in);
    while (1) {
        // 通过accept接受客户端的连接请求,并返回连接套接字用于收发数据
        conn_fd = accept(sock_fd, (struct sockaddr *)&cli_addr, &cli_len);
        if (conn_fd < 0) {
            my_err("accept", __LINE__);
        }

        printf("accept a new client, ip:%s\n", inet_ntoa(cli_addr.sin_addr));
        // 创建一个子进程处理刚刚接受的连接请求
        if ( (pid = fork()) == 0 ) {	// 子进程
            while(1) {
                memset(recv_buf, 0, sizeof(recv_buf));
                if ((ret = recv(conn_fd, recv_buf, sizeof (recv_buf), 0)) < 0) {
                    perror("recv");
                    exit(1);
                }
                recv_buf[ret-1] = '\0'; // 将数据结束标志'\n'替换成字符串结束标志
                if (flag_recv == USERNAME) {	// 接收到的是用户名
                    name_num = find_name(recv_buf);
                    switch (name_num) {
                    case -1:
                        send_data(conn_fd, "n\n");
                        break;
                    case -2:
                        exit(1);
                        break;
                    default:
                        send_data(conn_fd, "y\n");
                        flag_recv = PASSWORD;
                        break;
                    }
                } else if (flag_recv == PASSWORD) {	 // 接收到的是密码
                    if (strncmp(users[name_num].password, recv_buf, strlen(users[name_num].password)) == 0) {
                        send_data(conn_fd, "y\n");
                        send_data(conn_fd, "Welcome login my tcp server\n");
                        printf("%s login\n", users[name_num].username);
                        break; // 跳出while循环
                    } else
                        send_data(conn_fd, "n\n");
                }
            }
            close(sock_fd);
            close(conn_fd);
            exit(0);  // 结束子进程
        } else {   // 父进程关闭刚刚接受的连接请求,执行accept等待其他连接请求
            close(conn_fd);
        }
    }

    return 0;
}
(2)客户端client.c:
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define	BUFSIZE	1024

#define	INVALID_USERINFO	'n'	// 用户信息无效
#define	VALID_USERINFO		'y'	// 用户信息有效
/*自定义的错误处理函数*/
void my_err(const char * err_string, int line)
{
    fprintf(stderr, "line:%d  ", line);
    perror(err_string);
    exit(1);
}

/*
* 函数名: my_recv
* 描 述 : 从套接字上读取一次数据(以'\n'为结束标志)
* 参 数 : conn_fd 	-- 从该连接套接字上接收数据
*	  data_buf 	-- 读取到的数据保存在此缓冲中
*	  len 		-- data_buf所指向的空间长度
* 返回值: 出错返回-1, 服务器端已关闭连接则返回0, 成功返回读取的字节数
*/
int my_recv(int conn_fd, char *data_buf, int len)
{
    static  char	recv_buf[BUFSIZE]; 	// 自定义缓冲区,BUFSIZE定义在my_recv.h中
    static  char	*pread;			// 指向下一次读取数据的位置
    static  int	len_remain = 0;	 	// 自定义缓冲区中剩余字节数
    int			i;

    // 如果自定义缓冲区中没有数据,则从套接字读取数据
    if (len_remain <= 0) {
        if ((len_remain =recv(conn_fd, recv_buf, sizeof (recv_buf), 0)) < 0) {
            my_err("recv", __LINE__);
        } else if (len_remain == 0) {	// 目的计算机端的socket连接关闭
            return 0;
        }
        pread = recv_buf;	// 重新初始化pread指针
    }

    // 从自定义缓冲区中读取一次数据
    for (i=0; *pread != '\n'; i++) {
        if (i > len) {	// 防止指针越界
            return -1;
        }
        data_buf[i] = *pread++;
        len_remain--;
    }

    // 去除结束标志
    len_remain--;
    pread++;

    return i;	// 读取成功
}
/*获取用户输入存入到buf,buf的长度为len,用户输入数据以'\n'为结束标志*/
int get_userinfo(char *buf, int len)
{
    int	i;
    int	c;

    if (buf == NULL) {
        return -1;
    }

    i = 0;
    while ( ((c = getchar()) != '\n') && (c != EOF) && (i < len-2) ) {
        buf[i++] = c;
    }
    buf[i++] = '\n';
    buf[i++] = '\0';

    return 0;
}

// 输入用户名,然后通过fd发送出去
void input_userinfo(int conn_fd, const char *string)
{
    char	input_buf[32];
    char	recv_buf[BUFSIZE];
    int	flag_userinfo;
    int count = 0;
    // 输入用户信息直到正确为止
    do {
        printf("%s:", string);
        if (get_userinfo(input_buf, 32) < 0) {
            printf("error return from get_userinfo\n");
            exit(1);
        }

        if (send(conn_fd, input_buf, strlen(input_buf), 0) < 0) {
            my_err("send", __LINE__);
        }

        // 从连接套接字上读取一次数据
        if (my_recv(conn_fd, recv_buf, sizeof (recv_buf)) < 0) {
            printf("data is too long\n");
            exit(1);
        }

        if (recv_buf[0] == VALID_USERINFO) {
            flag_userinfo = VALID_USERINFO;
        } else {
            count++;
            if (count >= 3) {
                printf("input %s error for three times,exit!\n", string);
                exit(-1);
            }
            printf("%s error,input again!\n", string);
            flag_userinfo= INVALID_USERINFO;
        }
    } while(flag_userinfo == INVALID_USERINFO);
}

int main(int argc, char **argv)
{
    int			i;
    int			ret;
    int			conn_fd;
    int			serv_port;
    struct sockaddr_in	serv_addr;
    char			recv_buf[BUFSIZE];

    // 检查参数个数
    if (argc != 5) {
        printf("Usage: [-p] [serv_port] [-a] [serv_address]\n");
        exit(1);
    }

    // 初始化服务器端地址结构
    memset(&serv_addr, 0, sizeof (struct sockaddr_in));
    serv_addr.sin_family = AF_INET;
    // 从命令行获取服务器端的端口与地址
    for (i=1; i<argc; i++) {
        if (strcmp("-p", argv[i]) == 0) {
            serv_port = atoi(argv[i+1]);
            if (serv_port < 0 || serv_port > 65535) {
                printf("invalid serv_addr.sin_port\n");
                exit(1);
            } else {
                serv_addr.sin_port = htons(serv_port);
            }
            continue;
        }

        if (strcmp("-a", argv[i]) == 0) {
            if (inet_aton(argv[i+1], &serv_addr.sin_addr) == 0) {
                printf("invalid server ip address\n");
                exit(1);
            }
            continue;
        }
    }
    // 检测是否少输入了某项参数
    if (serv_addr.sin_port == 0 || serv_addr.sin_addr.s_addr == 0) {
        printf("Usage: [-p] [serv_addr.sin_port] [-a][serv_address]\n");
        exit(1);
    }

    // 创建一个TCP套接字
    conn_fd = socket(AF_INET, SOCK_STREAM,0);
    if (conn_fd < 0) {
        my_err("socket", __LINE__);
    }

    // 向服务器端发送连接请求
    if (connect(conn_fd, (struct sockaddr *)&serv_addr, sizeof (struct sockaddr)) < 0) {
        my_err("connect", __LINE__);
    }

    // 输入用户名和密码
    input_userinfo(conn_fd, "username");
    input_userinfo(conn_fd, "password");

    // 读取欢迎信息并打印出来
    if ((ret = my_recv(conn_fd, recv_buf, sizeof (recv_buf))) < 0) {
        printf("data is too long\n");
        exit(1);
    }
    for (i=0; i<ret; i++) {
        printf("%c", recv_buf[i]);
    }
    printf("\n");

    close(conn_fd);
    return 0;
}
(3)编译运行:
gcc -o server server.c
./server
gcc -o client client.c
./client -p 4507 -a 127.0.0.1

3、socket文件传输(从server端下载文件)

(1)file_server.c:
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>

#define HELLO_WORLD_SERVER_PORT 6666
#define LENGTH_OF_LISTENQ_QUEUE 20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
int main(int argc, char *argv[])
{
    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);
    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);

    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        printf("create socket failed!\n");
        exit(1);
    }
    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr))) {
        printf("bind socket failed!\n");
        exit(1);
    }
    if (listen(server_socket, LENGTH_OF_LISTENQ_QUEUE)) {
        printf("listen socket failed!\n");
        exit(1);
    }
    while (1) {
        struct sockaddr_in client_addr;
        socklen_t length = sizeof(client_addr);
        int new_server_scoket = accept(server_socket, (struct sockaddr*)&client_addr, &length);
        if (new_server_scoket < 0) {
            printf("accept socket failed!\n");
            break;
        }
        char buffer[BUFFER_SIZE];
        bzero(buffer, BUFFER_SIZE);
        length = recv(new_server_scoket, buffer, BUFFER_SIZE, 0);
        if (length < 0) {
            printf("recv data failed!\n");
            break;
        }
        char file_name[FILE_NAME_MAX_SIZE + 1];
        bzero(file_name, FILE_NAME_MAX_SIZE + 1);
        strncpy(file_name, buffer, strlen(file_name) > FILE_NAME_MAX_SIZE ? FILE_NAME_MAX_SIZE : strlen(buffer));
        FILE *fp = fopen(file_name, "r");
        if (fp == NULL) {
            printf("File: %s not found\n", file_name);
        }
        else {
            bzero(buffer, BUFFER_SIZE);
            int file_length = 0;
            while((file_length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0) {
                printf("file_length = %d\n", file_length);
                if (send(new_server_scoket, buffer, file_length, 0) < 0) {
                    printf("send file: %s failed\n", file_name);
                    break;
                }
                bzero(buffer, BUFFER_SIZE);
            }
            fclose(fp);
            printf("file: %s trandfer finished\n", file_name);
        }
        close(new_server_scoket);
    }
    close(server_socket);
    return 0;
}
(2)file_client.c:
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>

#define HELLO_WORLD_SERVER_PORT 6666
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512

int main(int argc, char *argv[]) 
{
    if (argc != 2) {
        printf("use:[./run][server_ipaddress]\n");
        exit(1);
    }
    struct sockaddr_in client_addr;
    bzero(&client_addr,sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    if (inet_aton(argv[1], &client_addr.sin_addr) == 0) {
        printf("server ip address error!\n");
        exit(1);
    }
    client_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
    socklen_t client_addr_length = sizeof(client_addr);
    int client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (client_socket < 0) {
        printf("create socket failed\n");
        exit(1);
    }
    if (connect(client_socket, (struct sockaddr*)&client_addr, client_addr_length) < 0) {
        printf("can not connect to address\n");
        exit(1);
    }
    char file_name[FILE_NAME_MAX_SIZE + 1];
    bzero(file_name, FILE_NAME_MAX_SIZE + 1);
    printf("please input file name:");
    scanf("%s", file_name);

    char buffer[BUFFER_SIZE];
    bzero(buffer, BUFFER_SIZE);
    strncpy(buffer, file_name, strlen(file_name) > BUFFER_SIZE ? BUFFER_SIZE : strlen(file_name));
    send(client_socket, buffer, BUFFER_SIZE, 0);
    FILE *fp = fopen(file_name, "w");
    if (fp == NULL) {
        printf("file can not open\n");
        exit(1);
    }
    bzero(buffer, BUFFER_SIZE);
    int length = 0;
    while (length = recv(client_socket, buffer, BUFFER_SIZE, 0)) {
        if (length < 0) {
            printf("recv failed\n");
            break;
        }
        int write_length = fwrite(buffer, sizeof(char), length, fp);
        if (write_length < length) {
            printf("file write failed\n");
            break;
        }
        bzero(buffer, BUFFER_SIZE);
    }
    printf("recieve file: %s from server %s finished!\n", file_name, argv[1]);
    close(fp);
    close(client_socket);
    return 0;
}
(3)编译运行
gcc -o server file_server.c
./server
gcc -o client file_client.c
./client 127.0.0.1
server显示:

client显示:

      注:server和client放在不同的目录下,需要传输的文件yang.txt与server在同一目录下。

4、模拟http服务器

(1)http服务器server.c:

#include <stdarg.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <signal.h>
#include <strings.h>
#define DEFAULTIP "127.0.0.1"
#define DEFAULTPORT "4040"
#define DEFAULTBACK "10"
#define DEFAULTDIR "/tmp/http/dir"
#define DEFAULTLOG "/tmp/http/log"

void prterrmsg(char *msg);
#define prterrmsg(msg)        { perror(msg); abort(); }
void wrterrmsg(char *msg);
#define wrterrmsg(msg)        { fputs(msg, logfp); fputs(strerror(errno), logfp);fflush(logfp); abort(); }

void prtinfomsg(char *msg);
#define prtinfomsg(msg)        { fputs(msg, stdout);  }
void wrtinfomsg(char *msg);
#define wrtinfomsg(msg)        {  fputs(msg, logfp); fflush(logfp);}

#define MAXBUF        1024

char buffer[MAXBUF + 1];
char *host = 0;
char *port = 0;
char *back = 0;
char *dirroot = 0;
char *logdir = 0;
unsigned char daemon_y_n = 0;
FILE *logfp;

#define MAXPATH        150

/*----------------------------------------
 *--- dir_up - 查找dirpath所指目录的上一级目录
 *----------------------------------------
 */
char *dir_up(char *dirpath)
{
    static char Path[MAXPATH];
    int len;

    strcpy(Path, dirpath);
    len = strlen(Path);
    if (len > 1 && Path[len - 1] == '/')
        len--;
    while (Path[len - 1] != '/' && len > 1)
        len--;
    Path[len] = 0;
    return Path;
}

/*------------------------------------------------------
 *--- AllocateMemory - 分配空间并把d所指的内容复制
 *------------------------------------------------------
 */
void AllocateMemory(char **s, int l, char *d)
{
    *s = (char *)malloc(l + 1);
    bzero(*s, l + 1);
    memcpy(*s, d, l);
}
/*------------------------------------------------------
 *--- GiveResponse - 把Path所指的内容发送到client_sock去
 *-------------------如果Path是一个目录,则列出目录内容
 *-------------------如果Path是一个文件,则下载文件
 *------------------------------------------------------
 */
void GiveResponse(FILE * client_sock, char *Path)
{
    struct dirent *dirent;
    struct stat info;
    char Filename[MAXPATH];
    DIR *dir;
    int fd, len, ret;
    char *p, *realPath, *realFilename, *nport;

    /* 获得实际工作目录或文件 */
    len = strlen(dirroot) + strlen(Path) + 1;
    realPath = (char *)malloc(len + 1);
    bzero(realPath, len + 1);
    sprintf(realPath, "%s/%s", dirroot, Path);

    /* 获得实际工作端口 */
    len = strlen(port) + 1;
    nport = (char *)malloc(len + 1);
    bzero(nport, len + 1);
    sprintf(nport, ":%s", port);

    /* 获得实际工作目录或文件的信息以判断是文件还是目录 */
    if (stat(realPath, &info)) {
        fprintf(client_sock,
                "HTTP/1.1 200 OK\r\nServer: DAS by Centos\r\nConnection: close\r\n\r\n"
                "<html>"
                "<meta charset=\"UTF-8\"/>"
                "<head><title>%d - %s</title></head>"
                "<body><font size=+4>Linux下目录访问服务器</font><br><hr width=\"100%%\"><br><center>"
                "<table border cols=3 width=\"100%%\">"
                , errno, strerror(errno));
        fprintf(client_sock,
                "</table><font color=\"CC0000\" size=+2>请向管理员咨询为何出现如下错误提示:\n%s %s</font></body></html>",
                Path, strerror(errno));
        goto out;
    }
    /* 处理浏览文件请求,即下载文件 */
    if (S_ISREG(info.st_mode)) {
        fd = open(realPath, O_RDONLY);
        len = lseek(fd, 0, SEEK_END);
        p = (char *) malloc(len + 1);
        bzero(p, len + 1);
        lseek(fd, 0, SEEK_SET);
        ret = read(fd, p, len);
        close(fd);
        fprintf(client_sock,
                "HTTP/1.1 200 OK\r\nServer: DAS by Centos\r\nConnection: keep-alive\r\nContent-type: application/*\r\nContent-Length:%d\r\n\r\n",
                len);
        fwrite(p, len, 1, client_sock);
        free(p);
    } else if (S_ISDIR(info.st_mode)) {
        /* 处理浏览目录请求 */
        dir = opendir(realPath);
        fprintf(client_sock,
                "HTTP/1.1 200 OK\r\nServer: DAS by Centos\r\nConnection: close\r\n\r\n"
                "<html>"
                "<meta charset=\"UTF-8\"/>"
                "<head><title>%s</title></head>"
                "<body><font size=+4>Linux下目录访问服务器</font><br><hr width=\"100%%\"><br><center>"
                "<table border cols=3 width=\"100%%\">", Path);
        fprintf(client_sock,
                "<caption><font size=+3>目录 %s</font></caption>\n",
                Path);
        fprintf(client_sock,
                "<tr><td>名称</td><td>大小</td><td>修改时间</td></tr>\n");
        if (dir == 0) {
            fprintf(client_sock,
                    "</table><font color=\"CC0000\" size=+2>%s</font></body></html>",
                    strerror(errno));
            return;
        }
        /* 读取目录里的所有内容 */
        while ((dirent = readdir(dir)) != 0) {
            if (strcmp(Path, "/") == 0)
                sprintf(Filename, "/%s", dirent->d_name);
            else
                sprintf(Filename, "%s/%s", Path, dirent->d_name);
            fprintf(client_sock, "<tr>");
            len = strlen(dirroot) + strlen(Filename) + 1;
            realFilename = (char *)malloc(len + 1);
            bzero(realFilename, len + 1);
            sprintf(realFilename, "%s/%s", dirroot, Filename);
            if (stat(realFilename, &info) == 0) {
                if (strcmp(dirent->d_name, "..") == 0)
                    fprintf(client_sock,
                            "<td><a href=\"http://%s%s%s\">(parent)</a></td>",
                            host, atoi(port) == 80 ? "" : nport,
                            dir_up(Path));
                else
                    fprintf(client_sock,
                            "<td><a href=\"http://%s%s%s\">%s</a></td>",
                            host, atoi(port) == 80 ? "" : nport, Filename,
                            dirent->d_name);
                if (S_ISDIR(info.st_mode))
                    fprintf(client_sock, "<td>目录</td>");
                else if (S_ISREG(info.st_mode))
                    fprintf(client_sock, "<td>%d</td>", info.st_size);
                else if (S_ISLNK(info.st_mode))
                    fprintf(client_sock, "<td>链接</td>");
                else if (S_ISCHR(info.st_mode))
                    fprintf(client_sock, "<td>字符设备</td>");
                else if (S_ISBLK(info.st_mode))
                    fprintf(client_sock, "<td>块设备</td>");
                else if (S_ISFIFO(info.st_mode))
                    fprintf(client_sock, "<td>FIFO</td>");
                else if (S_ISSOCK(info.st_mode))
                    fprintf(client_sock, "<td>Socket</td>");
                else
                    fprintf(client_sock, "<td>(未知)</td>");
                fprintf(client_sock, "<td>%s</td>", ctime(&info.st_ctime));
            }
            fprintf(client_sock, "</tr>\n");
            free(realFilename);
        }
        fprintf(client_sock, "</table></center></body></html>");
    } else {
        /* 既非常规文件又非目录,禁止访问 */
        fprintf(client_sock,
                "HTTP/1.1 200 OK\r\nServer: DAS by Centos\r\nConnection: close\r\n\r\n"
                "<html>"
                "<meta charset=\"UTF-8\"/>"
                "<head><title>permission denied</title></head>"
                "<body><font size=+4>Linux下目录访问服务器</font><br><hr width=\"100%%\"><br><center>"
                "<table border cols=3 width=\"100%%\">");
        fprintf(client_sock,
                "</table><font color=\"CC0000\" size=+2>你访问的资源'%s'被禁止访问,请联系管理员解决!</font></body></html>",
                Path);
    }
out:
    free(realPath);
    free(nport);
}

/*------------------------------------------------------
 *--- getoption - 分析取出程序的参数
 *------------------------------------------------------
 */
void getoption(int argc, char **argv)
{
    //    int c, len;
    //    char *p = 0;
    //
    //    opterr = 0;
    //    while (1) {
    //        int option_index = 0;
    //        static struct option long_options[] = {
    //            {"host", 1, 0, 0},
    //            {"port", 1, 0, 0},
    //            {"back", 1, 0, 0},
    //            {"dir", 1, 0, 0},
    //            {"log", 1, 0, 0},
    //            {"daemon", 0, 0, 0},
    //            {0, 0, 0, 0}
    //        };
    //        /* 本程序支持如一些参数:
    //         * --host IP地址 或者 -H IP地址
    //         * --port 端口 或者 -P 端口
    //         * --back 监听数量 或者 -B 监听数量
    //         * --dir 网站根目录 或者 -D 网站根目录
    //         * --log 日志存放路径 或者 -L 日志存放路径
    //         * --daemon 使程序进入后台运行模式
    //         */
    //        c = getopt_long(argc, argv, "H:P:B:D:L",
    //                        long_options, &option_index);
    //        if (c == -1 || c == '?')
    //            break;
    //
    //        if(optarg)        len = strlen(optarg);
    //        else        len = 0;
    //
    //        if ((!c && !(strcasecmp(long_options[option_index].name, "host")))
    //            || c == 'H')
    //            p = host = malloc(len + 1);
    //        else if ((!c
    //                  &&
    //                  !(strcasecmp(long_options[option_index].name, "port")))
    //                 || c == 'P')
    //            p = port = malloc(len + 1);
    //        else if ((!c
    //                  &&
    //                  !(strcasecmp(long_options[option_index].name, "back")))
    //                 || c == 'B')
    //            p = back = malloc(len + 1);
    //        else if ((!c
    //                  && !(strcasecmp(long_options[option_index].name, "dir")))
    //                 || c == 'D')
    //            p = dirroot = malloc(len + 1);
    //        else if ((!c
    //                  && !(strcasecmp(long_options[option_index].name, "log")))
    //                 || c == 'L')
    //            p = logdir = malloc(len + 1);
    //        else if ((!c
    //                  &&
    //                  !(strcasecmp
    //                    (long_options[option_index].name, "daemon")))) {
    //            daemon_y_n = 1;
    //            continue;
    //        }
    //        else
    //            break;
    //        bzero(p, len + 1);
    //        memcpy(p, optarg, len);
    //    }
}

int main(int argc, char **argv)
{
    struct sockaddr_in addr;
    int sock_fd, addrlen;

    /* 获得程序工作的参数,如 IP 、端口、监听数、网页根目录、目录存放位置等 */
    //getoption(argc, argv);

    if (!host) {
        addrlen = strlen(DEFAULTIP);
        AllocateMemory(&host, addrlen, DEFAULTIP);
    }
    if (!port) {
        addrlen = strlen(DEFAULTPORT);
        AllocateMemory(&port, addrlen, DEFAULTPORT);
    }
    if (!back) {
        addrlen = strlen(DEFAULTBACK);
        AllocateMemory(&back, addrlen, DEFAULTBACK);
    }
    if (!dirroot) {
        addrlen = strlen(DEFAULTDIR);
        AllocateMemory(&dirroot, addrlen, DEFAULTDIR);
    }
    if (!logdir) {
        addrlen = strlen(DEFAULTLOG);
        AllocateMemory(&logdir, addrlen, DEFAULTLOG);
    }

    printf
            ("host=%s port=%s back=%s dirroot=%s logdir=%s %s是后台工作模式(进程ID:%d)\n",
             host, port, back, dirroot, logdir, daemon_y_n?"":"不", getpid());

    /* fork() 两次处于后台工作模式下 */
    if (daemon_y_n) {
        if (fork())
            exit(0);
        if (fork())
            exit(0);
        close(0), close(1), close(2);
        logfp = fopen(logdir, "a+");
        if (!logfp)
            exit(0);
    }

    /* 处理子进程退出以免产生僵尸进程 */
    signal(SIGCHLD, SIG_IGN);

    /* 创建 socket */
    if ((sock_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
        if (!daemon_y_n) {
            prterrmsg("socket()");
        } else {
            wrterrmsg("socket()");
        }
    }

    /* 设置端口快速重用 */
    addrlen = 1;
    setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &addrlen,
               sizeof(addrlen));

    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(port));
    addr.sin_addr.s_addr = htonl(INADDR_ANY);//htonl(INADDR_ANY);
    addrlen = sizeof(struct sockaddr_in);
    /* 绑定地址、端口等信息 */
    if (bind(sock_fd, (struct sockaddr *) &addr, addrlen) < 0) {
        if (!daemon_y_n) {
            prterrmsg("bind()");
        } else {
            wrterrmsg("bind()");
        }
    }
    /* 开启临听 */
    if (listen(sock_fd, atoi(back)) < 0) {
        if (!daemon_y_n) {
            prterrmsg("listen()");
        } else {
            wrterrmsg("listen()");
        }
    }
    while (1) {
        int len;
        int new_fd;
        addrlen = sizeof(struct sockaddr_in);
        /* 接受新连接请求 */
        new_fd = accept(sock_fd, (struct sockaddr *) &addr, (socklen_t *)&addrlen);
        if (new_fd < 0) {
            if (!daemon_y_n) {
                prterrmsg("accept()");
            } else {
                wrterrmsg("accept()");
            }
            break;
        }
        bzero(buffer, MAXBUF + 1);
        sprintf(buffer, "连接来自于: %s:%d\n",
                inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
        if (!daemon_y_n) {
            prtinfomsg(buffer);
        } else {
            wrtinfomsg(buffer);
        }
        /* 产生一个子进程去处理请求,当前进程继续等待新的连接到来 */
        if (!fork()) {
            bzero(buffer, MAXBUF + 1);
            puts("recving...");
            if ((len = recv(new_fd, buffer, MAXBUF, 0)) > 0) {
                FILE *ClientFP = fdopen(new_fd, "w");
                if (ClientFP == NULL) {
                    if (!daemon_y_n) {
                        prterrmsg("fdopen()");
                    } else {
                        prterrmsg("fdopen()");
                    }
                } else {
                    char Req[MAXPATH + 1] = "";
                    sscanf(buffer, "GET %s HTTP", Req);
                    bzero(buffer, MAXBUF + 1);
                    sprintf(buffer, "请求取文件: \"%s\"\n", Req);
                    if (!daemon_y_n) {
                        prtinfomsg(buffer);
                    } else {
                        wrtinfomsg(buffer);
                    }
                    /* 处理用户请求 */
                    GiveResponse(ClientFP, Req);
                    fclose(ClientFP);
                }
            }
            puts("go out");
            exit(0);
        }
        close(new_fd);
    }
    close(sock_fd);
    return 0;
}
(2)编译运行(浏览器为客户端):
gcc -o server server.c
./server 
服务器运行:
在浏览器中输入:http://127.0.0.1:4040

注:需建立/tmp/http/dir和/tmp/http/log文件夹,并在dir下建立目录树


然后在浏览器中点击文件即可下载文件,点击目录可进一步进行查看。点击parent可以返回到上一级目录。

(3)http客户端client.c(从服务器上下载文件并重新命名)
#include <stdlib.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <strings.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include<errno.h>
int main(int argc, char *argv[])
{
    char buffer[1024] = {0};
    char host_addr[256] = {0};
    char host_file[256] = {0};
    char local_file[256] = {0};
    int sockfd;
    int send, totalsend;
    int nbytes;
    char request[1024] = {0};
    struct sockaddr_in server_addr;
    struct hostent *host;
    if (argc != 2) {
        fprintf(stderr, "Usage:%s web-address!\n", argv[0]);
        exit(1);
    }
    int portnumber = 4040;
    strcpy(host_addr, argv[1]);
    strcpy(host_file, "server.c");
    if ((host = gethostbyname(argv[1])) == NULL) {
        fprintf(stderr,"Gethostname error\n", strerror(errno));
        exit(1);
    }
    char ip_str[32] = {0};
    printf("address: %s\n", inet_ntop(host->h_addrtype, host->h_addr, ip_str, sizeof(ip_str)));
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        fprintf(stderr,"Socket Error:%s!\n",strerror(errno));
        exit(1);
    }
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(portnumber);
    server_addr.sin_addr = *((struct in_addr *)host->h_addr);
    if (connect(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1) {
        fprintf(stderr,"Connect Error:%s!\n",strerror(errno));
        exit(1);
    }
    sprintf(request, "GET /%s HTTP/1.1\r\nAccept: */*\r\nAccept-Language: zh-cn\r\n  \
            User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n  \
            Host: %s:%d\r\nConnection: Close\r\n\r\n", host_file, host_addr, portnumber);
    strcpy(local_file, "local.txt");
    send = 0;
    totalsend = 0;
    nbytes = strlen(request);
    while (totalsend < nbytes) {
        send = write(sockfd, request + totalsend, nbytes - totalsend);
        if (send == -1) {
            printf("send error:%s!\n", strerror(errno));
            exit(0);
        }
        totalsend += send;
        printf("%d bytes send OK!\n", totalsend);
    }
    FILE * fp = fopen(local_file, "a");
    if(!fp) {
        printf("create file error:%s!\n", strerror(errno));
        return 0;
    }
    //printf("The following is the response header:\n");
    int i = 0;
    /* 连接成功了,接收http响应,response */
    while((nbytes = read(sockfd, buffer, 1)) == 1) {
        if (i < 4) {
            if (buffer[0] == '\r' || buffer[0] == '\n')
                i++;
            else
                i = 0;
            //printf("%c", buffer[0]);
        }
        else {
            fwrite(buffer, 1, 1, fp);/*将http主体信息写入文件*/
            //printf("%c", buffer[0]);
            i++;
            if(i % 1024 == 0)
                fflush(fp);   /**每1K时存盘一次**/
        }
    }
    printf("\n");
    fclose(fp);
    /* 结束通讯 */
    close(sockfd);
    return 0;
}

在客户端程序的当前的目录下下载了local.txt,其中的内容是服务器上server.c文件的内容。

三、总结

(1)上述代码在centos上测试通过,只是功能实现,代码执行效率暂未考虑。
(2)若有建议,请留言,在此先感谢!

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乌托邦2号

博文不易,支持的请给予小小打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值