c语言socket通讯和进行进行大文件分片传输

c 专栏收录该内容
1 篇文章 0 订阅

c语言socket通讯和进行进行大文件分片传输

server端代码和client端代码

在windows上创建Cygwing工程11socket-server,创建完成后启动工程看不到效果,必须放到linux上启动工程才可以看到效果

服务端代码

#include <arpa/inet.h>
#include <asm/byteorder.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/unistd.h>

#define SERVER_PORT 6666
/*
 监听后,一直处于accept阻塞状态,
 直到有客户端连接,
 当客户端如数quit后,断开与客户端的连接
 */
int main() {
  //调用socket返回的文件描述符
  int server_socket;
  //调用accpet返回的文件描述符
  int client_socket;
  //声明两个套接字sockaddr_in结构体变量,分别表示客户端和服务器
  struct sockaddr_in server_addr;
  struct sockaddr_in client_addr;
  int addr_len = sizeof(client_addr);

  char buffer[1024*1024];
  int data_size;

  //创建socket,返回文件描述符
  if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket");
    return 1;
  }

  //清空server_addr
  bzero(&server_addr, sizeof(server_addr));
  //初始化服务器端的套接字,并用htons和htonl将端口和地址转成网络字节序
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(SERVER_PORT);
  //ip可是是本服务器的ip,也可以用宏INADDR_ANY代替,代表0.0.0.0,表明所有地址
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

  //将socket文件描述符和socket绑定
  if (bind(server_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
    perror("bind");
    return 1;
  }

  //设置服务器上的socket为监听状态
  if (listen(server_socket, 5) < 0) {
    perror("listen");
    return 1;
  }
  while (1) {
    printf("监听端口: %d\n", SERVER_PORT);
    //接受发送到server_socket中的清求,将请求方的ip和端口写入到client_addr,并返回接受数据的文件描述符
    client_socket = accept(server_socket, (struct sockaddr*) &client_addr, (socklen_t*) &addr_len);
    if (client_socket < 0) {
      perror("accept");
      continue;
    }
    printf("等待消息...\n");

    printf("peer ip   : %s\n", inet_ntoa(client_addr.sin_addr));
    printf("peer port : %d\n", htons(client_addr.sin_port));
    while (1) {
      printf("读取消息:");
      buffer[0] = '\0';
      data_size = recv(client_socket, buffer, 1024*1024, 0);
      if (data_size < 0) {
        perror("recv null");
        continue;
      }
      buffer[data_size] = '\0';
      if (strcmp(buffer, "quit") == 0)
        break;
      printf("%s\n", buffer);

      printf("发送消息:");
      scanf("%s", buffer);
      printf("\n");
      send(client_socket, buffer, strlen(buffer), 0);
      if (strcmp(buffer, "quit") == 0)
        break;
    }
  }
  close(server_socket);
  return 0;
}

客户端代码

#include <arpa/inet.h>
#include <asm/byteorder.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/unistd.h>

#define SERVER_PORT 6666
/*
 连接到服务器后,会不停循环,等待输入,
 输入quit后,断开与服务器的连接
 */
int main() {
  //客户端套接字文件描述符
  int client_socket;
  //描述服务器的socket,包含地址类型,IP,端口
  struct sockaddr_in server_addr;
  char sendbuf[1024*1024];
  char recvbuf[1024*1024];
  int date_size;

  //创建socket,返回文件描述符
  if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket");
    return 1;
  }

  //初始化服务端地地址
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(SERVER_PORT);
  server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

  //连接服务端口端口获取socket,将获取的socket和client_socket绑定
  if (connect(client_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
    perror("connect");
    return 1;
  }

  printf("连接到主机...\n");
  while (1) {
    printf("发送消息:");
    //读取输入到senbuf
    scanf("%s", sendbuf);
    printf("\n");
    //数据到client端文件描述符
    send(client_socket, sendbuf, strlen(sendbuf), 0);
    if (strcmp(sendbuf, "quit") == 0)
      break;
    printf("读取消息:");
    recvbuf[0] = '\0';
    date_size = recv(client_socket, recvbuf, 1024*1024, 0);
    recvbuf[date_size] = '\0';
    printf("%s\n", recvbuf);
  }
  close(client_socket);
  return 0;
}

测试效果

服务端代码解析

socket常用函数

socket

原型int socket(int domain, int type, int protocol);

头文件:sys/socket.h

参数

domain表示使用的地址类型,一般都是ipv4,AF_INET

type 表示套接字类型:tcp:面向连接的稳定数据传输SOCK_STREAM

protocol设置为0

功能:创建socket,返回文件描述符

返回值:失败返回-1,成功返回int类型的文件描述符

 

bind

原型:int bind (int, const struct sockaddr *__my_addr, socklen_t __addrlen);

头文件:sys/socket.h

功能:将socket文件描述符和socket绑定

返回:失败返回-1,成功返回0

 

listen

原型:int listen (int, int __n);

参数

第一个参数是int类型的socket文件描述符,第一个参数设置5

功能:将socket文件描述符对应的socket设置为监听状态

返回:成功返回0

调用示例if (listen(server_socket, 5) < 0) {}

函数详解

listen做两件事:

1、当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect发起连接的客户套接字。listen函数把一个未连接的套接字转换为一个被动套接字,指示内核应该接受指向该套接字的连接请求。根据TCP状态转换图,调用listen导致套接字从CLOSED状态转换到LISTEN状态。

2、listen函数的第二个参数规定了内核应该为相应套接字排队的最大连接个数:

1 #include<sys/socket.h>2 int listen(int sockfd, int backlog);3 返回:若成功则为0,若出错则为-1

 为了理解其中的backlog参数,我们必须认识到内核为任何一个给定的监听套接字维护两个队列:

(1)未完成连接队列,每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套接字处于SYN_RECV状态

(2)已完成连接队列,每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态。

下图描绘了监听套接字的两个队列

 

每当在未完成连接队列中创建一项时,来自监听套接字的参数就复制到即将建立的连接中,连接的创建机制是完全自动的。无需服务器进程插手。下图展示了用这两个队列建立连接时所交换的分组:

 

当来自客户的SYN到达时,TCP在未完成连接队列中创建一个新项,然后响应以三路握手的第二个分节:服务器的SYN响应,其中捎带对客户SYN的ACK。这一项一直保留在未完成连接队列中,直到三路握手的第三个分节(客户对服务器的SYN的ACK)到达或者该项超时为止。

 

如果三路握手正常完成,该项从未完成连接队列移到已完成连接队列的队尾。当进程调用accept时,已完成连接队列中的队头项将返回给进程,或者该队列为空,那么进程就被投入睡眠,直到TCP在该队列中放入一项才唤醒它。

 

总结:

1、accept()函数不参与三次握手,而只负责从建立连接队列中取出一个连接和socketfd进行绑定;

2、backlog参数决定了未完成队列和已完成队列中连接数目之和的最大值

3、accept()函数调用,会从已连接队列中取出一个“连接”,未完成队列和已完成队列中连接目之和将减少1;即accept将监听套接字对应的sock的接收队列中的已建立连接的sk_buff取下(从该sk_buff中可以获得对端主机的发送过来的tcp/ip数据包)

4、监听套接字的已完成队列中的元素个数大于0,那么该套接字是可读的。

5、当程序调用accept的时候(设置阻塞参数),那么判定该套接字是否可读,不可读则进入睡眠,直至已完成队列中的元素个数大于0(监听套接字可读)而唤起监听进程)

 

accept

int accept (int, struct sockaddr *__peer, socklen_t *);

调用accept函数后,会进入阻塞状态,如果成功accept返回一个套接字的文件描述符,如果失败,返回-1

调用实例,accept会一直等待server_socket接收到数据,将客户端的ip和port写入到__peer中,

client_socket = accept(server_socket, (struct sockaddr*) &client_addr, (socklen_t*) &addr_len);

传入的client_addr的长度,是为了避免避免缓冲区溢出。

 

connect

int connect (int, const struct sockaddr *, socklen_t);

头文件sys/socket.h

参数

第一个参数是客户端socket文件描述符,第二个参数是服务端地址和端口,第三个是第二个参数的长度

返回值:连接成功返回0

示例

if (connect(client_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)

 

send

ssize_t send (int s, const void *__buff, size_t __len, int __flags);

头文件sys/socket.h

参数

(1)第一个参数指定发送端套接字描述符;

(2)第二个参数指明一个存放应用程序要发送数据的缓冲区;

(3)第三个参数指明实际要发送的数据的字节数;

(4)第四个参数一般置0。

send(client_socket, sendbuf, strlen(sendbuf), 0);

功能说明

这里只描述同步Socket的send函数的执行流程。

当调用该函数时,send先比较待发送数据的长度len和套接字s发送缓冲区的长度, 如果len大于s发送缓冲区的长度,该函数返回SOCKET_ERROR;如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s发送缓冲区中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s发送缓冲区中的数据或者s发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len,如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议的send发送得道另一端,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里).

如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send将返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开,那么send函数也返回SOCKET_ERROR。

要注意send函数把buf中的数据copy成功到s的发送缓冲区的剩余空间里后sned执行完成并返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误,下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲区中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)。

注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。

 

recv

函数原型:int recv( SOCKET s, char *buf, int len, int flags)

头文件:sys/socket.h

参数

参数一:指定接收端套接字描述符;

参数二:指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;

参数三:指明buf的长度;

参数四 :一般置为0。

功能:不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。

同步Socket的recv函数的执行流程:用程序调用recv函数时,recv先等待s发送缓冲区中的数据被协议传送完毕,如果协议在传送s发送缓冲中区的数据时出现网络错误,那么recv函数返回SOCKET_ERROR;

如果s发送缓冲中区没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕;

当协议把数据接收完毕,recv函数就把s接收缓冲区中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s接收缓冲中的数据全部copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数;

如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断,则返回0。

读数据的时候需要考虑的是当recv()返回的大小,如果返回的大小大于或等于缓冲区中的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取,recv函数仅仅是copy数据,真正的接收数据是协议来完成的, recv函数返回其实际copy的字节数。

如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。默认 socket 是阻塞的 解阻塞与非阻塞recv返回值没有区分,都是 <0 出错 =0 连接关闭 >0 接收到数据大小,特别:返回值<0时并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的,继续接收。只是阻塞模式下recv会阻塞着接收数据,非阻塞模式下如果没有数据会返回,不会阻塞着读,因此需要循环读取)。

从TCP协议角度来看,一个已建立的TCP连接有两种关闭方式,一种是正常关闭,即四次挥手关闭连接;还有一种则是异常关闭,我们通常称之为连接重置(RESET)。首先说一下正常关闭时四次挥手的状态变迁,关闭连接的主动方状态变迁是FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT,而关闭连接的被对方的状态变迁是CLOSE_WAIT->LAST_ACK->TIME_WAIT。在四次挥手过程中ACK包都是协议栈自动完成的,而FIN包则必须由应用层通过closesocket或shutdown主动发送,通常连接正常关闭后,recv会得到返回值0,send会得到错误码10058。

除此之外,在我们的日常应用中,连接异常关闭的情况也很多。比如应用程序被强行关闭、本地网络突然中断(禁用网卡、网线拔出)、程序处理不当等都会导致连接重置,连接重置时将会产生RST包,同时网络络缓冲区中未接收(发送)的数据都将丢失。连接重置后,本方send或recv会得到错误码10053(closesocket时是10038),对方recv会得到错误码10054,send则得到错误码10053(closesocket时是10054)。

操作系统为我们提供了两个函数来关闭一个TCP连接,分别是closesocket和shutdown。通常情况下,closesocket会向对方发送一个FIN包,但是也有例外。比如有一个工作线程正在调用recv接收数据,此时外部调用closesocket,会导致连接重置,同时向对方发送一个RST包,这个RST包是由本方主动产生的。

shutdown可以用来关闭指定方向的连接,该函数接收两个参数,一个是套接字,另一个是关闭的方向,可用值为SD_SEND,SD_RECEIVE和SD_BOTH。方向取值为SD_SEND时,无论socket处于什么状态(recv阻塞,或空闲状态),都会向对方发送一个FIN包,注意这点与closesocket的区别。此时本方进入FIN_WAIT_2状态,对方进入CLOSE_WAIT状态,本方依然可以调用recv接收数据;方向取值为SD_RECEIVE时,双发连接状态没有改变,依然处于ESTABLISHED状态,本方依然可以send数据,但是,如果对方再调用send方法,连接会被立即重置,同时向对方发送一个RST包,这个RST包是被动产生的,这点注意与closesocket的区别。

 

close

int close (int __fildes);

头文件 <unistd.h>

参数:socket描述符

功能:关闭socket

网络字节序和本地字节序

[主机字节序和网络字节序概念]

网络字节顺序NBO(Network Byte Order):

按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。

 

主机字节顺序(HBO,Host Byte Order):

不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。

如 Intelx86结构下,short型数0x1234表示为34 12, int型数0x12345678表示为78 56 34 12

如IBM power PC结构下,short型数0x1234表示为12 34, int型数0x12345678表示为12 34 56 78

由于这个原因不同体系结构的机器之间无法通信,所以要转换成一种约定的数序,也就是网络字节顺序

 

[网络字节顺序与本地字节顺序之间的转换函数]

htonl()--"Host to Network Long"

ntohl()--"Network to Host Long"

htons()--"Host to Network Short"

ntohs()--"Network to Host Short"

 

头文件asm/byteorder.h

 

在Linux和Windows网络编程时需要用到htons和htonl函数,用来将主机字节顺序转换为网络字节顺序。

在Intel机器下,执行以下程序

int main(){
  printf("%d \n",htons(16));
  return 0;
}

到的结果是4096,起初看感觉很怪。

解释如下,数字16的16进制表示为0x0010,数字4096的16进制表示为0x1000。 由于Intel机器是小尾端,存储数字16时实际顺序为1000,存储4096时实际顺序为0010。因此在发送网络包时为了报文中数据为0010,需要 经过htons进行字节转换。如果用IBM等大尾端机器,则没有这种字节顺序转换,但为了程序的可移植性,也最好用这个函数。

另外用注意,数字所占位数小于或等于一个字节(8 bits)时,不要用htons转换。这是因为对于主机来说,大小尾端的最小单位为字节(byte)。

#include <stdio.h>
#include <netinet/in.h>
#include <asm/byteorder.h>
void main(){
  printf("result:%d",htonl(INADDR_ANY));
}

输出的结果是0

INADDR_ANY的值是

#define INADDR_ANY ((in_addr_t) 0x00000000)

其他函数

bzero

原型:extern void bzero(void *s, int n);

参数说明:s 要置零的数据的起始地址; n 要置零的数据字节个数。

用法:#include <string.h>

功能:置字节字符串s的前n个字节为零且包括‘\0’。

perror

原型:void perror(const char *s)

功能:将你指定的函数发生错误的原因输出到标准设备,s所指的字符串会先打印出来,然后跟着错误的原因

例子:

#include <stdio.h>
#include <sys/socket.h>
void main(){
  int server_socket;
  server_socket=socket(AF_INET,SOCK_STREAM,1);
  if(server_socket <0){
    perror("socket");
  }
}

执行上面的代码,发生错误会显示 
socket : Protocol not supported

IP地址点分十进制转换

为什么要使用inet_ntoa进行转换
之前使用服务端的IP地址使用的htonl
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
因为这次转换的对象是client_addr.sin_addr
inet_ntoa(client_addr.sin_addr)

inet_ntoa
函数声明:char *inet_ntoa (struct in_addr);
头文件:<arpa/inet.h>
函数功能:将网络字节序IP转换为点分十进制IP

inet_addr
头文件:<arpa/inet.h>
是将一个点分十进制的IP转换成一个长整数型数(u_long类型),即网络字节序IP
实例

#include <stdio.h>
#include <arpa/inet.h>
int main(){
  ulong l1=inet_addr("127.0.0.1");
  printf("%ld",l1);
  return 0;
}

输出结果是16777343

输出一个十进制的IP

#include <arpa/inet.h>
#include <cygwin/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>

int main(int argc, char *argv[]) {
  struct in_addr addr1;
  ulong l1 = inet_addr("192.168.0.74");
  //赋值变量的内容地址
  memcpy(&addr1, &l1, 4);
  //转成成点分十进制输出
  printf("%s\n", inet_ntoa(addr1));
  return 0;
}

PF_INET和AF_INET的区别

在写网络程序的时候,建立TCP socket:

sock = socket(PF_INET, SOCK_STREAM, 0);

然后在绑定本地地址或连接远程地址时需要初始化sockaddr_in结构,其中指定address family时一般设置为AF_INET,即使用IP。

AF = Address Family

PF = Protocol Family

在windows中的Winsock2.h中,
#define AF_INET 0
#define PF_INET AF_INET

所以在windows中AF_INET与PF_INET完全一样. 

CentOS-7 <sys/socket.h>相关头文件中的定义:
#define AF_INET         2
#define PF_INET         AF_INET

而在Unix/Linux系统中,在不同的版本中这两者有微小差别.对于BSD,是AF,对于POSIX是PF.

理论上建立socket时是指定协议,应该用PF_xxxx,设置地址时应该用AF_xxxx。当然AF_INET和PF_INET的值是相同的,混用也 不会有太大的问题。也就是说你socket时候用PF_xxxx,设置的时候用AF_xxxx也是没关系的,这点随便找个TCPIP例子就可以验证出来 了。如下,不论是AF_INET还是PF_INET都是可行的,只不过这样子的话,有点不符合规范。

  /* 服务器端开始建立socket描述符 */
  //if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) 
  if((sockfd=socket(PF_INET,SOCK_STREAM,0))==-1){
    fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
    exit(1);
  }

 /* 服务器端填充 sockaddr结构 */ 
 bzero(&server_addr,sizeof(struct sockaddr_in));
 //server_addr.sin_family=PF_INET;
 server_addr.sin_family=AF_INET;
 server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
 server_addr.sin_port=htons(portnumber);

使用socket传输文件


使用socket传输文件分片概念

在C/C++网络编程中不免会遇到需要传输大数据、大文件的情况,而由于socket本身缓冲区的限制,大概一次只能发送4K左右的数据,所以在传输大数据时客户端就需要进行分包,在目的地重新组包。而实际上已有一些消息/通讯中间件对此进行了封装,提供了直接发送大数据/文件的接口;除此之外,利用共享目录,ftp,ssh等系统命令来实现大文件/数据也不失为一种好的方法。

客户端读取一个文件发送的服务端,服务端读取该文本并保存

使用socket传输文件代码

server端代码12big-file-transfer-socker-server.c

#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <unistd.h>

#define SERVER_PORT                6666
#define LENGTH_OF_LISTEN_QUEUE     20
#define BUFFER_SIZE                1024
#define FILE_NAME_MAX_SIZE         512

int main(int argc, char **argv) {
  // 设置一个socket地址结构server_addr,代表服务器internet的地址和端口
  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(SERVER_PORT);

  // 创建用于internet的流协议(TCP)socket,用server_socket代表服务器向客户端提供服务的接口
  int server_socket = socket(PF_INET, SOCK_STREAM, 0);
  if (server_socket < 0) {
    printf("Create Socket Failed!\n");
    exit(1);
  }

  // 把socket和socket地址结构绑定
  if (bind(server_socket, (struct sockaddr*) &server_addr, sizeof(server_addr))) {
    printf("Server Bind Port: %d Failed!\n", SERVER_PORT);
    exit(1);
  }

  // server_socket用于监听
  if (listen(server_socket, LENGTH_OF_LISTEN_QUEUE)) {
    printf("Server Listen Failed!\n");
    exit(1);
  }

  // 服务器端一直运行用以持续为客户端提供服务
  while (1) {
    // 定义客户端的socket地址结构client_addr,当收到来自客户端的请求后
    // 调用accept接受此请求,同时将client端的地址和端口等信息写入client_addr中
    struct sockaddr_in client_addr;
    socklen_t length = sizeof(client_addr);

    // 接受一个从client端到达server端的连接请求,将客户端的信息保存在client_addr中
// 如果没有连接请求,则一直等待直到有连接请求为止,这是accept函数的特性,
// 可以用select()来实现超时检测
    // accpet返回一个新的socket,这个socket用来与此次连接到server的client进行通信
    // 这里的client_socket代表了这个通信通道socket的文件描述符
    int client_socket = accept(server_socket, (struct sockaddr*) &client_addr, &length);
    if (client_socket < 0) {
      printf("Server Accept Failed!\n");
      break;
    }

    char buffer[BUFFER_SIZE];
    bzero(buffer, sizeof(buffer));
    length = recv(client_socket, buffer, BUFFER_SIZE, 0);
    if (length < 0) {
      printf("Server Recieve Data Failed!\n");
      break;
    }

    char file_name[FILE_NAME_MAX_SIZE + 1];
    bzero(file_name, sizeof(file_name));
    length = strlen(buffer) > FILE_NAME_MAX_SIZE ? FILE_NAME_MAX_SIZE : strlen(buffer);
    strncpy(file_name, buffer, length);

    FILE *fp = fopen(file_name, "r");
    if (fp == NULL) {
      printf("File:\t%s Not Found!\n", file_name);
    } else {
      bzero(buffer, BUFFER_SIZE);
      int file_block_length = 0;
      while ((file_block_length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0) {
        printf("file_block_length = %d\n", file_block_length);

        // 发送buffer中的字符串到new_server_socket,实际上就是发送给客户端
        if (send(client_socket, buffer, file_block_length, 0) < 0) {
          printf("Send File:\t%s Failed!\n", file_name);
          break;
        }

        bzero(buffer, sizeof(buffer));
      }
      fclose(fp);
      printf("File:\t%s Transfer Finished!\n", file_name);
    }

    close(client_socket);
  }
  close(server_socket);
  return 0;
}

client端代码12big-file-transfer-socker-client.c

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <unistd.h>

#define SERVER_PORT                   6666
#define BUFFER_SIZE                   1024
#define FILE_NAME_MAX_SIZE            512

int main(int argc, char **argv) {
  if (argc != 2) {
    printf("Usage: ./%s ServerIPAddress\n", argv[0]);
    exit(1);
  }

  // 设置一个socket地址结构client_addr, 代表客户机的internet地址和端口
  struct sockaddr_in client_addr;
  bzero(&client_addr, sizeof(client_addr));
  client_addr.sin_family = AF_INET; // internet协议族
  client_addr.sin_addr.s_addr = htons(INADDR_ANY); // INADDR_ANY表示自动获取本机地址
  client_addr.sin_port = htons(0); // auto allocated, 让系统自动分配一个空闲端口

  // 创建用于internet的流协议(TCP)类型socket,用client_socket代表客户端socket
  int client_socket = socket(AF_INET, SOCK_STREAM, 0);
  if (client_socket < 0) {
    printf("Create Socket Failed!\n");
    exit(1);
  }

  // 把客户端的socket和客户端的socket地址结构绑定
  if (bind(client_socket, (struct sockaddr*) &client_addr, sizeof(client_addr))) {
    printf("Client Bind Port Failed!\n");
    exit(1);
  }

  // 设置一个socket地址结构server_addr,代表服务器的internet地址和端口
  struct sockaddr_in server_addr;
  bzero(&server_addr, sizeof(server_addr));
  server_addr.sin_family = AF_INET;

  // 服务器的IP地址来自程序的参数
  if (inet_aton(argv[1], &server_addr.sin_addr) == 0) {
    printf("Server IP Address Error!\n");
    exit(1);
  }

  server_addr.sin_port = htons(SERVER_PORT);
  socklen_t server_addr_length = sizeof(server_addr);

  // 向服务器发起连接请求,连接成功后client_socket代表客户端和服务器端的一个socket连接的文件描述符
  if (connect(client_socket, (struct sockaddr*) &server_addr, server_addr_length) < 0) {
    printf("Can Not Connect To %s!\n", argv[1]);
    exit(1);
  }

  char file_name[FILE_NAME_MAX_SIZE + 1];
  bzero(file_name, sizeof(file_name));
  printf("Please Input File Name On Server.\t");
  scanf("%s", file_name);

  char buffer[BUFFER_SIZE];
  bzero(buffer, sizeof(buffer));
  strncpy(buffer, file_name, strlen(file_name) > BUFFER_SIZE ? BUFFER_SIZE : strlen(file_name));
  // 向服务器发送buffer中的数据,此时buffer中存放的是客户端需要接收的文件的名字
  send(client_socket, buffer, BUFFER_SIZE, 0);

  FILE *fp = fopen(file_name, "w");
  if (fp == NULL) {
    printf("File:\t%s Can Not Open To Write!\n", file_name);
    exit(1);
  }

  // 从服务器端接收数据到buffer中
  bzero(buffer, sizeof(buffer));
  int length = 0;
  while ((length = recv(client_socket, buffer, BUFFER_SIZE, 0))) {
    if (length < 0) {
      printf("Recieve Data From Server %s Failed!\n", argv[1]);
      break;
    }

    int write_length = fwrite(buffer, sizeof(char), length, fp);
    if (write_length < length) {
      printf("File:\t%s Write Failed!\n", file_name);
      break;
    }
    bzero(buffer, BUFFER_SIZE);
  }
  printf("Recieve File:\t %s From Server[%s] Finished!\n", file_name, argv[1]);
  // 传输完毕,关闭socket
  fclose(fp);
  close(client_socket);
  return 0;
}

 

  • 4
    点赞
  • 0
    评论
  • 32
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值