约定协议:
客户端发送请求: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