天天看點

網絡基礎 TCP多程序/多線程伺服器

TCP協定

TCP協定段格式

網絡基礎 TCP多程式/多線程伺服器
  • 源/目的端口号: 表示資料是從哪個程序來, 到哪個程序去;
  • 32位序号/32位确認号: 以後再了解
  • 4位TCP報頭⻓度: 表⽰該TCP頭部有多少個32位bit(有多少個4位元組); 是以TCP頭部最⼤⻓度是15 * 4 =60
  • 6位标志位:
  1. URG: 緊急指針是否有效
  2. ACK: 确認号是否有效
  3. PSH: 提示接收端應⽤程式立刻從TCP緩沖區把資料讀走
  4. RST: 對方要求重建立立連接配接; 我們把攜帶RST辨別的稱為複位報⽂段
  5. SYN: 請求建立連接配接; 我們把攜帶SYN辨別的稱為同步封包段
  6. 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
           

多程序伺服器運作截圖:

網絡基礎 TCP多程式/多線程伺服器

如圖可見有2個用戶端連接配接到伺服器。

網絡基礎 TCP多程式/多線程伺服器

用戶端可以輸入quit退出連接配接。

網絡基礎 TCP多程式/多線程伺服器

編寫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
           
網絡基礎 TCP多程式/多線程伺服器
網絡基礎 TCP多程式/多線程伺服器
網絡基礎 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>  			



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壓力增大,性能下降
  • 多線程伺服器穩定性較差,一個線程挂掉,全部挂掉

多線程伺服器優點:

  • 可處理多個客戶
  • 編寫周期短,代碼簡單