1.TCP
TCP有专门的传递保证机制,收到数据时会自动发送确认消息,发送方收到确认消息后才会继续发送消息,否则继续等待。这样的好处是传输的数据是可靠的,此外它是有连接的传输,大多数网络传输都是用的TCP。
1.1 TCP流程图
1.2 TCP步骤分析
程序分为服务器端和客户机端,先从服务器端开始分析。
服务器端:
a. 创建socket
if (-1 == sock_fd){ fprintf(stderr,"socket error:%sa", strerror(errno)); exit(1);}
所需要头文件:
#include #include
函数格式:
int socket(int domain, int type, int protocol);
函数功能:
创建一个套接字;
domain:协议域(族),决定了套接字的地址类型,例如AF_INET决定了要用IPv4地址(32位)与端口号(16位)的组合。常见的协议族有:AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX)、AF_ROUTE等;
type:指定套接字类型,SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAW
protocol:指定socket所使用的传输协议编号,通常为0
返回值:
若成功,返回一个套接字描述符,否则返回-1;
Socket就是一种文件描述符,和普通的打开文件一样,需要检测其返回结果。
b. 设置socket
memset(&server_addr, 0, sizeof(struct sockaddr_in));//clearserver_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY:This machine all IPserver_addr.sin_port = htons(PORT_NUMBER);
设置何种协议族,设置本机IP和端口,也就有了唯一性。
c. 绑定socket
ret = bind(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)); if(-1 == ret) { fprintf(stderr,"bind error:%sa", strerror(errno)); close(sock_fd); exit(1);}
所需要头文件:
#include #include
函数格式:
int bind(int sockfd, struct sockaddr *addr, int addrlen);
函数功能:
把套接字绑定到本地计算机的某一个端口上;
sockfd:待绑定的套接字描述符
addr:一个struct sockaddr *指针,指定要绑定给sockfd的协议地址。内容结构由前面的协议族决定。
addrlen:地址的长度
返回值:
若成功,返回0,否则返回-1,错误信息存在errno中;
d. 开始监听
ret = listen(sock_fd, BACKLOG); if (-1 == ret) { fprintf(stderr,"listen error:%sa", strerror(errno)); close(sock_fd); exit(1); }
所需要头文件:
#include #include
函数格式:
int listen(int sockfd, int backlog);
函数功能:
使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的连接请求,最大连接数量为backlog≤128;
sockfd:待监听的套接字描述符
backlog:最大可监听和连接的客户端数量
返回值:
若成功,返回0,否则返回-1;
e. 阻塞,等待连接
addr_len = sizeof(struct sockaddr); new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &addr_len); if (-1 == new_fd) { fprintf(stderr,"accept error:%sa", strerror(errno)); close(sock_fd); exit(1); }
所需要头文件:
#include #include
函数格式:
int accept(int sockfd, struct sockaddr *addr, int *addrlen);1
函数功能:
接受连接请求,建立起与客户机之间的通信连接。服务器处于监听状态时,如果某时刻获得客户机的连接请求,此时并不是立即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时再处理客户机的连接请求;
当accept函数接受一个连接时,会返回一个新的socket标识符,以后的数据传输和读取就要通过这个新的socket编号来处理,原来参数中的socket也可以继续使用,继续监听其它客户机的连接请求;
accept连接成功时,参数addr所指的结构体会填入所连接机器的地址数据;
sockfd:待监听的套接字描述符
addr:指向struct sockaddr的指针,用于返回客户端的协议地址
addrlen:协议地址的长度
返回值:
若成功,返回一个由内核自动生成的一个全新描述字,代表与返回客户的TCP连接,否则返回-1,错误信息存在errno中;
f. 接收数据
recv_len = recv(new_fd, recv_buf, 999, 0); if (recv_len <= 0) { fprintf(stderr, "recv error:%sa", strerror(errno)); close(new_fd); exit(1); } else { recv_buf[recv_len] = '0'; printf("Get msg from client%d: %s", client_num, recv_buf); }
所需要头文件:
#include #include
函数格式:
int recv(int sockfd, void *buf, size_t len, int flags);
函数功能:
用新的套接字来接收远端主机传来的数据,并把数据存到由参数buf指向的内存空间;
sockfd:sockfd为前面accept的返回值,即new_fd,也就是新的套接字
buf:指明一个缓冲区
len:指明缓冲区的长度
flags:通常为0
返回值:
若成功,返回接收到的字节数,另一端已关闭则返回0,否则返回-1,错误信息存在errno中;
g. 关闭socket
exit(0);
为了应对多个连接,并保证它们之间相互独立,实际编程中往往还要加入多进程fork()。让子进程接收数据,父进程继续监听新的连接。
- 客户机端:
a. 创建socket
sock_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET:IPV4;SOCK_STREAM:TCP if (-1 == sock_fd) { fprintf(stderr,"socket error:%sa", strerror(errno)); exit(1); }
b. 设置socket
memset(&server_addr, 0, sizeof(struct sockaddr_in));//clearserver_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT_NUMBER);
其中注意的是,这里设置的socket内容是指 希望连接的服务器IP和端口号信息,IP地址来自用户的输入,并转换格式得到。因此,这里的设置和服务器的设置,要保持内容上的一致。
ret = inet_aton(argv[1], &server_addr.sin_addr); if(0 == ret) { fprintf(stderr,"server_ip error."); close(sock_fd); exit(1); }
c. 连接
ret = connect(sock_fd, (const struct sockaddr *)&server_addr, sizeof(struct sockaddr)); if (-1 == ret) { fprintf(stderr,"connect error:%sa", strerror(errno)); close(sock_fd); exit(1); }
所需要头文件:
#include `#include
函数格式:
int connect (int sockfd, struct sockaddr *serv_addr, int addrlen);
函数功能:
用来请求连接远程服务器,将参数sockfd的socket连至参数serv_addr所指定的服务器IP和端口号上去;
sockfd:客户端的socket套接字
serv_addr:一个struct sockaddr类型的结构体指针变量,存储着远程服务器的IP与端口号信息
addrlen:结构体变量的长度
返回值:
若成功,返回0,否则返回-1,错误信息存在errno中;
d. 发送
send_buf = send(sock_fd, send_buf, strlen(send_buf), 0);if (send_buf <= 0){fprintf(stderr,"send error:%sa", strerror(errno));close(sock_fd);exit(1);}
所需要头文件:
#include #include
函数格式:
int send(int sockfd, const void *buf, int len, int flags);
函数功能:用来发送数据给指定的远端主机;sockfd:客户端的socket套接字buf:指明一个缓冲区len:指明缓冲区的长度flags:通常为0
返回值:若成功,返回发送的字节数,否则返回-1,错误信息存在errno中
d. 关闭socket
close(sock_fd); exit(0);
1.3 TCP完整代码
/** tcp_server.c# Copyright (C) 2017 hceng, # Licensed under terms of GPLv2## This program is used for TCP / UDP learning.# https://hceng.cn/*/#include #include #include #include #include #include #include #include #include #include #include #define PORT_NUMBER 8888#define BACKLOG 10/* socket->bind->listen->accept->send/recv->close*/int main(int argc, char **argv){ int sock_fd, new_fd; struct sockaddr_in server_addr; struct sockaddr_in client_addr; int ret; int addr_len; int recv_len; unsigned char recv_buf[1000]; int client_num = -1; signal(SIGCHLD,SIG_IGN); /* socket */ sock_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET:IPV4;SOCK_STREAM:TCP if (-1 == sock_fd) { fprintf(stderr,"socket error:%sa", strerror(errno)); exit(1); } /* set server sockaddr_in */ memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY:This machine all IP server_addr.sin_port = htons(PORT_NUMBER); /* bind */ ret = bind(sock_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)); if(-1 == ret) { fprintf(stderr,"bind error:%sa", strerror(errno)); close(sock_fd); exit(1); } /* listen */ ret = listen(sock_fd, BACKLOG); if (-1 == ret) { fprintf(stderr,"listen error:%sa", strerror(errno)); close(sock_fd); exit(1); } /* accept */ while(1) { addr_len = sizeof(struct sockaddr); new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &addr_len); if (-1 == new_fd) { fprintf(stderr,"accept error:%sa", strerror(errno)); close(sock_fd); exit(1); } client_num++; fprintf(stderr, "Server get connetion form client%d: %s", client_num, inet_ntoa(client_addr.sin_addr)); if (!fork()){ /* Child process */ while (1) { /* recv */ recv_len = recv(new_fd, recv_buf, 999, 0); if (recv_len <= 0) { fprintf(stderr, "recv error:%sa", strerror(errno)); close(new_fd); exit(1); } else { recv_buf[recv_len] = '0'; printf("Get msg from client%d: %s", client_num, recv_buf); } } close(new_fd); } } /* close */ close(sock_fd); exit(0); }
/** tcp_client.c# Copyright (C) 2017 hceng, # Licensed under terms of GPLv2## This program is used for TCP / UDP learning.# https://hceng.cn/*/#include #include #include #include #include #include #include #include #include #include #define PORT_NUMBER 8888/* socket->connect->send->close*/int main(int argc, char *argv[]){ int sock_fd; struct sockaddr_in server_addr; int ret; unsigned char send_buf[1000]; int send_len; if(argc != 2) { fprintf(stderr, "Usage:%s hostnamea", argv[0]); exit(1); } /* socket */ sock_fd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET:IPV4;SOCK_STREAM:TCP if (-1 == sock_fd) { fprintf(stderr,"socket error:%sa", strerror(errno)); exit(1); } /* set sockaddr_in parameter*/ memset(&server_addr, 0, sizeof(struct sockaddr_in));//clear server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT_NUMBER); ret = inet_aton(argv[1], &server_addr.sin_addr); if(0 == ret) { fprintf(stderr,"server_ip error."); close(sock_fd); exit(1); } /* connect */ ret = connect(sock_fd, (const struct sockaddr *)&server_addr, sizeof(struct sockaddr)); if (-1 == ret) { fprintf(stderr,"connect error:%sa", strerror(errno)); close(sock_fd); exit(1); } while (1) { if (fgets(send_buf, 999, stdin)) { /* send */ send_len = send(sock_fd, send_buf, strlen(send_buf), 0); if (send_len <= 0) { fprintf(stderr,"send error:%sa", strerror(errno)); close(sock_fd); exit(1); } } } /* close */ close(sock_fd); exit(0);}
1.4 测试结果
先在Ubuntu主机上交叉编译服务器端代码,再在Ubuntu主机上编译客户端代码。在开发板上运行服务器端代码,在Ubuntu主机先启动tmux分屏,再分别运行客户端代码。
服务器端