前两篇博客实现了旭日x3与ESP32之间的UDP多机通信。正巧我手边有三块旭日X3派,如何实现旭日x3派的多机通讯呢?答案是显而易见的——TCP通信!
TCP和UDP区别:
1.TCP简介
2.TCP报文
首部中的重要概念
序号:Seq序号,占32位。用于说明当前数据第一个字节在所有数据(整个文件)中的位置
确认号:Ack序号,占32位。用于告诉发送者接下来需要发送的数据序号。
数据偏移:用于说明首部长度。(比如1111说明首部长为15*4字节)
标志位:tcp标志位有6种:
SYN(synchronous发起一个新连接)
ACK(acknowledgement 确认)
PSH(push传送)
FIN(finish结束)
RST(reset重置)
URG(urgent紧急)
窗口:表示发送/接收缓存窗口大小。如下图。
3.TCP建立与断开连接
3.1 TCP的三次握手
三次握手其实就是建立连接的过程。其过程如图:
1)第一次握手:Client将标志位SYN(建立新连接)置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK(确认)都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
3)第三次握手:Client收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送给Server,Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
3.2 TCP的四次挥手
两块旭日X3间TCP通信demo实现!
套接字socket = (IP地址+端口号)
可以认为, 套接字就是网络编程的接口,是连接网络的一种工具。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
1)domain 是协议族,也就是 IP 地址类型,常用的有 AF_INET(IPv4) 和 AF_INET6(IPv6)
2)type 为数据传输方式,常用的有 SOCK_STREAM (面向连接的数据传输方式)和 SOCK_DGRAM(无连接的数据传输方式)
3)protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议
bind() 函数
bind() 函数.服务器端 将套接字与特定的IP地址和端口绑定。
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
成功返回0,失败返回-1。
sockfd 要分配地址信息(IP地址和端口号)的套接字文件描述符
myaddr 存有地址信息的结构体变量地址值
addrlen 第二个结构体变量的长度
其中的结构体原型:
struct sockaddr_in
{
sa_family_t sin_family;//地址族
uint16_t sin_port;//16位tcp/udp端口号,以网络字节序保存
struct in_addr sin_addr;//32位IP地址,以网络字节序保存
char sin_zero[8];//不使用,必需为0,
为使结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员
};
该结构体的初始化举例:
struct sockaddr_in addr;
char * serv_ip = "211.217.168.13"; //声明IP地址字符串
char * serv_port = "9190"; //声明端口号字符串
memset(&addr, 0, sizeof(addr)); //结构体变量addr的所有成员置零
addr.sin_family = AF_INET; //指定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); //基于字符串的IP地址初始化
addr.sin_port = htons(atoi(serv_port)); //基于字符串的端口号初始化
connect() 函数
connect() 函数由客户端用来建立连接
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
listen()函数
listen()函数让套接字进入被动监听状态。
int listen(int sockfd, int backlog);
accept()函数
accept() 函数当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
addr 保存发起连接请求的客户端地址信息的变量地址值 .函数调用成功时,accept函数内部将产生用于数据I/O的套接字,并返回其文件描述符,套接字自动创建并自动与发起连接的客户端建立连接 。
send()与recv()函数
send() 函数发送数据,recv() 函数接受数据
int send(int sockfd, char *buf, int len, int flags);
int recv(int sockfd, char *buf, int len, int flags);
buf 为要发送的数据的缓冲区地址,len 为要发送的数据的字节数,flags 为发送数据时的选项。
closesocket()函数关闭套接字
服务端旭日X3代码(C/C++):
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#define MAXDATASIZE 100
#define PORT 8087
#define KEY 123
#define SIZE 1024
int main()
{
char buf[100];
memset(buf,0,100);
int server_sockfd,client_sockfd;
socklen_t server_len,client_len;
struct sockaddr_in server_sockaddr,client_sockaddr;
/*建立服务端套接字*/
server_sockfd = socket(AF_INET,SOCK_STREAM,0);
server_sockaddr.sin_family = AF_INET; //ipv4
server_sockaddr.sin_port = htons(PORT); //端口 #define PORT 8087
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); //任意地址
server_len = sizeof(server_sockaddr);
int on;
setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR,&on,sizeof(on));
/*bind 将套接字与IP地址和端口绑定*/
if(bind(server_sockfd, (struct sockaddr*)&server_sockaddr, server_len)==-1){
printf("bind error");
exit(1);
}
//监听
if(listen(server_sockfd, 5)==-1){
printf("listen error");
exit(1);
}
client_len = sizeof(client_sockaddr);
pid_t ppid,pid;
while(1) {
printf("start\n");
//当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。
if((client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_sockaddr, &client_len)) == -1){
printf("connect error");
exit(1);
} else
{
printf("create connection successfully\n");
while(1){
//发送数据
int error = send(client_sockfd, "I am the server", strlen("I am the server"), 0);
if(error > 0 ){
printf("send : I am the server\n");
}
//接收数据
int numbytes = recv(client_sockfd, buf, MAXDATASIZE, 0);
if (numbytes > 0) {
buf[numbytes] = '\0';
printf("Received: %s\n",buf);
}
sleep(1);
}
}
printf("next\n");
}
return 0;
}
客户端c/c++代码
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#define SERVER_PORT 8087
#define MAXDATASIZE 100
#define SERVER_IP "127.0.0.1"//修改为服务端旭日X3派的IP地址
int main() {
int sockfd, numbytes;
char buf[MAXDATASIZE];
struct sockaddr_in server_addr;
printf("\n client initialization \n");
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
bzero(&(server_addr.sin_zero),sizeof(server_addr.sin_zero));
//连接
if (connect(sockfd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)) == -1){
perror("connect error");
exit(1);
}
while(1) {
bzero(buf,MAXDATASIZE);
//接收消息
if ((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1){
perror("recv");
exit(1);
} else if (numbytes > 0) { //若成功接收
int len, bytes_sent;
buf[numbytes] = '\0';
printf("Received:%s\n",buf);
char msg[100];
strcpy(msg,"i am a client");
len = strlen(msg);
//sent to the server
if(send(sockfd, msg,len,0) == -1){
perror("send error");
}else{
printf("Send :i am a client\n");
}
}
else {
printf("soket end!\n");
break;
}
sleep(1);
}
close(sockfd);
return 0;
}
最后别忘了编译CPP文件
g++ -O3 tcp_client.cpp -o client
g++ -O3 tcp_server.cpp -o server
然后分别在终端运行这两个文件
./server
./client
服务端效果
客户端效果
旭日X3派 TCP通信建立成功!
评论(0)
您还未登录,请登录后发表或查看评论