服务器端先初始化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服务器相关推荐

  1. Socket网络编程--简单Web服务器(6)

    本来是想实现ssl连接的,但是弄了好久都不成功,就索性不做了,等以后有能力再做了.所以这一小节就是本次的最后一节了.就简单的说几个注意点. 1.加个配置文件 使用单例模式,使用一个类,该类保存一些信息 ...

  2. 【linux高级程序设计】(第十三章)Linux Socket网络编程基础 2

    BSD Socket网络编程API 创建socket对象 int socket (int __domain, int __type, int __protocol) :成功返回socket文件描述符, ...

  3. C# Socket网络编程入门(服务器与客户端通信,客户端与客户端通信)

    WebSocket全双工通讯链接,用于前台和后台自由发送信息 一.效果展示: 效果描述: 1.服务器充当管理员,给所有人发送信息,除服务器以外其他人都能接受到. 2.其他用户发送信息除自己以外其他用户 ...

  4. Linux Socket网络编程UDP、TCP 阻塞与非阻塞 断线重连机制

    三种非阻塞模式的方法: (1) fcntl函数 int Mode = fcntl(sockfd, F_GETFL, 0);       //获取文件的Mode值     fcntl(sockfd, F ...

  5. 网络编程实现FTP服务器

    1:FTP 文件传输协议(File Transfer Protocol,FTP)是Internet上使用最广泛的文件传送协议,它是TCP/IP协议族中的协议之一,其目标是提高文件的共享性,提供可靠高效 ...

  6. Linux socket 网络编程 常用头文件

    一 三种类型的套接字: 1.流式套接字(SOCKET_STREAM) 提供面向连接的可靠的数据传输服务.数据被看作是字节流,无长度限制.例如FTP协议就采用这种. 2.数据报式套接字(SOCKET_D ...

  7. Linux socket 网络编程常用函数总结

    1.字节序函数 #include <netinet.h> uint16_t htons(uint16_t host16bitvalue); uint32_t htonl(uint32_t  ...

  8. Socket网络编程--简单Web服务器(2)

    上一小节通过阅读开源的Web服务器--tinyhttpd.大概知道了一次交互的请求信息和应答信息的具体过程.接下来我就自己简单的实现一个Web服务器. 下面这个程序只是实现一个简单的框架出来.这次先实 ...

  9. Socket网络编程--简单Web服务器(3)

    上一小节已经实现了浏览器发送请求,然后服务器给出应答信息,然后浏览器显示出服务器发送过来的网页.一切看起来都是那么的美好.这一小节就准备实现可以根据地址栏url的不同来返回指定的网页.目前还不考虑带参 ...

最新文章

  1. BERT为什么是NLP的革新者
  2. 不用 IDE 手工创建、开发、编译、安装 Android 应用程
  3. Android MVP 设计模式
  4. 用AI击破传统行业痛点 “百度大脑行业创新论坛”将提7大行业解决方案
  5. oracle 运营维护_oracle运维(持续更新)
  6. 来腾讯云开发者实验室 学习.NET
  7. Java——集合(模拟斗地主洗牌和发牌进行排序)
  8. Nginx、Haproxy、LVS负载均衡从原理到部署(一)
  9. ant编辑java忽略注释_java – 注释不起作用
  10. puppet成长日记四 Exec资源详细介绍及案例分析
  11. ibm刀片服务器系统瘫痪,ibm刀片服务器系统安装资料
  12. Java依赖包下载地址
  13. 文献检索是利用计算机对文献,文献检索计算机课.ppt
  14. Unity下载安装和Android打包成APK
  15. 最新勒索软件WannaCrypt病毒感染前后应对措施
  16. 【coq】函数语言设计 笔记 08 - maps
  17. spring boot V部落 V人事项目
  18. 洛谷 P1719 最大加权矩形 (前缀和,动态规划)
  19. 马自达CX-5,中控台的点烟器没电
  20. php免费利用飞信发送验证码,PHP 使用飞信 API 来发送免费短信

热门文章

  1. Can't clobber writable file **************
  2. oralce函数大全
  3. 镭神16线激光雷达跑SC-LeGo-LOAM算法
  4. 修改谷歌和火狐浏览器滚动条的样式
  5. i9 12900hk参数 i9 12900HK功耗
  6. 2020-10-29 实验四 进程同步与通信
  7. 史上最全的主流java开发工具(一)
  8. 加州洛杉矶计算机研究生,加州洛杉矶计算机硕士文书要求
  9. Cloudera是个什么东西
  10. KB927917解决方法