Linux之网络编程

Linux之网络编程

TCP协议

TCP(Transmission ControlProtocol) : 传输控制协议,是一个
面向连接的、可靠的、基于字节流的传输层的协议。TCP 协议建立的是一种点到点的,一对一的可靠连接协议

特点:

  • 数据无丢失
  • 数据无失序
  • 数据无错误
  • 数据无重复

使用场景

  • 适合于对传输质量要求较高,以及传输大量数据的通信。
  • 在需要可靠数据传输的场合,通常使用 TCP 协议
  • MSN/QQ 等即时通讯软件的用户登录账户管理相关的功能通常采用 TCP 协议

面向连接,数据可靠

  • 三次握手,本意指 TCP/IP 协议栈中,要求 TCP 协议提供可靠的连接服务。我们需要建立可靠的,稳定的链接的时候,我们就需要使用三次握手。它的实质是指建立一个TCP 连接的时候,客户端和服务端需要发送 3 个数据包。
  • 四次挥手,当用户想要断开连接的时候,需要发送四次数据包,才会中断连接。

在这里插入图片描述

TCP 源端口(Source Port): 源计算机上的应用程序的端口号,占 16 位。
TCP 目的端口(Destination Port): 目标计算机的应用程序端口号,占 16 位

数据序号 (Sequence Number,seq):它表示本报文段所发送数据的第一个字节的编号。在 TCP 连接中,所传送的字节流的每一个字节都会按顺序编号。当SYN 标记不为 1 时,这是当前数据分段第一个字母的序列号;如果 SYN 的值是 1时,这个字段的值就是初始序列值(ISN),用于对序列号进行同步。这时,第一个字节的序列号比这个字段的值大 1,也就是 ISN 加 1

确认序号 (Acknowledgment Number,ack) :占 32bit, 它表示接收方期望收到发送方下一个报文段的第一个字节数据的编号。其值是接收计算机即将接收到的下一个序列号,也就是下一个接收到的字节的序列号加 1。

TCP 首部长度(Header Length):数据偏移是指数据段中的 “数据” 部分起始处距离 TCP 数据段起始处的字节偏移量,占 4 位。其实这里的 “数据偏移” 也是在确定TCP 数据段头部分的长度,告诉接收端的应用程序,数据从何处开始。

保留 (Reserved): 占 4 位;为 TCP 将来的发展预留空间,目前必须全部为 0

标志位字段:
U——URG,表示本报文段中发送的数据是否包含紧急数据:URG=1 时表示有紧急数据。当 URG=1 时,后面的紧急指针字段才有效。
A——ACK,表示前面的确认号字段是否有效:ACK=1 时表示有效;只有当 ACK=1时,前面的确认号字段才有效;TCP 规定,连接建立后,ACK 必须为 1
P——PSH, 告诉对方收到该报文段后是否立即把数据推送给上层。如果值为 1,表示应当立即把数据提交给上层,而不是缓存起来
R——RST,表示是否重置连接:若 RST=1,说明 TCP 连接出现了严重错误(如主机崩溃),必须释放连接,然后再重新建立连接
S——SYN,在建立连接时使用,用来同步序号:当 SYN=1,ACK=0 时,表示这是一个请求建立连接的报文段;当 SYN=1,ACK=1 时,表示对方同意建立连接;SYN=1时,说明这是一个请求建立连接或同意建立连接的报文;
只有在前两次握手中 SYN 才为 1
F——FIN,标记数据是否发送完毕:若 FIN=1,表示数据已经发送完成,可以释放连接

窗口大小 (Window Size): 占 16 位;它表示从 Ack Number 开始还可以接收多少字节的数据量,也表示当前接收端的接收窗口还有多少剩余空间。该字段可以用于 TCP 的流量控制。

校验和 (TCP Checksum): 占 16 位;它用于确认传输的数据是否有损坏。发送端基于数据内容校验生成一个数值,接收端根据接收的数据校验生成一个值。两个值必须相同,才能证明数据是有效的。如果两个值不同,则丢掉这个数据包。Checksum 是根据伪头 + TCP 头 + TCP 数据三部分进行计算的。

紧急指针 (Urgent Pointer): 仅当前面的 URG 控制位为 1 时才有意义。它指出本数据段中为紧急数据的字节数,占 16 位;当所有紧急数据处理完后,TCP 就会告诉应用程序恢复到正常操作。即使当前窗口大小为 0,也是可以发送紧急数据的,因为紧急数据无须缓存。

选项 (Option): 长度不定,但长度必须是 32bits 的整数倍;选项中的内容不确定,因此就必须使用首部长度来区分选项具体的长度.

填充字段 (Fill): 这是为了使整个首部长度是 4 个字节的倍数。IP 数据报的首部也同样有这个字段,也要 4 字节对齐

三次握手

第一次握手,由客户端发送请求连接即 SYN = 1,TCP 规定 SYN=1 的时候,不能够携带数据。但是需要消耗一个 seq 序号。因此,产生了一个序号 seq = x.

第二次握手,然后 B 主机收到 A 主机发送的消息。向 A 主机发送确认。发送 SYN = 1, 表示请求连接已经收到,然后发送确认 ACK=1,把 TCP 包中 ACK 位置为 1. 在来发送一个新的序列号 seq =y,确认好 ack = x + 1;

第三次握手,其实经过两次连接之后,双方的基本连接已经建立。但是 A 收到 B 的确认之后,还需要向 B 给出确认,说明自己已经收到确认包了。设置确认 ACK = 1,ack = y + 1. 而顺序号 seq =x + 1,。双方建立稳定的连接。此时 ACK 报文可以携带数据。

四次挥手

第一次挥手:
客户端打算关闭连接,此时会发送⼀个 TCP⾸部 FIN 标志位被置为 1 的报⽂,也即FIN 报文,之后客户端进⼊ FIN_WAIT_1 状态。

第二次挥手:
服务端收到该报⽂后,就向客户端发送 ACK 应答报⽂,接着服务端进⼊CLOSED_WAIT 状态。

第三次挥手:
客户端收到服务端的 ACK 应答报⽂后,之后进⼊ FIN_WAIT_2 状态。但此时服务端可能还有一些数据未处理完。等待服务端处理完数据后,也向客户端发送 FIN 报⽂,之后服务端进⼊ LAST_ACK 状态。

第四次挥手:
客户端收到服务端的 FIN 报⽂后,回⼀个 ACK 应答报⽂,之后进⼊ TIME_WAIT 状态服务端收到了 ACK 应答报⽂后,就进⼊了 CLOSED 状态,⾄此服务端已经完成连接的关闭。
客户端在经过 2MSL ⼀段时间后,⾃动进⼊ CLOSED 状态,⾄此客户端也完成连接的关

UDP协议

特点

  • UDP 是无连接的协议。
  • UDP 使用尽最大努力交付,不保证数据可靠。
  • UDP 是面向报文的。
  • UDP 通信的实时性较高。

使用场景

  • 发送小尺寸数据(如对 DNS 服务器进行 IP 地址查询时)
  • 在接收到数据,给出应答较困难的网络中使用 UDP。(如:无线网络)
    适合于广播 / 组播式通信中。
  • MSN/QQ/Skype 等即时通讯软件的点对点文本通讯以及音视频通讯通常采用 UDP 协议
  • 流媒体、VOD、VoIP、IPTV 等网络多媒体服务中通常采用 UDP 方式进行实时数据传输

字节序转换 API

1、IP 字符串转换为网络字节序

方法1

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>typedef unsigned int uint32_t;
typedef unsigned int in_addr_t;in_addr_t inet_addr(const char *cp); 
功能:将cp指向的IP字符串转成网络字节序 返回值:成功返回网络字节序,失败返INADDR_NONE[0xffffffff]
注意:它不能识别255.255.255.255

方法2

int inet_aton(const char *cp, struct in_addr *inp); 
[addr to network]
功能:将cp指向的IP字符串转成网络字节inp保存的地址中。 
参数:@cp IP字符串首地址 @inp 存放网络字节序的地址 
返回值:成功返回非0,失败返回0 struct in_addr { 
unsigned int s_addr; 
}; 
实质:存储在inp结构体指针中的 s_addr这个变量

2、网络字节序转换为IP字符串

char *inet_ntoa(struct in_addr in); 
[network to addr]功能:将IP网络字节序转换成IP字符串
参数:@in IP网络字节序 
返回值:成功返回IP字符串首地址,失败返回NULL

3、端口转换为网络字节序

short htons(short data); 
[host to network short ]功能:将short类型的整数转成网络字节序 
参数: @data 序号转换的整数返回值:得到的网络字节序

4、网络字节序转换为端口

uint32_t ntohs(uint32_t netlong); 
[network to host short]功能:把网络字节序转换为主机端口
参数:@ netlong 网络字节序
返回值: 返回对应的主机端口

示例:
写一个代码实现以下功能
1.用户从命令行传递参数 ./a.out 127.0.0.1 9090
2.利用 inet_aton 和 htons 函数把 ip,port 转换为网络字节序后输出。
3.利用 inet_ntoa 和 noths 函数把 ip,port 转换为主机字节序后输出

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
void ip_to_network01(const char *cp){
in_addr_t net_addr;
net_addr=inet_addr(cp);
if(net_addr==INADDR_NONE){
perror("[ERROR] inet_addr():" );
exit(EXIT_FAILURE);
}printf("%x\n",net_addr);}void ip_to_network02(const char *cp){struct in_addr addr;int ret=inet_aton(cp,&addr);if(ret==0){perror("[ERROR] inet_aton:");exit(EXIT_FAILURE);}printf("network=%x\n",addr.s_addr);printf("addr=%s\n",inet_ntoa(addr));}void port_to_network(short data){short result=htons(data);
printf("network port=%d\n",result);printf("port=%d\n",ntohs(result));}int main(int argc, const char *argv[])
{ip_to_network01(argv[1]);printf("-------------\n");ip_to_network02(argv[1]);
port_to_network(atoi(argv[2]));return 0;
}

UDP通信流程

介绍用到的函数

在这里插入图片描述

1、创建套接字

int socket(int domain, int type, int protocol);
参数: @domain 地址族 :AF_UNIX 本地unix域通信 ,AF_INET IPV4 ineter网通信 [我们使用这个] 
@ type :使用协议类型 SOCK_STREAM 流式套接字(TCP), SOCK_DGRAM 报文套接字(UDP) ,SOCK_RAW 原始套接字: (IP,ICMP)
@protocol 协议编号 0 : 让系统自动识别 
返回值:成功返回得到的文件描述符。失败返回 -1

示例用法

int fd = socket(AF_INET,SOCK_DGRAM,0);

2、发送数据

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); 参数: @sockfd 套接字 
@buf 数据存放的首地址 
@len 期望发送的数据大小 
@flags 操作方式 0 表示默认操作
@dest_addr 向指定的地址发送数据 
@addrlen 发送的地址的大小
返回值: 成功返回实际发送的字节数,失败返回-1 struct sockaddr { 
unsigned short sa_family; 
char sa_data[14];
};//struct sockaddr 可强转为 struct sockaddr_in 
struct sockaddr_in { 
short int sin_family;
unsigned short int sin_port; 
struct in_addr sin_addr;
unsigned char sin_zero[8];
}; struct in_addr { 
uint32_t s_addr;
};
struct sockaddr_in peer_addr; 
peer_addr.sin_family = AF_INET;
peer_addr.sin_port = htons(8080);
peer_addr.sin_addr.s_addr = inet_addr("192.168.0.88");
n = sendto(sockfd,buf,n,0,(struct sockaddr *)&peer_addr,sizeof(struct sockaddr_in));

示例:udp客户端实现

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
void send_data(int sockfd,struct sockaddr_in *addr,int len){
int n=0;
char buf[1024]={0};
while(1){
printf(">");
memset(buf,sizeof(buf),0);
fgets(buf,sizeof(buf),stdin);
if(strncmp("quit",buf,4)==0){
break;
}
else{
ssize_t s=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)addr,len);//发送信息
if(s<0){
perror("[ERROR] sendto:");
}
}
}return;
}int main(int argc,const char *argv[]){
int socket_id;
struct sockaddr_in addr;
socklen_t len=sizeof(addr);
socket_id=socket(AF_INET,SOCK_DGRAM,0);
if(socket_id<0){
perror("[ERROR] socket():");
exit(EXIT_FAILURE);
}
memset(&addr,sizeof(addr),0);
addr.sin_family=AF_INET;
addr.sin_port=htons(atoi(argv[2]));//网络端口
addr.sin_addr.s_addr=inet_addr(argv[1]);//网络ip
send_data(socket_id,&addr,len);return 0;
}

3、把 ip 和端口与当前进程绑定

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
功能:把ip地址和端口绑定到socket中去。
参数:@sockfd socket创建的文件描述符
@addr 把IP和地址设置到对应的结构体中去。
@addrlen 表示 addr 参数对应类型的地址信息结构体的大小返回值
struct sockaddr { 
sa_family_t sa_family; 
char sa_data[14]; 
}
//struct sockaddr_in可强转为struct sockaddr 
struct sockaddr_in { 
sa_family_t sin_family; 
in_port_t sin_port;
struct in_addr sin_addr;
};
struct in_addr{
unsigned int s_addr;
}
返回值:成功 返回0失败返回 -1 ,并设置errno

用法:

//定义结构体 
struct sockaddr_in my_addr; 
memest(&my_addr,0,sizeof(my_addr)); //填充数据my_addr.sin_family = AF_INET;
my_addr.sin_port =htons(atoi(argv[2])); my_addr.sin_addr.s_addr = inet_addr(argv[1]);
//绑定数据
if(bind(sockfd,(struct sockaddr *)&my_addr),sizeof(my_addr) < 0){ ...}

4、接受数据

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
参数:@sockfd 套接字 
@buf 数据存放的首地址 
@len 期望接收的数据大小 
@flags 操作方式 0 表示默认操作 
@src_addr 获得发送方地址,谁发送的获得谁的地址。
@addrlen 值结果参数,必须进行初始化, 表示表示对方实际地址的大小。
返回值:成功返回实际接收的字节数,失败返回-1

示例用法:

struct sockaddr_in peer_addr;
socklen_t addrlen = sizeof(struct sockaddr_in);
n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&peer_addr,&addrlen);

示例:udp服务端实现

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>void recv_data(int socketfd){int n=0;
char buff[1024]={0};
struct sockaddr_in client_addr;
socklen_t len=sizeof(client_addr);
while(1){
memset(buff,0,sizeof(buff));
n=recvfrom(socketfd,buff,sizeof(buff),0,(struct sockaddr *)&client_addr,&len);
printf("n=%d\n",n);
if(n<0){
perror("Fail to  recvfrom");
exit(EXIT_FAILURE);
}
printf("=================\n");
printf("Recv from IP:%s\n",inet_ntoa(client_addr.sin_addr));
printf("Recv from port:%d\n",ntohs(client_addr.sin_port));
printf("Recv %d bytes:%s\n",n,buff);
if(strncmp(buff,"quit",4)==0){
break;
}}
return ;
}int main(int argc,const char *argv[]){
int socketfd;
struct sockaddr_in my_addr;
socklen_t  len=sizeof(my_addr);
socketfd=socket(AF_INET,SOCK_DGRAM,0);
if(socketfd<0){
perror("socketfd<0");
exit(EXIT_FAILURE);
}
memset(&my_addr,0,sizeof(my_addr));
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(atoi(argv[2]));
my_addr.sin_addr.s_addr=inet_addr(argv[1]);
if(bind(socketfd,(struct sockaddr *)&my_addr,len)<0){
perror("Fail to bind!\n");
exit(EXIT_FAILURE);
}
recv_data(socketfd);
close(socketfd);return 0;
}

多进程并发

常用的服务器模型

  • 迭代服务器
    大多数 UDP 都是迭代运行,服务器等用客户端的数据,收到数据后处理该数据,送回其应答,在等待下一个客户请求。
socket();
bind();
while(1){
recvfrom();
process();
sendto();
}
close();
  • 并发服务器:并发服务器是指在同一个时刻可以响应多个客户端的请求。
    本质是创建多进程 / 多线程,对多数用户的信息进行处理。

UDP 协议一般默认是不支持多线程并发的,因为默认 UDP 服务器只有一个sockfd,
所有的客户端都是通过同一个 sockfd 进行通信的. udp 一个socket,如何做到做并发呢?

sockfd = socket();
bind();
while(1){ 
recvfrom(); ...
}
close();

当 UDP 协议针对客户请求的处理需要消耗过长的时间时,我们期望 UDP 服务器具有某种形式的并发

示例:

多个 udp 客户端登录用户需要先验证密钥是否正确后,才能允许用户进行数据交互。假设密钥为 “root”.
服务器接收客户端信息的时候,需要考虑两种情况
A 用户的密钥验证请求消息。
B 用户的数据交互接收消息。
多个用户之间实现并发

服务端udp_fork_server.c

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>#define LOGIN_SUCCESS 1
#define LOGIN_FAILURE 0int init_socket(const char *ip,const char *port){int sockfd=0;
struct sockaddr_in my_addr;
socklen_t len=sizeof(my_addr);sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0){
perror("Fail to socket!\n");
exit(EXIT_FAILURE);
}
memset(&my_addr,0,sizeof(my_addr));
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(atoi(port));
my_addr.sin_addr.s_addr=inet_addr(ip);
if(bind(sockfd,(struct sockaddr *)&my_addr,len)<0)
{
perror("Fail to bind\n");
return -1;
}
return sockfd;
}int user_login(const char *ip,const char *port){int n=0;
char buf[20]={0};
struct sockaddr_in client_addr;
socklen_t len=sizeof(client_addr);
unsigned char login_flag;
int sockfd;
int new_sockfd;sockfd=init_socket(ip,port);
while(1){
//Receive messagememset(buf,0,sizeof(buf));n=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,&len);if(n<0){perror("Fail to sendto");exit(EXIT_FAILURE);}printf("key=%s\n",buf);login_flag=(strncmp(buf,"root",4)==0)?LOGIN_SUCCESS:LOGIN_FAILURE;if(login_flag==LOGIN_SUCCESS){if(fork()==0){//son process	close(sockfd);new_sockfd=init_socket(ip,"0");sendto(new_sockfd,&login_flag,sizeof(login_flag),0,(struct sockaddr *)&client_addr,len);break;}}else{sendto(sockfd,&login_flag,sizeof(login_flag),0,(struct sockaddr*)&client_addr,len);}}return new_sockfd;
}void printf_client_info(struct sockaddr_in *addr,char *buf){printf("===============================\n");printf("user IP : %s\n",inet_ntoa(addr->sin_addr));printf("user port : %d\n", ntohs(addr->sin_port)); printf("user data : %s\n",buf);}void recv_data(int new_sockfd){
int n=0;
char buf[1024]={0};struct sockaddr_in client_addr;
socklen_t len=sizeof(client_addr);
while(1){memset(buf,0,sizeof(buf));
n=recvfrom(new_sockfd,buf,sizeof(buf),0,(struct sockaddr *)&client_addr,&len);if(n<0){perror("Fail to sendto");
exit(EXIT_FAILURE);
}printf_client_info(&client_addr,buf);if(strncmp(buf,"quit",4)==0){
break;
}}close(new_sockfd);
exit(EXIT_SUCCESS);
return;}void sig_handler(int signum){waitpid(-1,NULL,WNOHANG);printf("recv singnum = %d zombie\n",signum);return ;
}int main(int argc,const char *argv[]){int sockfd;
unsigned char login_flag;
if(argc<3){
fprintf(stderr,"Usage :%s is port!\n",argv[0]);
exit(EXIT_FAILURE);
}if(signal(SIGCHLD,sig_handler) == SIG_ERR) {perror("Fail to single\n");return -1;
}sockfd=user_login(argv[1],argv[2]);
recv_data(sockfd);
return 0;}

udp_client.c

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>#define LOGIN_SUCCESS 1
#define LOGIN_FAILURE 0void user_login(int sockfd,struct sockaddr_in *addr,struct sockaddr_in *new_addr,int len){int n=0;
char buf[1024]={0};
unsigned char flag=LOGIN_FAILURE;while(1){
putchar('>');
memset(buf,0,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]='\0';n=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)addr,len);recvfrom(sockfd,&flag,sizeof(flag),0,(struct sockaddr *)new_addr,&len);if(flag==LOGIN_SUCCESS){break;}}return;}void send_message(int sockfd,struct sockaddr_in *addr,int addr_len){int n = 0;char buf[1024] = {0}; while(1) { printf("Input : ");memset(buf,0,sizeof(buf));fgets(buf,sizeof(buf),stdin);buf[strlen(buf) - 1] = '\0';n = sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)addr,addr_len); if(n < 0) {perror("Fail to sendto");exit(EXIT_FAILURE); }if(strncmp(buf,"quit",4) == 0)break;}return ;
}int main(int argc,const char *argv[]){
int socketfd=0;
struct sockaddr_in peer_addr;
struct sockaddr_in server_addr;
socklen_t len=sizeof(peer_addr);if(argc<3){fprintf(stderr,"Usage:%s is port \n",argv[0]);
exit(EXIT_FAILURE);}
socketfd=socket(AF_INET,SOCK_DGRAM,0);
if(socketfd<0){
perror("Fali to socket");
exit(EXIT_FAILURE);
}memset(&peer_addr,0,sizeof(peer_addr));
peer_addr.sin_family=AF_INET;
peer_addr.sin_port=htons(atoi(argv[2]));
peer_addr.sin_addr.s_addr=inet_addr(argv[1]);
memset(&server_addr,0,sizeof(server_addr));
user_login(socketfd,&peer_addr,&server_addr,len);send_message(socketfd,&server_addr,len); 
close(socketfd);return 0;
}

TCPSocket编程

客户端连接

在整个流程中, 主要涉及以下几个接口
socket() : 创建套接字, 使用的套接字类型为流式套接字
connect() : 连接服务器
send() : 数据发送
recv() : 数据接收

1、创建套接字的函数为 socket ,具体信息如下:
函数头文件

#include <sys/types.h>
#include <sys/socket.h>

函数原型

int socket(int domain,int type,int protocol)

函数功能:创建套接字

函数参数

domain: 协议族 ,AF_INTE
type : 套接字类型SOCK_STREAM:流式套接字, 传输层使用 tcp 协议SOCK_DGRAM: 数据包套接字, 传输层使用 udp 协议protocol : 协议, 可以填 0

函数返回值
成功 : 返回 套接字文件描述符
失败 : 返回 -1,并设置 errno

2、连接服务器要调用的函数为connect , 具体如下:

函数头文件

#include <sys/types.h>
#include <sys/socket.h>

函数原型

int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen)

函数功能:发起对套接字的连接 (基于面向连接的协议)

函数参数

sockfd : 套接字文件描述符
addr : 连接的套接字的地址结构对象的地址 (一般为服务器)
internet 协议族使用的 struct sockaddr_in 结构体,大小与通用struct sockaddr 结构体一致
addrlen : 地址结构的长度

函数返回值

成功 : 返回 0
失败 : 返回 -1,并设置 errno

示例:TCP连接实现客户端

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
int main(int argc,const char *argv[]){
int sockfd=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in svr_addr;
bzero(&svr_addr,sizeof(svr_addr));
svr_addr.sin_family=AF_INET;
svr_addr.sin_port=htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr=inet_addr(argv[1]);
int ret=connect(sockfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr));if(ret==-1){
perror("[ERROR] connect():");
exit(EXIT_FAILURE);}close(sockfd);return 0;}

bzero()函数: 能够将内存块(字符串)的前n个字节清零

头文件

#include <string.h>

原型为:

    void bzero(void *s, int n);
//【参数】s为内存(字符串)指针,n 为需要清零的字节数。

客户端发送与接受数据

基于 socket 发送数据需要调用send函数, 下面是 send 函数的具体信息

函数头文件

#include <sys/types.h>
#include <sys/socket.h>

函数原型

ssize_t send(int sockfd,const void *buf,size_t len,int flags)

函数功能:基于套接字(建立连接)发送数据
函数参数

sockfd : 套接字文件描述符
buf : 发送缓冲区的地址
len : 发送数据的长度
flags : 发送标志位

函数返回值
成功 : 返回 成功发送的字节数
失败 : 返回 -1, 并设置 errno

基于 socket 接收数据需要调用recv函数, 具体信息如下:
函数头文件

#include <sys/types.h>
#include <sys/socket.h>

函数原型

ssize_t recv(int sockfd,void *buf,size_t len,int flags)

函数功能:基于套接字接收数据
函数参数

sockfd : 套接字文件描述符
buf : 接收缓冲区的地址
len : 接收数据最大长度
flags : 标志位

函数返回值
成功 : 返回 成功接收的字节数

案例:客户端接受与发送文件

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
int main(int argc,const char *argv[]){
int sockfd=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in svr_addr;
bzero(&svr_addr,sizeof(svr_addr));
svr_addr.sin_family=AF_INET;
svr_addr.sin_port=htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr=inet_addr(argv[1]);
int ret=connect(sockfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr));if(ret==-1){
perror("[ERROR] connect():");
exit(EXIT_FAILURE);}
char buff[64]={"hello server"};char recv_buff[64]={0};
ssize_t wbytes=send(sockfd,buff,strlen(buff),0);
if(wbytes==-1){
perror("[ERROR] send:");
exit(EXIT_FAILURE);
}ssize_t rbytes=recv(sockfd,recv_buff,sizeof(recv_buff),0);//注意是sizeof
printf("recv data:%s\n",recv_buff);close(sockfd);return 0;}

服务端连接

socket() : 创建套接字, 使用的套接字类型为流式套接字
bind : 绑定 ip 地址与端口号,用于客户端连接服务器
listen : 建立监听队列,并设置套接字的状态为 listen 状态, 表示可以接收连接请求
accept : 接受连接, 建立三次握手, 并创建新的文件描述符, 用于数据传输
recv() : 数据接收
send() : 数据发送
close():关闭socket

socket 套接字状态如下:

CLOSED : 关闭状态
SYN-SENT : 套接字正在试图主动建立连接 [发送 SYN 后还没有收到 ACK],很短暂
SYN-RECEIVE : 正在处于连接的初始同步状态 [收到对方的 SYN,但还没收到自己发过去的SYN 的 ACK]
ESTABLISHED : 连接已建立

函数头文件

#include <sys/types.h>
#include <sys/socket.h>

函数原型:

int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)

函数功能:绑定 ip 地址与端口号

函数参数
sockfd : 套接字文件描述符
buf : 接收缓冲区的地址
len : 接收数据最大长度
flags : 标志位

函数返回值
成功 : 返回 成功接收的字节数
失败 : 返回 -1, 并设置 errno

在服务器绑定 ip 地址与端口号之后, 则需要让服务器 socket 套接字设置成被动监听状态,并创建监听队列,这里需要调用listen函数

listen
函数的详细信息如下:
函数头文件

#include <sys/types.h>
#include <sys/socket.h>

函数原型

int listen(int sockfd,int backlog)

函数功能:设置套接字状态为被动监听,并创建监听队列
函数参数:
sockfd : 套接字文件描述符
backlog : 监听队列的长度

函数返回值
成功 : 返回 0
失败 : 返回 -1, 并设置 errno

在 tcp 服务器中 设置套接字为监听状态, 并创建监听队列
当 客户端 发出连接请求之后, 则需要调用accept函数建立连接
accept 函数具体信息如下:
函数头文件

#include <sys/types.h>
#include <sys/socket.h>

函数原型

int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)

函数功能:接受来自于其他 socket 的连接请求,并建立连接

函数参数
sockfd : 套接字文件描述符
addr : 网络地址结构的指针(输出参数,用于保存发送请求端的地址信息)
addrlen : 网络地址结构长度的指针 (输出参数,但是需要进行初始化)

函数返回值
成功 : 返回新的文件描述符
失败 : -1 , 并设置 errno

示例:设计一个服务器程序,并和客户端建立连接,并打印客户端的 ip 地址和端口号

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define LISTEN_SZ 10int main(int argc,const char *argv[]){int sockfd,ret,accept_fd;
struct sockaddr_in svr_addr;
struct sockaddr_in cli_addr;sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1){
perror("[ERROR] socket():");
exit(EXIT_FAILURE);
}bzero(&svr_addr,sizeof(struct sockaddr_in));
svr_addr.sin_family=AF_INET;
svr_addr.sin_port=htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr=inet_addr(argv[1]);
ret=bind(sockfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr_in));if(ret==-1){
perror("[ERROR] bind():");
exit(EXIT_FAILURE);
}
ret=listen(sockfd,LISTEN_SZ);
if(ret==-1){
perror("[ERROR] listen():");
close(sockfd);
exit(EXIT_FAILURE);
}
socklen_t len=sizeof(struct sockaddr_in);
bzero(&cli_addr,sizeof(struct sockaddr));accept_fd =accept(sockfd,(struct sockaddr *)&cli_addr,&len);
printf("ip :%s,port:%d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));for(;;){}
close(accept_fd);
close(sockfd);
return 0;
}

tcp 服务器数据接收与发送都是使用send函数与recv函数

示例 : 实现 echo 服务器(将客户端发送的数据再重新发送给客户端)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define LISTEN_SZ 10int main(int argc,const char *argv[]){int sockfd,ret,accept_fd;
struct sockaddr_in svr_addr;
struct sockaddr_in cli_addr;char buffer[64]={0};sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1){
perror("[ERROR] socket():");
exit(EXIT_FAILURE);
}bzero(&svr_addr,sizeof(struct sockaddr_in));
svr_addr.sin_family=AF_INET;
svr_addr.sin_port=htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr=inet_addr(argv[1]);
ret=bind(sockfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr_in));if(ret==-1){
perror("[ERROR] bind():");
exit(EXIT_FAILURE);
}
ret=listen(sockfd,LISTEN_SZ);
if(ret==-1){
perror("[ERROR] listen():");
close(sockfd);
exit(EXIT_FAILURE);
}
socklen_t len=sizeof(struct sockaddr_in);
bzero(&cli_addr,sizeof(struct sockaddr));accept_fd =accept(sockfd,(struct sockaddr *)&cli_addr,&len);
printf("ip :%s,port:%d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));for(;;){ssize_t rbytes = recv(accept_fd,buffer,sizeof(buffer),0);
if (rbytes == -1){perror("recv(): ");exit(EXIT_FAILURE);
}else if (rbytes == 0){//说明客户端下线了printf("The client is offline.\n"); exit(EXIT_FAILURE); 
}else if (rbytes > 0){ ssize_t sbytes = send(accept_fd,buffer,sizeof(buffer),0);if (sbytes == -1){perror("[ERROR] send(): ");exit(EXIT_FAILURE);}
}
}
close(accept_fd);
close(sockfd);
return 0;
}

TCP粘包

粘包演示:

tpc_server.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define LISTEN_SZ 10int main(int argc,const char *argv[]){int sockfd,ret,accept_fd;
struct sockaddr_in svr_addr;
struct sockaddr_in cli_addr;char buffer[64]={0};sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1){
perror("[ERROR] socket():");
exit(EXIT_FAILURE);
}bzero(&svr_addr,sizeof(struct sockaddr_in));
svr_addr.sin_family=AF_INET;
svr_addr.sin_port=htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr=inet_addr(argv[1]);
ret=bind(sockfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr_in));if(ret==-1){
perror("[ERROR] bind():");
exit(EXIT_FAILURE);
}
ret=listen(sockfd,LISTEN_SZ);
if(ret==-1){
perror("[ERROR] listen():");
close(sockfd);
exit(EXIT_FAILURE);
}
socklen_t len=sizeof(struct sockaddr_in);
bzero(&cli_addr,sizeof(struct sockaddr));accept_fd =accept(sockfd,(struct sockaddr *)&cli_addr,&len);
printf("ip :%s,port:%d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));for(;;){ssize_t rbytes = recv(accept_fd,buffer,sizeof(buffer),0);if (rbytes == -1){perror("recv(): ");exit(EXIT_FAILURE);
}else if (rbytes == 0){//说明客户端下线了printf("The client is offline.\n"); exit(EXIT_FAILURE); 
}else if (rbytes > 0){ printf("buffer:%s\n",buffer);}sleep(1);//睡眠1秒
}
close(accept_fd);
close(sockfd);
return 0;
}

tcp_client.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
int main(int argc,const char *argv[]){
int sockfd=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in svr_addr;
bzero(&svr_addr,sizeof(svr_addr));
svr_addr.sin_family=AF_INET;
svr_addr.sin_port=htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr=inet_addr(argv[1]);
int ret=connect(sockfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr));if(ret==-1){
perror("[ERROR] connect():");
exit(EXIT_FAILURE);}
char buff[64]={"hello server"};char recv_buff[64]={0};for(;;){
ssize_t wbytes=send(sockfd,buff,strlen(buff),0);
if(wbytes==-1){
perror("[ERROR] send:");
exit(EXIT_FAILURE);
}
usleep(100);//睡眠100u秒<1秒}
close(sockfd);return 0;}

在这里插入图片描述

解决TCP粘包

方式一 : 使用定长数据包, 每次必须要读取固定长度的数据,适用于数据长度是固定的场景

方式二 : 使用数据长度 + 数据的方式,先接收数据长度,再根据长度接收数据, 这里就结合第一种方式,进行固定长度接收,这种方式适用于不定长数据场景。

在这里插入图片描述

方式三 : 使用特殊间隔符,如换行等来区分数据包的边界, 使用较少。

这里使用第二种方式,前四个字节保存长度

tcp_client.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
int main(int argc,const char *argv[]){
int sockfd=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in svr_addr;
bzero(&svr_addr,sizeof(svr_addr));
svr_addr.sin_family=AF_INET;
svr_addr.sin_port=htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr=inet_addr(argv[1]);
int ret=connect(sockfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr));if(ret==-1){
perror("[ERROR] connect():");
exit(EXIT_FAILURE);}
char buff[64]={"hello server"};
int length;
char recv_buff[64]={0};
char *pbuff;for(;;){length=strlen(buff);
pbuff=(char *)malloc(length+4);
memcpy(pbuff,&length,4);
memcpy(pbuff+4,buff,length);ssize_t wbytes=send(sockfd,pbuff,length+4,0);if(wbytes==-1){
perror("[ERROR] send:");
exit(EXIT_FAILURE);
}
usleep(100);}
close(sockfd);return 0;}

tcp_server.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define LISTEN_SZ 10int main(int argc,const char *argv[]){int sockfd,ret,accept_fd;
struct sockaddr_in svr_addr;
struct sockaddr_in cli_addr;char buffer[64]={0};
ssize_t rbytes;
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1){
perror("[ERROR] socket():");
exit(EXIT_FAILURE);
}bzero(&svr_addr,sizeof(struct sockaddr_in));
svr_addr.sin_family=AF_INET;
svr_addr.sin_port=htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr=inet_addr(argv[1]);
ret=bind(sockfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr_in));if(ret==-1){
perror("[ERROR] bind():");
exit(EXIT_FAILURE);
}
ret=listen(sockfd,LISTEN_SZ);
if(ret==-1){
perror("[ERROR] listen():");
close(sockfd);
exit(EXIT_FAILURE);
}
socklen_t len=sizeof(struct sockaddr_in);
bzero(&cli_addr,sizeof(struct sockaddr));accept_fd =accept(sockfd,(struct sockaddr *)&cli_addr,&len);
printf("ip :%s,port:%d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));int length,total_received;
for(;;){
length=0;
total_received=0;rbytes = recv(accept_fd,&length,4,0);for(;;){rbytes=recv(accept_fd,buffer+total_received,length-total_received,0);if (rbytes == -1){perror("recv(): ");exit(EXIT_FAILURE);
}else if (rbytes == 0){//说明客户端下线了printf("The client is offline.\n"); exit(EXIT_FAILURE); 
}else if (rbytes > 0){ 
total_received+=rbytes;
if(total_received==length){
break;
}}
}printf("buffer:%s\n",buffer);
sleep(1);
}
close(accept_fd);
close(sockfd);
return 0;
}

在这里插入图片描述

强化TCPSocket的使用

TCP并发服务器多进程

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<wait.h>#define LISTEN_SZ 10//使用信号量等待子进程退出,防止阻塞
void do_sigchld_handler(int sig){wait(NULL);
}void do_client(int cfd){
char  buffer[1024]={0};
ssize_t sbytes,rbytes;
__sighandler_t retsig;
retsig=signal(SIGCHLD,do_sigchld_handler);
if(retsig==SIG_ERR){
perror("[ERROR] signal():");
exit(EXIT_FAILURE);
}
memset(buffer,0,sizeof(buffer));
rbytes=recv(cfd,buffer,sizeof(buffer),0);
if(rbytes==-1){
perror("recv():");
exit(EXIT_FAILURE);
}else if(rbytes>0){sbytes=send(cfd,buffer,sizeof(buffer),0);}close(cfd);
exit(EXIT_SUCCESS);
}int main(int argc,const char *argv[]){int sockfd,ret,accept_fd;
struct sockaddr_in svr_addr;
struct sockaddr_in cli_addr;sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1){
perror("[ERROR] socket():");
exit(EXIT_FAILURE);
}bzero(&svr_addr,sizeof(struct sockaddr_in));
svr_addr.sin_family=AF_INET;
svr_addr.sin_port=htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr=inet_addr(argv[1]);
ret=bind(sockfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr_in));if(ret==-1){
perror("[ERROR] bind():");
exit(EXIT_FAILURE);
}
ret=listen(sockfd,LISTEN_SZ);
if(ret==-1){
perror("[ERROR] listen():");
close(sockfd);
exit(EXIT_FAILURE);
}
socklen_t len=sizeof(struct sockaddr_in);pid_t cpid;
for(;;){//for循环fork多进程bzero(&cli_addr,sizeof(struct sockaddr));accept_fd =accept(sockfd,(struct sockaddr *)&cli_addr,&len);
printf("ip :%s,port:%d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));cpid=fork();
if(cpid==-1){
perror("[ERROR] fork():");
close(accept_fd);
}
else if(cpid==0){
do_client(accept_fd);}}
close(accept_fd);
close(sockfd);
return 0;
}

TCP并发服务器多线程

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<wait.h>
#include<pthread.h>#define LISTEN_SZ 10void *do_client(void *arg){
char  buffer[1024]={0};
ssize_t sbytes,rbytes;
int cfd=*(int *)arg;
memset(buffer,0,sizeof(buffer));
rbytes=recv(cfd,buffer,sizeof(buffer),0);
if(rbytes==-1){
perror("recv():");
pthread_exit(NULL);
}else if(rbytes==0){printf("the client is  offline:\n");
pthread_exit(NULL);
}
else if(rbytes>0){sbytes=send(cfd,buffer,sizeof(buffer),0);}
pthread_exit(NULL);
}int main(int argc,const char *argv[]){int sockfd,ret,accept_fd;
struct sockaddr_in svr_addr;
struct sockaddr_in cli_addr;sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1){
perror("[ERROR] socket():");
exit(EXIT_FAILURE);
}bzero(&svr_addr,sizeof(struct sockaddr_in));
svr_addr.sin_family=AF_INET;
svr_addr.sin_port=htons(atoi(argv[2]));
svr_addr.sin_addr.s_addr=inet_addr(argv[1]);
ret=bind(sockfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr_in));if(ret==-1){
perror("[ERROR] bind():");
exit(EXIT_FAILURE);
}
ret=listen(sockfd,LISTEN_SZ);
if(ret==-1){
perror("[ERROR] listen():");
close(sockfd);
exit(EXIT_FAILURE);
}
socklen_t len=sizeof(struct sockaddr_in);
int err;
pthread_t tid;
for(;;){bzero(&cli_addr,sizeof(struct sockaddr));accept_fd =accept(sockfd,(struct sockaddr *)&cli_addr,&len);
printf("ip :%s,port:%d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));
//创建多线程
err=pthread_create(&tid,NULL,do_client,(void *)&accept_fd);
pthread_detach(tid);}
close(accept_fd);
close(sockfd);
return 0;
}

文件上传工具

文件上传工具主要分为如下几个模块
tcp socket 模块 : 主要封装了 tcp socket 的操作
file_transfer 模块 : 实现 文件传输相关操作
debug 模块: 用于打印格式化的调试信息

在这里插入图片描述

TcpSocket的封装

debug.h 打印日志信息

#ifndef _DEBUG_H_
#define _DEBUG_H_#define DEBUG_INFO(args...) do{ \
char b__[1024];\
sprintf(b__,args);\
fprintf(stderr,"[%s,%s,%d] : %s",__FILE__,__FUNCTION__,__LINE__,b__);\
}while (0)#endif

tcpsockert.h

#ifndef __TCP_SOCKET_H_
#define __TCP_SOCKET_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
//创建服务端连接
extern int create_tcp_server_socket(const char *ip,unsigned short port);//创建客户端连接
extern int create_tcp_client_socket(const char *svr_ip,const unsigned short svr_port);extern int wait_for_connect(int sfd,struct sockaddr_in *cli_addr);extern void show_tcp_network_address(struct sockaddr_in *sockaddr);
extern ssize_t tcp_send_pack(int sockfd,const void *buf,size_t len);
extern ssize_t tcp_recv_pack(int sockfd,void *buf,size_t len );
#endif

tcpsocket.c

#include"tcpsocket.h"
#include"debug.h"
#define BACKLOG 10int create_tcp_server_socket(const char *ip,unsigned short port){
int ret;
int sfd;struct sockaddr_in svr_addr;
sfd=socket(AF_INET,SOCK_STREAM,0);if(sfd==-1){
DEBUG_INFO("[ERROR] :%s\n",strerror(errno));
return -1;
}
bzero(&svr_addr,sizeof(svr_addr));
svr_addr.sin_family=AF_INET;
svr_addr.sin_port=htons(port);
svr_addr.sin_addr.s_addr=inet_addr(ip);
ret=bind(sfd,(const struct sockaddr *)&svr_addr,sizeof(svr_addr));
if(ret==-1){
DEBUG_INFO("[ERROR]:%s",strerror(errno));
return -1;}ret=listen(sfd,BACKLOG);
if(ret==-1){DEBUG_INFO("[ERROR]:%s\n",strerror(errno));
return -1;
}return sfd;
}int create_tcp_client_socket(const char *svr_ip,const unsigned short svr_port){int ret;
int sfd;
struct sockaddr_in svr_addr;sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1){DEBUG_INFO("[ERROR] :%s\n",strerror(errno));
return -1;
}bzero(&svr_addr,sizeof(svr_addr));
svr_addr.sin_family=AF_INET;
svr_addr.sin_port=htons(svr_port);
svr_addr.sin_addr.s_addr=inet_addr(svr_ip);ret=connect(sfd,(const struct sockaddr *)&svr_addr,sizeof(struct sockaddr));
if(ret=-1){
DEBUG_INFO("[ERROR]:%s\n",strerror(errno));
return -1;
}
DEBUG_INFO("[INFO] :Connect %s succeedd.\n",svr_ip);
return sfd;
}int wait_for_connect(int sfd,struct sockaddr_in *cli_addr){
int cfd;
socklen_t len=sizeof(struct sockaddr_in);
cfd=accept(sfd,(struct sockaddr *) cli_addr,&len);return cfd;}void show_tcp_network_address(struct sockaddr_in *sockaddr){printf("ip : %s\n",inet_ntoa(sockaddr->sin_addr)); printf("port : %d\n",ntohs(sockaddr->sin_port));}
ssize_t tcp_send_pack(int sockfd,const void *buf,size_t len){ return send(sockfd,buf,len,0);
}ssize_t tcp_recv_pack(int sockfd,void *buf,size_t len ){ return recv(sockfd,buf,len,0);
}

编写服务端的main函数

#include "tcpsocket.h"
#include<unistd.h>int main(int argc,char *argv[]){int sfd,cfd;struct sockaddr_in cli_addr;sfd=create_tcp_server_socket(argv[1],atoi(argv[2]));
if(sfd==-1) exit(EXIT_FAILURE);
bzero(&cli_addr,sizeof(struct sockaddr_in));
cfd=wait_for_connect(sfd,&cli_addr);
if(cfd==-1) exit(EXIT_FAILURE);show_tcp_network_address(&cli_addr);
close(cfd);
close(sfd);return 0;}

编写客户端的main函数

#include "tcpsocket.h"
#include <unistd.h>int main(int argc,char *argv[]){int cfd;
cfd=create_tcp_client_socket(argv[1],atoi(argv[2]));if(cfd==-1) exit(EXIT_FAILURE);
close(cfd);
return 0;}
文件传输模块设计

在文件数据传输的过程中,设计了相应的协议,用于传输文件大小与文件名,具体的设计如下:
在这里插入图片描述

typedef struct file_protocol{ 
size_t filesize;
char filename[FILENAME_SZ]; 
}file_protocol_t;

为防止 tcp 底层粘包, 在传输文件之前先接收协议头信息,在根据协议中的文件大小与文件名,来接收文件数据

file_transfer.h

#include"tcpsocket.h"
#include"debug.h"#define FILENAME_SZ 100typedef struct file_protocol{
size_t filesize;
char filename[FILENAME_SZ];
}file_protocol_t;extern int recv_protocol_head(int cfd,file_protocol_t *p_head);

file_transfer.c

#include"file_transfer.h"
//给file_protocol_t赋值
int recv_protocol_head(int cfd,file_protocol_t *p_head){
ssize_t rbytes;
ssize_t total_received=0;
char *buffer=(char *)p_head;
for(;;){rbytes=tcp_recv_pack(cfd,buffer+total_received,sizeof(file_protocol_t)-total_received);
if(rbytes==-1){
DEBUG_INFO("[ERROR]:%s\n",strerror(errno));
return -1;
}
else if(rbytes==0){
DEBUG_INFO("[INFO]: The client has been shut down!\n");
return-1;
}
else if(rbytes>0){
total_received+=rbytes;
if(total_received==sizeof(file_protocol_t)){break;
}
}if(total_received!=sizeof(file_protocol_t)){
DEBUG_INFO("[ERROR] :Failed to  receive data\n");;return -1;
}}return 0;
}

设计接收数据的函数主要功能如下:

  • 接收数据
  • 将接收的数据写入到文件中

在file_transfer.h中引入文件IO的头文件

#include <unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

在file_transfer.c中

int recv_filedata(int cfd,const char *filename,size_t targetsize){
int fd;
ssize_t rbytes=0,wbytes=0,file_size=0;
char buffer[1024]={0};
DEBUG_INFO("[INFO]:filename %s\n",filename);
fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd==-1){DEBUG_INFO("[ERROR] Failed to open filename");return -1;
}for(;;){
rbytes=tcp_recv_pack(cfd,buffer,sizeof(buffer));
if(rbytes>0){
wbytes=write(fd,buffer,rbytes);
if(wbytes!=rbytes){
DEBUG_INFO("[ERROR]: Failed to write data\n");
return -1;
}
file_size+=rbytes;
if(file_size==targetsize) break;
}
}
close(fd);
return file_size;}

设计文件上传接口的基本逻辑为 :
接收文件头,获取文件大小与文件名
接收数据, 并将数据写入到文件中

int client_uplod_file(int cfd){
int ret;
char *filename;
size_t filesize=0,recvsize=0;
file_protocol_t head;
ret=recv_protocol_head(cfd,&head);
filename=head.filename;
filesize=head.filesize;
recvsize=recv_filedata(cfd,filename,filesize);
return recvsize;
}

在发送文件之前,需要向服务器发送文件名与文件大小(文件头):

int sed_protocol_head(const char *filename,int sockfd){
int fd;
int filesize;
file_protocol_t head;
fd=open(filename,O_RDONLY);
if(fd==-1){DEBUG_INFO("[ERROR] failed to open filename\n");
return -1;
}filesize=lseek(fd,0,SEEK_END);
close(fd);
head.filesize=filesize;
strcpy(head.filename,filename);
ret=tcp_send_pack(sockfd,&head,sizeof(head));
if(ret!=sizeof(file_protocol_t)){
DEBUG_INFO("[ERROR] Failed to send protocol head\n");
return -1;
}return filesize;
}

文件上传发送接口主要用于客户端发送文件数据给服务器,具体设计如下:

边读文件内容边发送数据

int upload_file(const char  *filename,int sockfd){int file_size,upload_size;
int fd;
ssize_t rbytes=0,sbytes=0;
char buffer[1024]={0};
file_size=send_protocol_head(filename,sockfd);
if(file_size<0){
return -1;
}
fd=open(filename,O_RDONLY);
if(fd==-1){
DEBUG_INFO("[ERROR] Failed to open filename\n");
return -1;
}
for(;;){
rbytes=read(fd,buffer,sizeof(buffer));
if(rbytes==-1){DEBUG_INFO("[ERROR] Failed to read  data\n");return -1;
}
else if(rbytes==0){break;
}//发送数据给服务端
sbytes=tcp_send_pack(sockfd,buffer,rbytes);
if(sbytes==-1){
DEBUG_INFO("[ERROR] Failed to read data\n");
return -1;
}upload_size+=rbytes;}close(fd);
return upload_size;}

服务端代码

#include "tcpsocket.h"
#include<unistd.h>
#include"debug.h"
#include"file_transfer.h"
#include<pthread.h>void *do_task(void *arg){
size_t size;
int cfd=*(int *)arg;
size=client_upload_file(cfd);
printf("client upload file size :%ld\n",size);
pthread_exit(NULL);
}int main(int argc,char *argv[]){int sfd,cfd;
int ret;
pthread_t tid;
struct sockaddr_in cli_addr;sfd=create_tcp_server_socket(argv[1],atoi(argv[2]));
if(sfd==-1) exit(EXIT_FAILURE);
bzero(&cli_addr,sizeof(struct sockaddr_in));for(;;){
cfd=wait_for_connect(sfd,&cli_addr);
if(cfd==-1) exit(EXIT_FAILURE);
show_tcp_network_address(&cli_addr);
ret=pthread_create(&tid,NULL,do_task,(void *)&cfd);
if(ret!=0){DEBUG_INFO("[ERROR] pthread_create():%s\n",strerror(errno));
}
pthread_detach(tid);}
close(cfd);
close(sfd);return 0;}

客户端代码

#include "tcpsocket.h"
#include <unistd.h>
#include"file_transfer.h"int main(int argc,char *argv[]){
if(argc!=4){
fprintf(stderr,"Usage :%s<ip> <port><pathname>\n",argv[0]);
exit(EXIT_FAILURE);
}
int cfd;
cfd=create_tcp_client_socket(argv[1],atoi(argv[2]));
if(cfd==-1) exit(EXIT_FAILURE);upload_file(argv[3],cfd);close(cfd);
return 0;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/349813.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Zynq7000 系列FPGA模块化仪器

• 基于 XilinxXC7Z020 / 010 / 007S • 灵活的模块组合 • 易于嵌入的紧凑型外观结构 • 高性能的 ARM Cortex 处理器 • 成熟的 FPGA 可编程逻辑 &#xff0c;基于 IP 核的软件库 FPGA 控制器 Zynq7000 系列模块是基于 Xilinx XC7Z020/010/007S 全可编程片上系统 (SoC) 的…

Opengauss开源4年了,都谁在向其贡献代码?

2020 年 6 月 30 日&#xff0c;华为将Opengauss正式开源&#xff0c;截止目前已经过去4年时间&#xff0c;社区力量对这款数据库产品都起到了哪些作用&#xff0c;谁的代码贡献更大一些&#xff1f; 根据社区官网信息统计&#xff0c;截止目前&#xff08;2024年6月12日&…

【Java基础】OkHttp 超时设置详解

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

光纤跳线(又称光纤连接器)的种类

光纤跳线&#xff08;又称光纤连接器&#xff09;&#xff0c;也就是接入光模块的光纤接头&#xff0c;也有好多种&#xff0c;且相互之间不可以互用。SFP模块接LC光纤连接器&#xff0c;而GBIC接的是SC光纤连接器。下面对网络工程中几种常用的光纤连接器进行详细的说明&#x…

数字化制造案例分享以及数字化制造能力评估(34页PPT)

资料介绍&#xff1a; 通过全面的数字化企业平台和智能制造技术的应用&#xff0c;制造型企业不仅提升了自身的竞争力&#xff0c;也为整个制造业的数字化转型提供了借鉴。同时&#xff0c;数字化制造能力的评估是企业实现数字化转型的关键环节&#xff0c;需要从技术变革、组…

【C++】STL中list的使用

前言&#xff1a;在前面学习的 过程中我们学习了STL中的string,vector&#xff0c;今天我们来进一步的学习STL中的list的使用方法。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:高质量&#xff23;学习 &#x1f448; &#x1f4af;代…

Flowable-决策表设计器

✨✨✨ 最好用的Flowable决策表设计器 ✨✨✨ 最好用的Flowable流程设计器 本文中内容和案例出自贺波老师的书《深入Activiti流程引擎&#xff1a;核心原理与高阶实战》&#xff0c;书中的介绍更全面、详细&#xff0c;推荐给大家。 深入Activiti流程引擎

今年的就业环境不容乐观,你想好怎么应对了吗

今年的就业环境不容乐观&#xff0c;你想好怎么应对了吗 毕业生进入职场的历程往往充满挑战和未知&#xff0c;尤其是在当前经济环境下&#xff0c;失业问题愈发凸显。本文通过分享几位年轻人的真实经历&#xff0c;剖析大学生及职场人士面临的困境&#xff0c;并提供应对策略…

QT信号与槽/窗口组件优化

使用手动连接&#xff0c;将登录框中的取消按钮使用第二中连接方式&#xff0c;右击转到槽&#xff0c;在该槽函数中&#xff0c;调用关闭函数 将登录按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断u界面上输入的账号是否为"admin"&#xff0c;…

简单聊一下Oracle,MySQL,postgresql三种锁表的机制,行锁和表锁

MySQL&#xff1a; MySQL使用行级锁定和表级锁定。行级锁定允许多个会话同时写入表&#xff0c;适用于多用户、高并发和OLTP应用。表级锁定只允许一个会话一次更新表&#xff0c;适用于只读、主要读取或单用户应用。 比如mysql开启一个窗口执行 begin; update xc_county_a…

渗透测试和红蓝对抗是什么?二者之间有何区别?

在网络安全这个庞大的体系中&#xff0c;渗透测试、红蓝对抗是比较常见的专业名词&#xff0c;承担着非常重要的作用&#xff0c;那么什么是渗透测试、红蓝对抗?红蓝对抗和渗透测试有什么区别?小编通过这篇文章为大家介绍一下。 渗透测试 渗透测试&#xff0c;是通过模拟黑…

32T存储删除视频的恢复方法

由于存储技术的发展和普及目前很多行业都开始使用小型存储&#xff0c;NAS可以通过网络进行数据上传和读取&#xff0c;使用极为方便。但是由于NAS设备容量较大且碎片较多&#xff0c;所以此类设备删除或者格式后恢复难度是比较大的&#xff0c;下边我们来分享下32T存储的恢复方…

理解查准率P、查全率R及Fβ度量怎么得来的

如果得到的是一组样本在两个算法上的一次预测结果&#xff0c;其中每个样本都被赋予了一个为正样本的概率&#xff08;例如&#xff0c;通过逻辑回归或朴素贝叶斯分类器得到的概率估计&#xff09;&#xff0c;那么可以通过改变不同的阈值点来利用这些预测结果画出PR曲线。 如果…

python数据分析-房价数据集聚类分析

一、研究背景和意义 随着房地产市场的快速发展&#xff0c;房价数据成为了人们关注的焦点。了解房价的分布特征、影响因素以及不同区域之间的差异对于购房者、房地产开发商、政府部门等都具有重要的意义。通过对房价数据的聚类分析&#xff0c;可以深入了解房价的内在结构和规…

【计算机毕业设计】258基于微信小程序的课堂点名系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

代码签名证书如何选择

代码签名证书分为OV代码签名证书和EV代码签名证书。 OV代码签名证书在申请时只需要验证申请主体的真实性&#xff0c;部署安装后可以保护代码的完整性&#xff0c;防止代码被篡改&#xff0c;携带不良信息。 EV代码签名证书是OV代码签名证书的升级版&#xff0c;对代码的保护…

轮到国产游戏统治Steam榜单

6月10日晚8点&#xff0c;《黑神话:悟空》实体版正式开启全款预售,预售开启不到5分钟,所有产品即宣告售罄。 Steam上&#xff0c;《黑神话:悟空》持续占据着热销榜榜首的位置。 但在《黑神话:悟空》傲人的光环下&#xff0c;还有一款国产游戏取得出色的成绩。 6月10日&#…

Nacos启动报错

报错日志&#xff1a; Caused by: java.lang.NullPointerException at com.mysql.jdbc.ConnectionImpl.getServerCharset(ConnectionImpl.java:2983) at com.mysql.jdbc.MysqlIO.sendConnectionAttributes(MysqlIO.java:1873) at com.mysql.jdbc.Mysql…

vue+elementUI实现在表格中添加输入框并校验的功能

背景&#xff1a; vue2elmui 需求&#xff1a; 需要在一个table中添加若干个输入框&#xff0c;并且在提交时需要添加校验 思路&#xff1a; 当需要校验的时候可以考虑添加form表单来触发校验&#xff0c;因此需要在table外面套一层form表单&#xff0c;表单的属性就是ref…

el-cascader 支持多层级,多选(可自定义限制数量),保留最后一级

多功能的 el-cascader 序言&#xff1a;最近遇到一个需求关于级联的&#xff0c;有点东西&#xff0c;这里是要获取某个产品类型下的产品&#xff0c;会存在产品类型和产品在同一级的情况&#xff0c;但是产品类型不能勾选&#xff1b; 情况1&#xff08;二级菜单是产品&…