TCP协议
TCP协议段格式
- 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去;
- 32位序号/32位确认号: 以后再了解
- 4位TCP报头⻓度: 表⽰该TCP头部有多少个32位bit(有多少个4字节); 所以TCP头部最⼤⻓度是15 * 4 =60
- 6位标志位:
- URG: 紧急指针是否有效
- ACK: 确认号是否有效
- PSH: 提示接收端应⽤程序立刻从TCP缓冲区把数据读走
- RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报⽂段
- SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
- FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报⽂段
- 16位窗口大小: 描述目前接受能力,填写的是自己的接受缓冲区大小
- 16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP首部, 也包含TCP数据部分.
- 16位紧急指针: 标识哪部分数据是紧急数据;
- 40字节头部选项: 暂时忽略;
编写TCP多进程服务器:
服务器代码:
#include<stdio.h> #include<unistd.h> //unistd.h 是 C 和 C++ 程序设计语言中提供对 POSIX 操作系统 API 的访问功能的头文件的名称。 #include<stdlib.h> //tdlib.h里面定义了五种类型、一些宏和通用工具函数。 类型例如size_t,宏例如EXIT_FAILURE,常用的函数如malloc()、atoi() #include<string.h> #include<sys/socket.h> #include<netinet/in.h> //socketaddr_in 结构体 htons系统调用 #include<arpa/inet.h> //htonl, htons之类 int_addr #include<sys/wait.h> //使用wait()和waitpid()函数时需要 void Request(int client_fd,struct sockaddr_in* client_addr) { char buf[1024] = {0}; while(1) { ssize_t size = read(client_fd,buf,sizeof(buf)); if(size < 0) { perror("read"); continue; } if(strncasecmp(buf,"quit",4) == 0 || size == 0) { printf("=============client [%s] quit!===========\n",inet_ntoa(client_addr->sin_addr)); break; } // if(size == 0) // { // printf("client:%s is exited!\n",inet_ntoa(client_addr->sin_addr)); // close(client_fd); // break; // } buf[size] = '\0'; printf("client %s say:%s\n",inet_ntoa(client_addr->sin_addr),buf); write(client_fd,buf,strlen(buf)); } return; } void CreateWorker(int client_fd,struct sockaddr_in* client_addr) { pid_t pid = fork(); if(pid < 0) { perror("fork"); return; } else if(pid == 0) { //child if(fork() == 0) { //grand_child //此处创建孙子进程使子进程退出,是因为孙子进程有init回收 Request(client_fd,client_addr); } exit(0); } else { //father close(client_fd); waitpid(pid,NULL,0); } } int main(int argc,char* argv[]) { if(argc != 3) { // ./service 192.168.1.113 88888 printf("Usage:%s [IP] [port]\n",argv[0]); return 1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(argv[1]); addr.sin_port = htons(atoi(argv[2])); int fd = socket(AF_INET,SOCK_STREAM,0); if(fd < 0) { perror("socket"); return 2; } if(bind(fd,(struct sockaddr*)&addr,sizeof(addr)) < 0) { perror("bind"); return 3; } if(listen(fd,5) < 0) { perror("listen"); return 4; } while(1) { struct sockaddr_in client_addr; socklen_t len = sizeof(client_addr); int client_fd = accept(fd,(struct sockaddr*)&client_addr,&len); if(client_fd < 0) { perror("accept"); continue; } printf("########## %s connected!##########\n",inet_ntoa(client_addr.sin_addr)); CreateWorker(client_fd,&client_addr); } return 0; }
Makefile:
.PHONY:all all:service client service:service.c gcc -o $@ $^ client:client.c gcc -o $@ $^ -static .PHONY:clean clean: rm -f service client
多进程服务器运行截图:
如图可见有2个客户端连接到服务器。
客户端可以输入quit退出连接。
编写TCP多线程服务器:
服务器代码:
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<string.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/wait.h> void Request(int client_fd,struct sockaddr_in* client_addr) { char buf[1024] = {0}; while(1) { ssize_t size = read(client_fd,buf,sizeof(buf)); if(size < 0) { perror("read"); continue; } if(strncasecmp(buf,"quit",4) == 0 || size == 0) { printf("=============client [%s] quit!===========\n",inet_ntoa(client_addr->sin_addr)); break; } buf[size] = '\0'; printf("client %s say:%s\n",inet_ntoa(client_addr->sin_addr),buf); write(client_fd,buf,strlen(buf)); } return; } typedef struct Arg { int fd; struct sockaddr_in addr; }Arg; void* CreateWorker(void* ptr) { Arg* arg = (Arg*)ptr; Request(arg->fd,&arg->addr); free(arg); } int main(int argc,char* argv[]) { if(argc != 3) { // ./server 192.168.1.113 88888 printf("Usage:%s [IP] [port]\n",argv[0]); return 1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(argv[1]); addr.sin_port = htons(atoi(argv[2])); int fd = socket(AF_INET,SOCK_STREAM,0); if(fd < 0) { perror("socket"); return 2; } if(bind(fd,(struct sockaddr*)&addr,sizeof(addr)) < 0) { perror("bind"); return 3; } if(listen(fd,5) < 0) { perror("listen"); return 4; } while(1) { struct sockaddr_in client_addr; socklen_t len = sizeof(client_addr); int client_fd = accept(fd,(struct sockaddr*)&client_addr,&len); if(client_fd < 0) { perror("accept"); continue; } printf("########## %s connected!##########\n",inet_ntoa(client_addr.sin_addr)); pthread_t tid = 0; Arg* arg = (Arg*)malloc(sizeof(Arg)); arg->fd = client_fd; arg->addr = client_addr; pthread_create(&tid,NULL,CreateWorker,(void*)arg); pthread_detach(tid); //分离线程 } return 0; }
Makefile:多线程的makefile切记要加-lpthread选项。
.PHONY:all all:server client server:server.c gcc -o $@ $^ -lpthread client:client.c gcc -o $@ $^ -static .PHONY:clean clean: rm -f server client
多进程与多线程客户端代码相同,如下:
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<string.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/wait.h> int main(int argc,char* argv[]) { if(argc != 3) { printf("Usage: %s [IP] [port]\n",argv[0]); return 1; } char buf[1024]; memset(buf,'\0',sizeof(buf)); struct sockaddr_in service_addr; int sock = socket(AF_INET,SOCK_STREAM,0); bzero(&service_addr,sizeof(service_addr)); service_addr.sin_family = AF_INET; inet_pton(AF_INET,argv[1],&service_addr.sin_addr); service_addr.sin_port = htons(atoi(argv[2])); int ret = connect(sock,(struct sockaddr*)&service_addr,sizeof(service_addr)); if(ret < 0) { printf("connect failed...\n"); return 1; } printf("connect success...\n"); while(1) { printf("client#:"); fgets(buf,sizeof(buf),stdin); buf[strlen(buf) - 1] = 0; write(sock,buf,sizeof(buf)); //用来比较s1与s2前n个字符大小,若字符串相同则返回0 if(strncasecmp(buf,"quit",4) == 0) { printf("quit!\n"); break; } printf("please wait...\n"); read(sock,buf,sizeof(buf)); printf("service#:%s\n",buf); } close(sock); return 0; }
总结两种服务器优缺点:
多进程服务器缺点:
- 客户连接后才创建进程
- 多进程服务器吃资源,只能服务有限个客户
- 多进程服务器跟随进程增多cpu压力增大,性能下降
多进程服务器优点:
- 可处理多个客户
- 编写周期短,代码简单
- 稳定性强,一个进程挂掉不会影响其他进程
多线程服务器缺点:
- 客户连接后才创建进程
- 多进程服务器吃资源,只能服务有限个客户
- 多进程服务器跟随进程增多cpu压力增大,性能下降
- 多线程服务器稳定性较差,一个线程挂掉,全部挂掉
多线程服务器优点:
- 可处理多个客户
- 编写周期短,代码简单