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 [email protected] $^
client:client.c
gcc -o [email protected] $^ -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 [email protected] $^ -lpthread
client:client.c
gcc -o [email protected] $^ -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壓力增大,性能下降
- 多線程伺服器穩定性較差,一個線程挂掉,全部挂掉
多線程伺服器優點:
- 可處理多個客戶
- 編寫周期短,代碼簡單