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