TCP的优点: 可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。
TCP的缺点: 慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。
UDP的优点: 快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Floodg攻击等。
UDP的缺点: 不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。
什么时候应该使用TCP: 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输
什么时候应该使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 比如,日常生活中,常见使用UDP协议的应用如下: QQ语音 QQ视频 TFTP 有些应用场景对可靠性要求不高会用到UPD,比如长视频,要求速率
tcp中有序传输的原理:
(作业要求:做作业的时候不要再翻看视频上的教程,对函数理解不明白的全部通过man手册去查看,
自己思考框架,使用makefile编译,然后将测试的记录和结果添加到readme.txt文件中提交上来,代码实现完成测试通过后
再提交作业,网络部分学习不写代码不测试看不出问题的,良好的习惯帮助你们快速成长。)
准备工作:
课程里讲述的是server代码通过轮询的方式实现了接收/回复数据,但是有个问题,
当处理过程复杂 需要一段时间才能处理完的的时候,则会造成资源浪费无法快速响应等问题。
为了解决这个问题,可以采用resv-process结构,即:接收采用轮询,处理则创建进程的方式来解决问题。
此次作业中会把两种解决方式对应的c/s代码均写出。
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/wait.h> #define SERV_PORT 5050 #define SERV_IP_ADDR "192.168.0.158" int main() { int fd = -1; int result = 0; struct sockaddr_in mysockaddr; struct sockaddr_in clientscokaddr; socklen_t clientaddrlen; char botton[64]; /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return -1; } /*优化4: 允许绑定地址快速重用 */ int b_reuse = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); /*2. 绑定 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号 mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY); /*2.2 绑定 */ result = bind(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); if (result != 0) { perror("bind"); return -1; } /*3. 调用recvfrom 接收客户端发来的信息 */ /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/ int fd_client; pid_t pid; clientaddrlen = sizeof(clientscokaddr); while (1) { bzero((void*)botton, 64); bzero((void*)&clientscokaddr, clientaddrlen); do { result = recvfrom(fd, botton, 63, 0, (struct sockaddr*)&clientscokaddr, &clientaddrlen); } while (result < 0 && EINTR == errno); if (-1 == result) { perror("recvfrom"); break; } char ipv4_addr[16]; if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) { perror("inet_ntop"); return -1; } printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port)); printf("message is:%s\n", botton); } close(fd); return 0; }
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> #define DEBUG 0 void usage(char* s) { printf("\nThis is udp demo!\n"); printf("\nUsage:\n\t %s serv_ip serv_port", s); printf("\n\t serv_ip: udp server ip address"); printf("\n\t serv_port: udp server port(serv_port > 5000)\n\n"); } int main(int argc, char* argv[]) { int fd = -1; int result = 0; int SERV_PORT = -1; char* SERV_IP_ADDR = NULL; struct sockaddr_in mysockaddr; char MESSAGE[] = "hELLo wOLd!!!\n"; /* 在启动程序时 要加上IP 端口,如:test.out 192.168.1.31 5001 从而提高程序通用性*/ /* 下面的程序是用来检测输入的参数是否合法 */ #if DEBUG SERV_PORT = 5050; SERV_IP_ADDR = "192.168.0.158"; #else if (argc != 3) { usage(argv[0]); return -1; } SERV_PORT = atoi(argv[2]); if (SERV_PORT <= 5000) { usage(argv[0]); return -1; } SERV_IP_ADDR = argv[1]; #endif /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return -1; } /*2.连接服务器 写入数据 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT); //网络字节序的端口号 #if 0 mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); #else result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr); if (result != 1) { perror("inet_pton"); return -1; } #endif /*2.2 读写数据*/ printf("Client staring...OK!\n"); while (1) { do { result = sendto(fd, MESSAGE, strlen(MESSAGE), 0, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); } while (result < 0 && EINTR == errno); //判断是否阻塞了 if (result < 0) { perror("connect"); break; } sleep(10); } close(fd); return 0; }
上一个server代码通过轮询的方式实现了接收/回复数据,但是有个问题,
当处理过程复杂 需要一段时间才能处理完的的时候,则会造成资源浪费无法快速响应等问题。
为了解决这个问题,可以采用resv-process结构,即:接收采用轮询,处理则创建进程的方式来解决问题。
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/wait.h> #define SERV_PORT 5050 #define SERV_IP_ADDR "192.168.0.158" int fun_process(int fd,char* botton,sockaddr_in clientscokaddr, socklen_t clientaddrlen); void handler(int signo) { //自动回收子进程 防止僵尸进程 if (signo == SIGCHLD) { printf("Recall SIGINT\n"); waitpid(-1, NULL, WNOHANG); } } int main() { int fd = -1; int result = 0; pid_t pid; struct sockaddr_in mysockaddr; struct sockaddr_in clientscokaddr; socklen_t clientaddrlen; char botton[64]; /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return -1; } /*优化4: 允许绑定地址快速重用 */ int b_reuse = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); /*2. 绑定 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号 mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY); /*2.2 绑定 */ result = bind(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); if (result != 0) { perror("bind"); return -1; } /*3. 调用recvfrom 接收客户端发来的信息 */ /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/ clientaddrlen = sizeof(clientscokaddr); signal(SIGCHLD, handler); while (1) { bzero((void*)botton, 64); bzero((void*)&clientscokaddr, clientaddrlen); do { result = recvfrom(fd, botton, 63, 0, (struct sockaddr*)&clientscokaddr, &clientaddrlen); } while (result < 0 && EINTR == errno); if (-1 == result) { perror("recvfrom"); break; } pid = fork(); if (pid < 0) { perror("fork"); break; } else if (0 == pid) { result = fun_process(fd, botton,clientscokaddr, clientaddrlen); close(fd); return result; } } close(fd); return 0; } int fun_process(int fd, char* botton,sockaddr_in clientscokaddr, socklen_t clientaddrlen) { char ipv4_addr[16]; int result; char botton_re[64] = "recv success!"; printf("23333\n"); if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) { perror("inet_ntop"); return -1; } printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port)); printf("message is:%s\n", botton); do { result = sendto(fd, botton_re, strlen(botton_re), 0, (struct sockaddr*)&clientscokaddr, sizeof(clientscokaddr)); } while (result < 0 && EINTR == errno); //判断是否阻塞了 if (result < 0) { perror("connect"); return -1; } return 0; }
与server改进版 相对应
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> #define DEBUG 0 void usage(char* s) { printf("\nThis is udp demo!\n"); printf("\nUsage:\n\t %s serv_ip serv_port", s); printf("\n\t serv_ip: udp server ip address"); printf("\n\t serv_port: udp server port(serv_port > 5000)\n\n"); } int main(int argc, char* argv[]) { int fd = -1; int result = 0; int SERV_PORT = -1; char* SERV_IP_ADDR = NULL; struct sockaddr_in mysockaddr; char MESSAGE[] = "hELLo wOLd!!!\n"; char botton[64] = {0}; #if DEBUG SERV_PORT = 5050; SERV_IP_ADDR = "192.168.0.158"; #else if (argc != 3) { usage(argv[0]); return -1; } SERV_PORT = atoi(argv[2]); if (SERV_PORT <= 5000) { usage(argv[0]); return -1; } SERV_IP_ADDR = argv[1]; #endif /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return -1; } /*2.连接服务器 写入数据 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT); //网络字节序的端口号 #if 0 mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); #else result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr); if (result != 1) { perror("inet_pton"); return -1; } #endif /*2.2 读写数据*/ printf("Client staring...OK!\n"); struct sockaddr_in clientscokaddr; socklen_t clientaddrlen; clientaddrlen = sizeof(clientscokaddr); while (1) { do { result = sendto(fd, MESSAGE, strlen(MESSAGE), 0, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); } while (result < 0 && EINTR == errno); //判断是否阻塞了 if (result < 0) { perror("connect"); break; } sleep(1); bzero((void*)botton, 64); bzero((void*)&clientscokaddr, clientaddrlen); do { result = recvfrom(fd, botton, 63, MSG_DONTWAIT, (struct sockaddr*)&clientscokaddr, &clientaddrlen); } while (result < 0 && EINTR == errno); printf("message is:%s\n", botton); } close(fd); return 0; }
gcc -o server.out *.cpp -Wall
gcc -o client.out *.cpp -Wall