Linux之网络编程

news2024/6/29 19:40:30

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 0



int 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 message

	memset(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 0




void 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 10

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);
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 10

int 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 10

int 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 10

int 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 10

void *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 10

int 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 100

typedef 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.coloradmin.cn/o/1837220.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gitblit git pycharm 新建版本库及push备忘

在终端l中输入ssh,如果有消息弹出说明安装成功。 // 在任意路径打开GIT BASH,执行以下命令,期间所有询问可以直接Enter跳过 ssh-keygen -t rsa -C "注册Gitlab的邮箱" “”之内可以任何文字,备注提示作用。 设置用户名和邮箱 已经设置的可以检查一下。 #设置用…

Aiflow中,代码逻辑中明明不该触发的方法但是却触发了。

图中这个红圈的task&#xff0c;是我更新error记录的task&#xff0c;是某些特定的task特定情况会触发的。正常情况走的最下面的箭头的路径。但是现在就是就算只走了下面箭头的路径&#xff0c;红圈那个task依然被触发了。检查了半天才发现&#xff0c;它的TriggerRule设置的是…

Git记录 上传至Gitee

1.GitHub拉去的代码需要上传至自己的Gitee需要清除原有remote服务器信息 查看原始远程服务器信息&#xff0c;后删除远程服务器信息 git remote -v git remote rm origin 2.Gitee新建软件仓库 法1&#xff09;不用初始化仓库&#xff0c;初始化会自动生成.git。如果本地.git…

【Linux Vim的保姆级教程】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

MongoDB和AI 赋能行业应用:零售

欢迎阅读“MongoDB 和 AI 赋能行业应用”系列的第三篇。 本系列重点介绍 AI 应用于不同行业的关键用例&#xff0c;涵盖制造业和汽车行业、金融服务、零售、电信和媒体、保险以及医疗保健行业。 利用生成式 AI 技术&#xff08;Gen AI&#xff09;&#xff0c;零售商可以创造…

内网横向渗透篇

目录 一.什么是内网横向渗透 二.域环境搭建 一.什么是内网横向渗透 内网横向渗透是指攻击者在成功进入企业或组织的内部网络之后&#xff0c;试图扩展其访问权限和影响力&#xff0c;以便获取更多敏感信息或执行更深入的攻击。 拓扑图: 以下是内网横向渗透的简要步骤和策略&…

C++ 68 之 类模版作函数的参数

#include <iostream> // #include <cstring> #include <string> using namespace std;template<class T1, class T2> // 可以设置默认的类型值&#xff0c;后面在使用的时候&#xff0c;就不用再指定类型了 class Students08{ public:T1 m_name;T2 m_a…

mybatis框架相关问题总结(本地笔记搬运)

1、背景 2、运行启动问题 问题一 运行spring boot项目时报错&#xff1a;‘factoryBeanObjectType‘: java.lang.String 解决一 版本问题&#xff0c;springframework版本和mybatis/mybatis-plus版本不兼容。现spring-boot使用3.3.0版本&#xff0c;mybatis-plus使用3.5.7…

0618_QT4

练习&#xff1a; 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和…

骁龙662_高通SM6115主要参数_高通模块方案定制

骁龙662&#xff08;SM6115&#xff09;采用了全新的44 Kryo 260 CPU架构&#xff0c;由四核Cortex-A73(高达2.0 GHz)和四核Cortex-A53(高达1.8 GHz)组成。这种架构的设计使得骁龙662在性能上相较于上一代产品有了显著的提升&#xff0c;为用户提供了更快的运行速度和更流畅的使…

【机器学习300问】123、什么是GRU?GRU网络的基本结构是怎样的?

在之前的文章中&#xff0c;我们谈到了标准RNN所面临的诸多困境&#xff0c;你也可以理解为RNN的缺点。其中最让人苦恼的就是梯度消失问题&#xff0c;正是由于梯度消失问题的存在&#xff0c;导致RNN无法获得上下文的长期依赖信息。那么就没有办法解决了吗&#xff1f;非也&am…

查看服务器端口,如何查看服务器端口是多少并修改

查看服务器端口并修改内容是一个涉及网络管理和系统配置的专业任务。以下是一个详细的步骤说明&#xff0c;用于查看和修改服务器端口。 一、查看服务器端口 1. 使用命令行工具&#xff1a; - 对于Linux或Unix系统&#xff0c;可以使用netstat、lsof或ss等命令来查看端口状…

深度神经网络——什么是降维?

引言 什么是降维&#xff1f; 降维是用于降低数据集维度的过程&#xff0c;采用许多特征并将它们表示为更少的特征。 例如&#xff0c;降维可用于将二十个特征的数据集减少到仅有几个特征。 降维通常用于无监督学习任务 降维是一个用于降低数据集维度的过程&#xff0c;采用许…

JupyterLab使用指南(四):JupyterLab的Magic 命令

1. 什么是 Magic 命令 Magic 命令是 JupyterLab 中的一类特殊命令&#xff0c;用于简化和增强代码的执行。它们以 % 或 %% 开头&#xff0c;可以进行各种操作&#xff0c;如时间测量、环境设置、文件操作等。Magic 命令分为行 Magic 命令和单元 Magic 命令两种。 行 Magic 命…

Transformer预测 | 基于Transformer的锂电池寿命预测(Pytorch,CALCE数据集)

文章目录 文章概述模型描述程序设计参考资料文章概述 Pytorch实现基于Transformer 的锂电池寿命预测,环境为pytorch 1.8.0,pandas 0.24.2 随着充放电次数的增加,锂电池的性能逐渐下降。电池的性能可以用容量来表示,故寿命预测 (RUL) 可以定义如下: SOH(t)=CtC0100%, 其中,…

远程访问电脑共享文件怎么设置

方法一&#xff1a;Microsoft远程桌面 1、在目标电脑上设置远程桌面&#xff1a; - 打开“开始”>“设置”>“系统”>“远程桌面”。 - 将“启用远程桌面”开关向右拖动以激活该功能。 - 记住计算机的账号和密码。 2、在手机上进行远程访问配置&#xff1a; - 从Googl…

数字孪生引领智慧校园建设新篇章

一、引言 在数字化浪潮的推动下&#xff0c;教育行业正经历着一场深刻的变革。智慧校园作为现代教育的新风向&#xff0c;通过整合先进的信息技术&#xff0c;正在逐步改变传统的教学、管理与服务模式。其中&#xff0c;数字孪生技术以其独特的优势&#xff0c;为智慧校园的建…

Day 26:2288. 价格减免

Leetcode 2288. 价格减免 句子 是由若干个单词组成的字符串&#xff0c;单词之间用单个空格分隔&#xff0c;其中每个单词可以包含数字、小写字母、和美元符号 ‘$’ 。如果单词的形式为美元符号后跟着一个非负实数&#xff0c;那么这个单词就表示一个 价格 。 例如 “$100”、…

【论文阅读】Multi-Camera Unified Pre-Training via 3D Scene Reconstruction

论文链接 代码链接 多摄像头三维感知已成为自动驾驶领域的一个重要研究领域&#xff0c;为基于激光雷达的解决方案提供了一种可行且具有成本效益的替代方案。具有成本效益的解决方案。现有的多摄像头算法主要依赖于单目 2D 预训练。然而&#xff0c;单目 2D 预训练忽略了多摄像…

API-操作元素内容

学习目标&#xff1a; 掌握操作元素内容 学习内容&#xff1a; 操作元素内容元素innerText属性元素innerHTML属性案例 操作元素内容&#xff1a; DOM对象都是根据标签生成的&#xff0c;所以操作标签&#xff0c;本质上就是操作DOM对象。就是操作对象使用的点语法。如果想要修…