Linux socket网络编程实现FTP服务器
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
1.TCP和UDP是什么?
TCP:
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 定义。
UDP:
Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。RFC 768 描述了 UDP。
区别:
*TCP面向连接,通过三次握手建立连接,四次挥手接除连接;UDP是无连接的,即发送数据之前不需要建立连接,这种方式为UDP带来了高效的传输效率,但也导致无法确保数据的发送成功。
*TCP是可靠的通信方式。通过TCP连接传送的数据,确保数据无差错,不丢失,不重复,且按序到达;而UDP由于无需连接的原因,将会以最大速度进行传输,但不保证可靠交付,也就是会出现丢失、重复等等问题。
*TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流,由于连接的问题,当网络出现波动时,连接可能出现响应问题;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低。
*每一条TCP连接只能是点到点的;而UDP不建立连接,所以可以支持一对一,一对多,多对一和多对多的交互通信,也就是可以同时接受多个人的包。
*TCP需要建立连接,首部开销20字节相比8个字节的UDP显得比较大。
*TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
2.端口:
端口的概念:
在网络技术中,端口(Port)大致有两种意思:一是物理意义上的端口,比如,ADSL Modem、集线器、交换机、路由器用于连接其他网络设备的接口,如RJ-45端口、SC端口等等。二是逻辑意义上的端口,一般是指TCP/IP协议中的端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等等。
我们这里将要介绍的就是逻辑意义上的端口。我们这里所说的端口,不是计算机硬件的I/O端口,而是软件形式上的概念.工具提供服务类型的不同,端口分为两种,一种是TCP端口,一种是UDP端口。计算机之间相互通信的时候,分为两种方式:一种是发送信息以后,可以确认信息是否到达,也就是有应答的方式,这种方式大多采用TCP协议;一种是发送以后就不管了,不去确认信息是否到达,这种方式大多采用UDP协议。对应这两种协议的服务提供的端口,也就分为TCP端口和UDP端口。
端口的作用:
首先,tcp的连接是两个进程间的通信,端口号就是为了区分同一计算机上的不同进程,端口号本质上就是一个整型。
3.字节序:
多字节数据存储在存储器中的顺序就叫做字节序。字节序又分为俩种,一种叫做小端字节序;另外一种叫做大端字节序。
大端字节序:在大端字节序的机器中,首先会存储多字节数据类型的二进制表示的第一个字节;
小端字节序:在小端字节序的机器中,首先会存储多字节数据类型的二进制表示的最后一个字节;
*网络协议指定了通讯字节序—大端
*只有在多字节数据作为整体处理时才需要考虑字节序
*运行在同一台计算机上的进程相互通信时,一般不用考虑字节序
*异构计算机之间通讯,需要转换自己的字节序为网络字节序
简单的总结大小端存储方式:
*Big Endian 是指低地址端 存放 高位字节。
*Little Endian 是指低地址端 存放 低位字节。
4.字节序转换函数:
uint16_t htons(uint16_t hostint16);
功能:
将16位主机字节序数据转换成网络字节序数据
参数:
uint16_t:unsigned short int
hostint16:待转换的16位主机字节序数据
返回值:
成功:返回网络字节序的值
头文件:
#include <arpa/inet.h>
uint16_t ntohs(uint16_t netint16);//将16位网络字节序数据转换成主机字节序数据
uint32_t htonl(uint32_t hostint32);//将32位主机字节序数据转换成网络字节序数据
uint32_t ntohl(uint32_t netint32);//将32位网络字节序数据转换成主机字节序数据
5.地址转换API:
int inet_aton(const char *cp, struct in_addr *inp);
/*inet_aton
函数将网络主机地址cp
从 IPv4 的点分十格式转换为二进制值(以网络字节序)并且把它保存在inp
指针指向的结构体中。如果地址是合法的,那么inet_aton
函数返回非0值,反之返回0值。*/
char *inet_ntoa(struct in_addr in);
/*inet_ntoa
函数将网络主机地址in
转换为点分十格式的 IPv4 地址。该函数的返回值所指向的字符串驻留在静态内存中,后续调用将覆盖该缓冲区。*/
6.实现socket服务器的过程:
*socket创建套接字:int socket(int protofamily, int type, int protocol);//返回描述符sockfd
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
- protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
- type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET。
- protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
*为套接字添加信息(IP地址端口号)int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
- addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同。
- 如ipv4对应的是:
struct sockaddr_in {sa_family_t sin_family; /* address family: AF_INET */in_port_t sin_port; /* port in network byte order */struct in_addr sin_addr; /* internet address */ };/* Internet address. */ struct in_addr {uint32_t s_addr; /* address in network byte order */ };
- addrlen:对应的是地址的长度。
*监听并建立网络连接
**服务端调用 int listen(int sockfd, int backlog);函数监听客户端的连接:
listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
**客户端调用int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);函数建立与服务端的连接:
connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。
**服务端在listen()监听之后调用int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);函数接受conneet()的请求连接建立完成
参数sockfd:就是上面解释中的监听套接字。
参数addr:一个const struct sockaddr *指针,指向存储客户端地址的结构体(这个结构体和bind()函数中的相同),如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。
参数len:用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。
如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。
注意:
accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。
监听套接字: 监听套接字正如accept的参数sockfd,它是监听套接字,在调用listen函数之后,是服务器开始调用socket()函数生成的,称为监听socket描述字(监听套接字)。
连接套接字:accept函数返回的是已连接socket描述字(一个连接套接字),它代表着一个网络已经存在的点点连接。
自然要问的是:为什么要有两种套接字?原因很简单,如果使用一个描述字的话,那么它的功能太多,使得使用很不直观,同时在内核确实产生了一个这样的新的描述字。
*数据交互:read()、write()等函数
- read()/write()
- recv()/send()
- readv()/writev()
- recvmsg()/sendmsg()
- recvfrom()/sendto()
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
/*在服务端中参数fd是accept()返回的连接套接字,在客户端中fd是socket()返回的套接字,其他参数都和文件中的open(),read()相同。*/
*关闭套接字:int close(int fd);
需要分别关闭监听套接字和连接套接字。
FTP服务端代码:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include "config.h"
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>char *getDesDir(char *cmsg)
{char *p;p=strtok(cmsg," ");p=strtok(NULL," ");return p;
}int get_cmd_type(char *cmd)
{if(!strcmp("ls",cmd)) return LS;if(!strcmp("quit",cmd)) return QUIT;if(!strcmp("pwd",cmd)) return PWD;if(strstr(cmd,"cd") != NULL) return CD;if(strstr(cmd,"get") != NULL) return GET;if(strstr(cmd,"put") != NULL) return PUT; return 100;
}void msg_handler(struct Msg msg,int fd)
{char cmdBuf[1024]={0};int ret;char *file=NULL;int fdfile;FILE *r=NULL;printf("smd:%s\n",msg.cmd);//打印指令ret=get_cmd_type(msg.cmd);switch(ret){case LS:case PWD:msg.type=0;r=popen(msg.cmd,"r");//popen可以将执行的结果写入指针r指向的地址中fread(msg.cmd,sizeof(msg.cmd),1,r);//将r中的内容度到msg.cmd中write(fd,&msg,sizeof(msg));//将结构体msg发送给客户端break;case CD:msg.type=1;char *dir=getDesDir(msg.cmd);printf("dir:%s\n",dir);chdir(dir); //调用chdir()函数移至此文件下break;case GET:file=getDesDir(msg.cmd);//找到要下载的文件名if(access(file,F_OK) == -1){ //access函数判断文件是否存在strcpy(msg.cmd,"No This File!");write(fd,&msg,sizeof(msg));//将提示信息发送给客户端}else{msg.type=DOFILE;fdfile=open(file,O_RDWR);//打开目标文件read(fdfile,cmdBuf,sizeof(cmdBuf));//将文件内容读到cmdBuf中strcpy(msg.cmd,cmdBuf);//复制文件write(fd,&msg,sizeof(msg));//将内容发送给客户端}break;case PUT:fdfile=open(getDesDir(msg.cmd),O_RDWR|O_CREAT|0666);write(fdfile,msg.secondBuf,strlen(msg.secondBuf));close(fdfile);break;case QUIT:printf("client quit!\n");exit(-1);}
}int main(int argc,char **argv)
{int s_fd;//监听套接字int c_fd;//连接套接字int n_read;int addrlen;struct sockaddr_in s_addr;//定义socket中的结构体struct sockaddr_in c_addr;//定义accept中的结构体struct Msg msg;//命令接收结构体memset(&s_addr,0,sizeof(struct sockaddr_in));memset(&c_addr,0,sizeof(struct sockaddr_in));if(argc != 3){printf("data error!");exit(-1);}s_fd=socket(AF_INET,SOCK_STREAM,0);//创建套接字并判断if(s_fd == -1){perror("socket:");exit(-1);}s_addr.sin_family=AF_INET;//确定TCP/IP协议s_addr.sin_port=htons(atoi(argv[2]));//将端口号转变为网络字节序inet_aton(argv[1],&s_addr.sin_addr);//将地址转变为网络字节序bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//给套接字添加信息listen(s_fd,10);//监听,规定可以有10个客户端接入while(1){addrlen=sizeof(struct sockaddr_in);c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&addrlen);//阻塞等待连接并返回连接套接字if(fork() == 0){ //进入子进程while(1){n_read=read(c_fd,&msg,sizeof(msg));//接收客户端发送的信息放入结构体msg中if(n_read == 0){printf("client out\n");break;}else{msg_handler(msg,c_fd);//调用命令处理函数} }}}close(c_fd);close(s_fd);return 0;
}
FTP客户端代码:
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include "config.h"
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>char *getDesDir(char *cmsg)
{char *p;p=strtok(cmsg," ");p=strtok(NULL," ");return p;
}int get_cmd_type(char *cmd)
{if(!strcmp("ls",cmd)) return LS;if(!strcmp("quit",cmd)) return QUIT;if(!strcmp("pwd",cmd)) return PWD;if(strstr(cmd,"cd") != NULL) return CD;if(strstr(cmd,"get") != NULL) return GET;if(strstr(cmd,"put") != NULL) return PUT;if(!strcmp("lls",cmd)) return LLS;if(strstr(cmd,"lcd") != NULL) return LCD;return -1;
}int cmd_handler(struct Msg msg,int fd)
{char *dir=NULL;char buf[32];int ret;int filefd;ret=get_cmd_type(msg.cmd);switch(ret){case LS:case CD:case PWD:msg.type=0;write(fd,&msg,sizeof(msg));break;case GET:msg.type=2;write(fd,&msg,sizeof(msg));break;case PUT:strcpy(buf,msg.cmd);dir=getDesDir(buf);if(access(dir,F_OK) == -1){printf("%s not exsit\n",dir); }else{filefd=open(dir,O_RDWR);read(filefd,msg.secondBuf,sizeof(msg.secondBuf));close(filefd);write(fd,&msg,sizeof(msg)); } break;case LLS:system("ls");break;case LCD:dir=getDesDir(msg.cmd);chdir(dir);break;case QUIT:strcpy(msg.cmd,"quit");write(fd,&msg,sizeof(msg));close(fd);exit(-1);}return ret;
}void handler_server_message(int fd,struct Msg msg)
{int n_read;int new_file_fd;struct Msg msg_get;n_read=read(fd,&msg_get,sizeof(msg_get));//从服务器接收信息if(n_read == 0){printf("server is out,quit\n");exit(-1); }if(msg_get.type == DOFILE){char *p=getDesDir(msg.cmd);new_file_fd=open(p,O_RDWR|O_CREAT|0600);write(new_file_fd,msg_get.cmd,strlen(msg_get.cmd));putchar('>');close(new_file_fd);fflush(stdout);}else{printf("................................\n");printf("\n%s\n",msg_get.cmd);printf("................................\n");putchar('>');fflush(stdout);}
}int main(int argc,char **argv)
{int c_fd;struct sockaddr_in c_addr;//客户端不需要bind,listen,accept,只用一个套接字struct Msg msg;if(argc != 3){printf("data error\n");exit(-1);}memset(&c_addr,0,sizeof(struct sockaddr_in));memset(&msg,0,sizeof(msg));c_fd=socket(AF_INET,SOCK_STREAM,0);//建立套接字if(c_fd == -1){perror("socket:");exit(-1);}c_addr.sin_family=AF_INET;//确定协议c_addr.sin_port=htons(atoi(argv[2]));//端口inet_aton(argv[1],&c_addr.sin_addr);//地址if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){perror("connect");//申请连接}else{printf("connect ....\n"); } int mark=0;while(1){memset(msg.cmd,0,sizeof(msg.cmd));if(mark == 0){printf(">");}gets(msg.cmd);//获取指令if(strlen(msg.cmd) == 0){if(mark == 1){printf(">");}continue; }mark=1;int ret=cmd_handler(msg,c_fd);//调用指令函数if(ret>IFGO){putchar('>');fflush(stdout);continue;}if(ret == -1){printf("command not\n");fflush(stdout);continue;}handler_server_message(c_fd,msg);}return 0;
}
头文件:
#define LS 0
#define GET 1
#define PWD 2
#define CD 3#define IFGO 4#define LCD 5
#define LLS 6
#define PUT 7#define QUIT 8
#define DOFILE 9struct Msg
{int type;char cmd[1024];char secondBuf[128];
};
Linux socket网络编程实现FTP服务器相关推荐
- Socket网络编程--简单Web服务器(6)
本来是想实现ssl连接的,但是弄了好久都不成功,就索性不做了,等以后有能力再做了.所以这一小节就是本次的最后一节了.就简单的说几个注意点. 1.加个配置文件 使用单例模式,使用一个类,该类保存一些信息 ...
- 【linux高级程序设计】(第十三章)Linux Socket网络编程基础 2
BSD Socket网络编程API 创建socket对象 int socket (int __domain, int __type, int __protocol) :成功返回socket文件描述符, ...
- C# Socket网络编程入门(服务器与客户端通信,客户端与客户端通信)
WebSocket全双工通讯链接,用于前台和后台自由发送信息 一.效果展示: 效果描述: 1.服务器充当管理员,给所有人发送信息,除服务器以外其他人都能接受到. 2.其他用户发送信息除自己以外其他用户 ...
- Linux Socket网络编程UDP、TCP 阻塞与非阻塞 断线重连机制
三种非阻塞模式的方法: (1) fcntl函数 int Mode = fcntl(sockfd, F_GETFL, 0); //获取文件的Mode值 fcntl(sockfd, F ...
- 网络编程实现FTP服务器
1:FTP 文件传输协议(File Transfer Protocol,FTP)是Internet上使用最广泛的文件传送协议,它是TCP/IP协议族中的协议之一,其目标是提高文件的共享性,提供可靠高效 ...
- Linux socket 网络编程 常用头文件
一 三种类型的套接字: 1.流式套接字(SOCKET_STREAM) 提供面向连接的可靠的数据传输服务.数据被看作是字节流,无长度限制.例如FTP协议就采用这种. 2.数据报式套接字(SOCKET_D ...
- Linux socket 网络编程常用函数总结
1.字节序函数 #include <netinet.h> uint16_t htons(uint16_t host16bitvalue); uint32_t htonl(uint32_t ...
- Socket网络编程--简单Web服务器(2)
上一小节通过阅读开源的Web服务器--tinyhttpd.大概知道了一次交互的请求信息和应答信息的具体过程.接下来我就自己简单的实现一个Web服务器. 下面这个程序只是实现一个简单的框架出来.这次先实 ...
- Socket网络编程--简单Web服务器(3)
上一小节已经实现了浏览器发送请求,然后服务器给出应答信息,然后浏览器显示出服务器发送过来的网页.一切看起来都是那么的美好.这一小节就准备实现可以根据地址栏url的不同来返回指定的网页.目前还不考虑带参 ...
最新文章
- BERT为什么是NLP的革新者
- 不用 IDE 手工创建、开发、编译、安装 Android 应用程
- Android MVP 设计模式
- 用AI击破传统行业痛点 “百度大脑行业创新论坛”将提7大行业解决方案
- oracle 运营维护_oracle运维(持续更新)
- 来腾讯云开发者实验室 学习.NET
- Java——集合(模拟斗地主洗牌和发牌进行排序)
- Nginx、Haproxy、LVS负载均衡从原理到部署(一)
- ant编辑java忽略注释_java – 注释不起作用
- puppet成长日记四 Exec资源详细介绍及案例分析
- ibm刀片服务器系统瘫痪,ibm刀片服务器系统安装资料
- Java依赖包下载地址
- 文献检索是利用计算机对文献,文献检索计算机课.ppt
- Unity下载安装和Android打包成APK
- 最新勒索软件WannaCrypt病毒感染前后应对措施
- 【coq】函数语言设计 笔记 08 - maps
- spring boot V部落 V人事项目
- 洛谷 P1719 最大加权矩形 (前缀和,动态规划)
- 马自达CX-5,中控台的点烟器没电
- php免费利用飞信发送验证码,PHP 使用飞信 API 来发送免费短信