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;}相关文章:
Linux之网络编程
Linux之网络编程 TCP协议 TCP(Transmission ControlProtocol) : 传输控制协议,是一个 面向连接的、可靠的、基于字节流的传输层的协议。TCP 协议建立的是一种点到点的,一对一的可靠连接协议 特点: 数据无丢失数据无失序数据无错误数据无重…...
opencascade AIS_InteractiveContext源码学习1
AIS_InteractiveContext 前言 交互上下文(Interactive Context)允许您在一个或多个视图器中管理交互对象的图形行为和选择。类方法使这一操作非常透明。需要记住的是,对于已经被交互上下文识别的交互对象,必须使用上下文方法进行…...
TIA博途 WinCC下载到面板时,提示错误消息:“装载过程终止由于传输错误:8020AB001A06FFF4!”的解决办法
TIA博途 WinCC下载到面板时,提示错误消息:“装载过程终止由于传输错误:8020AB001A06FFF4!”的解决办法 这个错误信息是由于缺少设备镜像无法下载到操作面板而导致的。 当使用 TIA V15.1 Update 4 和 Update 5 组态 TP1000F Mobile 时,请遵守特别注意事项。 问题 在编译一个…...
【MySQL】聊聊数据库是如何保证数据不丢的
对于一个存储系统来说,其中比较关键的核心组件包含,网络、存储模型、持久化、数据结构等。而数据如何保证不丢失,对于不同的存储系统来说,比如Redis采用AOF和RDB的方式进行混合使用,而MySQL采用日志进行保证。也就是re…...
GitLab教程(四):分支(branch)和合并(merge)
文章目录 1.分支(branch)(1)分支的概念(2)branch命令 2.合并(merge)(1)三个命令pullfetchmergegit fetchgit mergegit pull (2)合并冲…...
2021数学建模A题目–“FAST”主动反射面的形状调节
A 题——“FAST”主动反射面的形状调节 思路:该题主要是通过利用伸缩杆调整FAST反射面,给出合适的调整方案 程序获取 第一题问题思路与结果: 当待观测天体S位于基准球面正上方,结合考虑反射面板调节因素,确定理想抛物…...
华为---- RIP路由协议基本配置
08、RIP 8.1 RIP路由协议基本配置 8.1.1 原理概述 RIP(Routing Information Protocol,路由协议)作为最早的距离矢量IP路由协议,也是最先得到广泛使用的一种路由协议,采用了Bellman-Ford算法,其最大的特点就是配置简单。 RIP协议要求网络中…...
Android studio在Ubuntu桌面上 创建桌面图标,以及导航栏图标
Android studio在Ubuntu桌面上 创建桌面图标,以及导航栏图标 1. 下载Android studio for Lunux 免安装版本之后,解压 2. 通过控制台运行 ~/Documents/android-studio-2024.1.1.2-linux/android-studio/bin$ ./studio.sh 3. 选择菜单,Tools…...
JAVA云HIS医院管理系统源码 云HIS系统的应用场景
JAVA云HIS医院管理系统源码 云HIS系统的应用场景 云HIS是针对中小医疗健康机构推出的一套基于云端的诊所云HIS服务平台,包括内部管理系统、临床辅助决策系统、体检系统、客户管理与服务系统、健康管理系统、知识管理系统、医患沟通系统、线上营销系统、其他外部系…...
Handler机制
目录 一、简介二、相关概念解释2.1 Message(消息)2.2 Handler(处理器)2.2.1 Handler的构造方法2.2.2 Handler sendMessage()相关的方法2.2.3 Handler dispatchMessage()方法 2.3 Mes…...
鸿蒙实现金刚区效果
前言: DevEco Studio版本:4.0.0.600 所谓“金刚区"是位于APP功能入口的导航区域,通常以“图标文字”的宫格导航的形式出现。之所以叫“金刚区”,是因为该区域会随着业务目标的改变,展示不同的功能图标ÿ…...
Ubuntu 查看设备温度
要在Ubuntu中查看设备的温度,可以使用一些命令行工具来获取系统硬件的温度信息。下面列出了几种常用的方法: 方法 1: 使用 sensors 命令 sensors 命令用于读取和显示系统中的传感器数据,包括CPU温度和其他硬件传感器的信息。首先需要安装 l…...
大型网站优化指南:打造流畅的在线体验
大型网站 大型网站是指具有高并发、大流量、高可用性、海量数据处理能力,并能提供7*24小时不间断服务的网站。 这些网站通常面临用户分布广泛、网络情况复杂、安全环境恶劣等挑战。 同时需要快速适应市场变化和用户需求,通过渐进式的发展策略运营成大型…...
Redis变慢了?
Redis变慢了? 什么是Redis?测定Redis变慢?最大响应延迟平均响应延迟设置Redis慢日志 分析Redis变慢bigkeysbigkey的危害bigkey优化 写在最后 什么是Redis? 作为一个技术人员来说,大家用的最多的可能就是Redis了&#…...
11.6.k8s实战-节点扩缩容
目录 一,需求描述 二、集群缩容-节点下线 1,节点下线案例说明 2,查看现有节点 3,查看所有名称空间下的pod 编辑4,驱逐下线节点的pod 5,驱逐后再次查看pod 6,驱逐pod后再次查看节点信息…...
相亲交友APP系统|婚恋交友社交软件|语音聊天平台定制开发
在现代社会,婚恋交友已经成为了人们日常生活中的一项重要任务。为了方便用户进行相亲交友活动,各种相亲交友APP系统和婚恋交友社交软件应运而生。本文将介绍相亲交友APP系统、婚恋交友社交软件的开发以及语音聊天平台的定制开发的相关知识和指导。 一、…...
2005-2022年款福特福克斯维修手册和电路图线路图接线图资料更新
经过整理,2005-2022年款福特福克斯全系列已经更新至汽修帮手资料库内,覆盖市面上99%车型,包括维修手册、电路图、新车特征、车身钣金维修数据、全车拆装、扭力、发动机大修、发动机正时、保养、电路图、针脚定义、模块传感器、保险丝盒图解对…...
nodejs爬取小红书图片
昨天的文章已经描述了可以抓取评论区内容, 抓取图片内容和抓取评论区的内容基本一致 我们可以看到接口信息中含有图片链接,我们要做的就是爬取图片链接然后下载 这边要用到的模块为const downloadrequire(download) 将爬到的图片链接存放到images数组…...
MySQL从5.7升级到8.0步骤及其问题
MySQL从5.7升级到8.0步骤及其问题 前言 本文源自微博客,且以获得授权,请尊重版权。 一、需求背景 Docker环境下,MySQL5.7升级到8.0,数据迁移时使用的是mysqldump方式迁移。 二、迁移步骤 数据备份: docker exec -i 1…...
中年帕金森:守护健康,从容面对生活挑战
在快节奏的现代生活中,中年人群面临着越来越多的健康挑战。其中,帕金森病作为一种常见的神经系统疾病,逐渐引起了人们的关注。帕金森病不仅影响患者的身体健康,还对其日常生活造成极大的困扰。那么,我们该如何应对中年…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...
使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...
C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...
Java并发编程实战 Day 11:并发设计模式
【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天,今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案,它们不仅提供了优雅的设计思路,还能显著提升系统的性能…...
数据分析六部曲?
引言 上一章我们说到了数据分析六部曲,何谓六部曲呢? 其实啊,数据分析没那么难,只要掌握了下面这六个步骤,也就是数据分析六部曲,就算你是个啥都不懂的小白,也能慢慢上手做数据分析啦。 第一…...
Easy Excel
Easy Excel 一、依赖引入二、基本使用1. 定义实体类(导入/导出共用)2. 写 Excel3. 读 Excel 三、常用注解说明(完整列表)四、进阶:自定义转换器(Converter) 其它自定义转换器没生效 Easy Excel在…...
无需布线的革命:电力载波技术赋能楼宇自控系统-亚川科技
无需布线的革命:电力载波技术赋能楼宇自控系统 在楼宇自动化领域,传统控制系统依赖复杂的专用通信线路,不仅施工成本高昂,后期维护和扩展也极为不便。电力载波技术(PLC)的突破性应用,彻底改变了…...
