您的位置:首页 > 运维架构 > Linux

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

2017-09-29 16:29 495 查看
转自:http://blog.csdn.net/taiyang1987912/article/details/49738351


一、简介

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


二、详解

1、Linux下tcp端口扫描

(1)scanport.c:

[html] view
plain copy

// 端口扫描程序,只支持扫描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)编译运行

[html] view
plain copy

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:

[html] view
plain copy

// 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:

[html] view
plain copy

#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)编译运行:

[html] view
plain copy

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:

[html] view
plain copy

#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:

[html] view
plain copy

#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)编译运行

[html] view
plain copy

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:

[html] view
plain copy

#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)编译运行(浏览器为客户端):

[html] view
plain copy

gcc -o server server.c

./server

服务器运行:


在浏览器中输入:http://127.0.0.1:4040



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



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

(3)http客户端client.c(从服务器上下载文件并重新命名)

[html] view
plain copy

#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)若有建议,请留言,在此先感谢!

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: