IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> TCP编程详解 -> 正文阅读

[网络协议]TCP编程详解

1.1 TCP介绍、编程流程

TCP回顾:
? 1.面向连接的流式协议,可靠、出错重传、且每收到一个数据都要给出相应的确认;
? 2.通信之前需要建立链接;
? 3.服务器是被动链接,客户端是主动链接

TCP与UDP的差异:
?

TCP C/S架构

TCP编程流程

服务器:
? 创建套接字socket()
? 将套接字与服务器网络信息结构体绑定bind()
? 将套接字设置为监听状态listen()
? 阻塞等待客户端的连接请求accept()(阻塞函数)
? 进行通信recv()/send()
? 关闭套接字close()

客户端:
? 创建套接字socket()
? 发送客户端连接请求connect()
? 进行通信send()/recv()
? 关闭套接字close()

1.2 TCP编程—socket
#include <sys/types>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
功能:创建一个套接字,返回一个文件描述符
参数:
	domain:	通信域,协议族
			AF_UNIX		本地通信
			AF_INET		ipv4网络协议
			AF_INET6	ipv6网络协议
			AF_PACKET	底层接口
	type:	套接字的类型
			SOCK_STREAM		流式套接字(tcp)
			SOCK_DGRAM		数据报套接字(udp)
			SOCK_RAW		原始套接字
	protocol:	附加协议,如果不需要,则设置为0
返回值:
	成功:	文件描述符
	失败:	-1

案例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>

int main(int argc, char **argv){
	//通过socket函数创建一个TCP套接字
	int sockfd;
	if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
		perror("fail to socket");
		exit(1);
	}
	return 0;
}
1.3 connect、send、recv
1.3.1 connect函数
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr* addr, socklen_t len);
功能:	给服务器发送客户端的连接请求
参数:	
	 sockfd:	文件描述符,socket函数的返回值
	 addr:		要连接的服务器的网络信息结构以(需要自己设置)
	 addrlen:	addr的长度
返回值:
	 成功:0
	 失败:-1

注意:
? 1.connect建立连接之后不会产生新的套接字
? 2.连接成功后才可以开始传输TCP数据
? 3.头文件:#include<sys/socket.h>

1.3.2 send函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd,const void *buf,size_t len,int flags);
功能:		发送数据
参数:	
	sockfd:	文件描述符
			客户端:socket函数的返回值
			服务器:accept函数的返回值
	buf:	发送的数据
	len:	buf的长度
	flags:	标志位
			0	阻塞
			MSG_DONTWAIT	非阻塞
返回值:
	成功:发送的字节数
	失败:-1

注意:
? 不能用TCP协议发送0长度的数据包

1.3.3 recv函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd,void *buf,size_t len,int flags);
功能:		接收数据
参数:	
	sockfd:	文件描述符
			客户端:socket函数的返回值
			服务器:accept函数的返回值
	buf:	发送的数据
	len:	buf的长度
	flags:	标志位
			0	阻塞
			MSG_DONTWAIT	非阻塞
返回值:
	成功:接收的字节数
	失败:-1
	如果发送端关闭文件描述符或者关闭进程,则recv函数会返回0
1.3.4 客户端代码

使用windows下的网络调试助手作为服务器

客户端的代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet.in.h>
#include <string.h>

int main(int argc, char **argv){

	if(argc < 3){
		fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
	}
	//第一步:通过socket函数创建一个TCP套接字
	int sockfd;
	if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
		perror("fail to socket");
		exit(1);
	}
	
	//第二步:发送客户端连接请求
	struct sockaddr_in serveraddr;
	socklen_t addrlen = sizeof(serveraddr);
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(agrv[2]));
	
	if(connect(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
	{
		perror("fail to connect");
		exit(1);
	}
	
	//第三步:进行通信
	//发送数据
	char buf[128] = "";
	fgets(buf,128,stdin);
	buf[strlen(buf) - 1] = '\0';
	if(send(sockfd,buf,128,0) == -1){
		perror("fail to send");
		exit(1);
	}
	//接收数据
	char text[128] = "";
    if(recv(sockfd,text,128,0) == -1){
    	perror("fail to recv");
    	exit(1);
    }
    
    printf("from server: %s\n",text);
    
    //第四步:关闭套接字文件描述符
    close(sockfd);
	
	return 0;
}
1.4 TCP服务器—bind、listen、accept
1.4.1 作为TCP服务器需要具备的条件

1.具备一个可以确知的地址;
2.让操作系统知道是一个服务器,而不是客户端;
3.等待连接的到来

对于面向连接的TCP协议来说,连接的建立才真正意味着数据通信的开始。

1.4.2 bind函数
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
功能:将套接字与网络信息结构体绑定
参数:
	sockfd:文件描述符,socket的返回值
	addr:	网络信息结构体  //#include <netinet/in.h>
	addrlen:addrlen的长度
返回值:
	成功: 0
	失败: -1
1.4.3 listen函数
#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd,int backlog);
功能:
	将套接字由主动修改为被动
	使操作系统为该套接字设置一个连接队列,用来记录所有连接到该套接字的连接
参数:
	sockfd:	socket监听套接字
	backlog:连接队列的长度
返回值:
	成功:返回0
	失败:其他
1.4.4 accept函数
#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);
功能:
	从已连接队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待(阻塞)
参数:
	sockfd:	socket监听套接字
	cliaddr:用于存放客户端套接字地址结构
	addrlen:套接字地址结构体长度的地址
返回值:
	成功:新的文件描述符(只要由客户端连接,就会产生新的文件描述符,这个新的文件描述符专门与指定的客户端进行通信的)
	失败:-1
	
注意:返回的是一个已连接套接字,这个套接字代表当前这个连接
1.4.5 TCP服务器例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc, char **argv)
{
	if(argc < 3){
		fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
	}
	//第一步:通过socket函数创建一个TCP套接字
	int sockfd;
	if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
		perror("fail to socket");
		exit(1);
	}
	
	//第二步:将套接字与服务器网络信息结构体绑定
	struct sockaddr_in serveraddr;
	socklen_t addrlen = sizeof(serveraddr);
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));
	if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
	{
		perror("fail to bind");
		exit(1);
	}
	
	//第三步:将套接字设置为被动监听状态
	if(listern(sockfd,10) == -1){
		perror("fail to listen");
		exit(1);
	}
	
	//第四步:阻塞等待客户端的连接请求
	int acceptfd;
	struct sockaddr_in clientaddr;
	if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){
		perror("fail to accept");
		exit(1);
	}
	//打印连接的客户端信息
	printf("ip:%s ,port:%d\n",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port));
	
	//第五步:进行通信
	char buf[128] = "";
	if(recv(acceptfd,buf,128,0) == -1){
		perror("fail to recv");
		exit(1);
	}
	
	printf("from client: %s\n",buf);
	
	strcat(buf,"*_*");
	if(send(acceptfd,buf,128,0) == -1){
		perror("fail to send");
		exit(1);
	}
	
	return 0;
}
1.5 TCP编程—close、三次握手、四次挥手
1.5.1 close关闭套接字

1.使用close函数即可关闭套接字(关闭一个代表已连接套接字将导致另一端接收到一个0长度的数据包)

2.做服务器时
? 1>关闭监听套接字将导致服务器无法接收到新的连接,但不会影响已经建立的连接
? 2>关闭accept返回的已连接套接字将导致他所代表的连接被关闭,但不会影响服务器的监听

3.做客户端时

? 关闭连接就是关闭连接,不意味着其他

1.5.2 三次握手
1.5.3 四次挥手
1.6 TCP并发服务器

TCP原本不是并发服务器,TCP服务器同一时间只能与一个客户端通信。

原始代码:

client.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet.in.h>
#include <string.h>

int main(int argc, char **argv){

	if(argc < 3){
		fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
	}
	//第一步:通过socket函数创建一个TCP套接字
	int sockfd;
	if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
		perror("fail to socket");
		exit(1);
	}
	
	//第二步:发送客户端连接请求
	struct sockaddr_in serveraddr;
	socklen_t addrlen = sizeof(serveraddr);
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(agrv[2]));
	
	if(connect(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
	{
		perror("fail to connect");
		exit(1);
	}
	
	//第三步:进行通信
	//发送数据
	char buf[128] = "";
	while(1){
		fgets(buf,128,stdin);
		buf[strlen(buf) - 1] = '\0';
		if(send(sockfd,buf,128,0) == -1){
			perror("fail to send");
			exit(1);
		}
		if(strncmp(buf,"quit",4) == 0){
			exit(0);
		}
		
		//接收数据
		char text[128] = "";
    	if(recv(sockfd,text,128,0) == -1){
    		perror("fail to recv");
    		exit(1);
    	}
    
  	  printf("from server: %s\n",text);
    }
   	//第四步:关闭套接字文件描述符
   	close(sockfd);
	
	return 0;
}

server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc, char **argv)
{
	if(argc < 3){
		fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
	}
	//第一步:通过socket函数创建一个TCP套接字
	int sockfd;
	if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
		perror("fail to socket");
		exit(1);
	}
	
	//将套接字设置为允许重复使用本机地址或者设置为端口复用
	int on = 1;
	if(setsockopt(sockfd,SOL_SOCKET,SO)REUSEADDR,&on,sizeof(on) < 0){
		perror("fail to setsockopt");
		exit(1);
	}
	
	//第二步:将套接字与服务器网络信息结构体绑定
	struct sockaddr_in serveraddr;
	socklen_t addrlen = sizeof(serveraddr);
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));
	if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
	{
		perror("fail to bind");
		exit(1);
	}
	
	//第三步:将套接字设置为被动监听状态
	if(listern(sockfd,10) == -1){
		perror("fail to listen");
		exit(1);
	}
	
	//第四步:阻塞等待客户端的连接请求
	int acceptfd;
	struct sockaddr_in clientaddr;
	if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){
		perror("fail to accept");
		exit(1);
	}
	//打印连接的客户端信息
	printf("ip:%s ,port:%d\n",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port));
	
	//第五步:进行通信
	char buf[128] = "";
	ssize_t bytes;
	while(1){
		if(bytes = recv(acceptfd,buf,128,0) < 0){
			perror("fail to recv");
			exit(1);
		}
		else if(bytes == 0){
			printf("the clinet quited\n");
			exiy(1);
		}
		
		if(strncmp(buf,"quit",4) == 0){
			exit(0);
		}
	
		printf("from client: %s\n",buf);
	
		strcat(buf,"*_*");
		if(send(acceptfd,buf,128,0) == -1){
			perror("fail to send");
			exit(1);
		}
	}
	return 0;
}

TCP不能实现并发的原因:
由于TCP服务器端有两个读阻塞函数,accept和recv,两个函数需要先后运行,所以导致运行一个函数的时候另一个函数无法执行,所以无法保证一边连接客户端,一边与其他客户端通信。

如何实现TCP并发服务器:
1.使用多进程实现TCP并发服务器
2.使用多线程实现TCP并发服务器

1.6.1 多进程实现并发
int sockfd = socket();
bind()
listen()
while(1)
{
	accept()
	pid = fork();
	if(pid > 0)
	{
	
	}
	else if(pid == 0)
	{
		while(1)
		{
			recv()/send()
		}
	}
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>

//使用多进程实现TCP并发服务器

#define N 128
#define ERR_LOG(errmsg)do{\										perror(errmsg);\
						exit(1);\
						}while(0)
						
void handler(int sig){
	wait(NULL);
}

int main(int argc, char **argv)
{
	if(argc < 3){
		fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
	}
	//第一步:通过socket函数创建一个TCP套接字
	int sockfd;
	if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
		ERR_LOG("fail to socket");
	}
	
	//将套接字设置为允许重复使用本机地址或者设置为端口复用
	int on = 1;
	if(setsockopt(sockfd,SOL_SOCKET,SO)REUSEADDR,&on,sizeof(on) < 0){
		ERR_LOG("fail to setsockopt");
	}
	
	//第二步:将套接字与服务器网络信息结构体绑定
	struct sockaddr_in serveraddr;
	socklen_t addrlen = sizeof(serveraddr);
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));
	if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
	{
		ERR_LOG("fail to bind");
	}
	
	//第三步:将套接字设置为被动监听状态
	if(listern(sockfd,10) == -1){
		perror("fail to listen");
		exit(1);
	}
	
	//使用信号,异步的方式处理僵尸进程
	signal(SIGCHID,handler)
	
	while(1){
		//第四步:阻塞等待客户端的连接请求
		int acceptfd;
		struct sockaddr_in clientaddr;
		if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){
			perror("fail to accept");
			exit(1);
		}
		//打印连接的客户端信息
		printf("ip:%s ,port:%d\n",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port));
	
		//第五步:使用fork函数创建子进程,父进程继续负责连接,子进程负责与客户端通信
		pid_t pid;
		if((pid = fork()) < 0){
			ERR_LOG("fail to fork");
		}
		else if(pid > 0){
			//父进程负责执行accept,所以if语句结束后继续在accept函数的位置阻塞
		}
		else{
			//子进程负责和指定的客户端通信
			char buf[128] = "";
			ssize_t bytes;
			while(1){
				if(bytes = recv(acceptfd,buf,128,0) < 0){
					perror("fail to recv");
					exit(1);
				}
				else if(bytes == 0){
					printf("the clinet quited\n");
					exit(1);
				}
		
				if(strncmp(buf,"quit",4) == 0){
					exit(0);
				}
	
				printf("from client: %s\n",buf);
	
				strcat(buf,"*_*");
				if(send(acceptfd,buf,128,0) == -1){
					perror("fail to send");
					exit(1);
				}
			}
		}
	}
	return 0;
}
1.6.2 多线程并发实现
void *thread_fun(void *arg){
	while(1){
		recv()/send()
	}
}

sockfd = socket()
bind()
listen()
while(1)
{
	accept()
	//只要有客户端连接上,则创建一个子线程与之通信
	pthread_create(&thread,NULL,thread_fun,...)
	pthread_detach();	
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>

//使用多进程实现TCP并发服务器

#define N 128
#define ERR_LOG(errmsg)do{\										perror(errmsg);\
						exit(1);\
						}while(0)
						
typedef struct{
	struct sockaddr_in addr;
	int acceptfd;
}MSG;

void *pthread_fun(void *arg){
	char buf[N] = "";
	ssize_t bytes;
	MSG msg = *(MSG *)arg;
	while(1){
		if(bytes = recv(msg.acceptfd,buf,128,0) < 0){
			perror("fail to recv");
			exit(1);
		}
		else if(bytes == 0){
			printf("the clinet quited\n");
			exit(1);
		}
		
		if(strncmp(buf,"quit",4) == 0){
			printf("The client quited\n");
			pthread_exit(NULL);
		}
		printf("[%s - %d]: %s\n",inet_ntoa(msg.addr.sin_addr),ntohs(msg.addr.sin_port),buf);
		strcat(buf,"*_*");
		if(send(msg.acceptfd,buf,128,0) == -1){
			perror("fail to send");
			exit(1);
		}
	}
}

int main(int argc, char **argv)
{
	if(argc < 3){
		fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
	}
	//第一步:通过socket函数创建一个TCP套接字
	int sockfd;
	if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
		ERR_LOG("fail to socket");
	}
	
	//将套接字设置为允许重复使用本机地址或者设置为端口复用
	int on = 1;
	if(setsockopt(sockfd,SOL_SOCKET,SO)REUSEADDR,&on,sizeof(on) < 0){
		ERR_LOG("fail to setsockopt");
	}
	
	//第二步:将套接字与服务器网络信息结构体绑定
	struct sockaddr_in serveraddr;
	socklen_t addrlen = sizeof(serveraddr);
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));
	if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
	{
		ERR_LOG("fail to bind");
	}
	
	//第三步:将套接字设置为被动监听状态
	if(listern(sockfd,10) == -1){
		perror("fail to listen");
		exit(1);
	}
	
	//使用信号,异步的方式处理僵尸进程
	signal(SIGCHID,handler)
	
	while(1){
		//第四步:阻塞等待客户端的连接请求
		int acceptfd;
		struct sockaddr_in clientaddr;
		if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){
			perror("fail to accept");
			exit(1);
		}
		//打印连接的客户端信息
		printf("ip:%s ,port:%d\n",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port));
	
		//创建子线程与客户端进行通信
		MSG msg;
		msg.addr = clientaddr;
		msg.acceptfd = acceptfd;
		pthread_t thread;
		if(pthread_create(&thread,NULL,pthread_fun,&msg) != 0){
			ERR_LOG("fail to pthread_create");
		}
		pthread_detach(thread);	
		}
	}
	return 0;
}
1.7 Web服务器介绍
1.7.1 web服务器简介

Web服务器又称WWW服务器、网站服务器等

特点:
使用HTTP协议与客户机浏览器进行信息交流
不仅能存储信息,还能在用户通过web浏览器提供的信息的基础上运行脚本和程序
该服务器可安装在UNIX、Linux或Windows等操作系统上
著名的服务器有Apache、Tomcat、IIS等

1.7.2 HTTP协议

Webserver—HTTP协议(超文本协议)

概念:一种详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议

特点:
1.支持C/S架构
2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径,常用方法:GET、POST
3.无连接:限制每次连接只处理一个请求

1.7.3 Webserver通信过程
1.7.4 Web编程开发

网页浏览(使用过GET方式)

客户端浏览请求

Web服务器的ip地址是192.168.3.103,端口号是9999,要访问的网页时about.html

服务器收到的数据:

GET/index.html HTTP/1.1
Accept:image/gif.image/jpeg,*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
Accept-Encoding:gzip,deflate

服务器应答的格式:

服务器接收到浏览器发送的数据之后,需要判断GET/后面跟的网页是否存在,如果存在则请求成功,发送指定的指令,并发送文件内容给浏览器,如果不存在,则发送请求失败的指令

请求成功

"HTTP/1.1 200 OK\r\n"				\
"Content-Type: text/html\r\n"	\
"\r\n";

请求失败

"HTTP/1.1 404 Not Found\r\n"		\
"Content-Type: text/html\r\n"		\
"\r\n"							  \
"<HTML><BODY>File not found</BODY></HTML>"

案例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>

//使用多进程实现TCP并发服务器

#define N 128
#define ERR_LOG(errmsg)do{\			perror(errmsg);\
						exit(1);\
						}while(0)

void *pthread_fun(void *arg){
	int acceptfd = *(int *)arg;
	char buf[N] = "";
	char head[] = "HTTP/1.1 200 OK\r\n"				\
				"Content-Type: text/html\r\n"		\
				"\r\n";
	char err[] = "HTTP/1.1 404 Not Found\r\n"		\
				"Content-Type: text/html\r\n"	\
				"\r\n"						\
				"<HTML><BODY>File not found</BODY></HTML>";
	//接收浏览器通过http协议发送的数据包
	if(recv(acceptfd,buf,N,0) < 0){
		ERR_LOG("fail to recv");
	}
	
	printf("******************\n\n");
	printf("%s\n",buf);
	
	//通过获取的数据包中得到浏览器要访问的网络文件名
	// GET /about.html http/1.1
	char filename[128] = "";
	sscanf(buf,"GET /%s",filename);   //sscanf函数遇空格结束,所以直接可以获取文件名
	if(strncmp(filename,"HTTP/1.1",strlen("http/1.1")) == 0){
		strcpy(filename,"about.html");
	}
	printf("filename = %s\n",filename);
	
	char path[128] = "./sqlite/";
	strcat(path,filename);
	
	//通过解析出来的网页文件名,查找本地中有没有这个文件
	int fd;
	if((fd = open(path,O_RDONLY)) < 0){
		//如果文件不存在,则发送不存在对应的指令
		if(errno == ENOENT)
		{
			if(send(acceptfd,err,strlen(err),0) < 0){
				ERR_LOG("fail to send");
			}
			close(acceptfd);
			pthread_exit(NULL);
		}
		else{
			ERR_LOG("fail to open");
		}
	}
	
	//如果文件存在,先发送指令告知浏览器
	if(send(acceptfd,head,strlen(head),0) < 0){
		ERR_LOG("fail to send");
	}
	//读取网页文件中的内容并发送给浏览器
	ssize_t bytes;
	char text[1024] = "";
	while((bytes = read(fd,text,1024)) > 0){
		if(send(acceptfd,text,bytes,0) < 0){
			ERR_LOG("fail to send");
		}
	}
	pthread_exit(NULL);
}

int main(int argc, char **argv)
{
	if(argc < 3){
		fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
	}
	//第一步:通过socket函数创建一个TCP套接字
	int sockfd;
	if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
		ERR_LOG("fail to socket");
	}
	
	//将套接字设置为允许重复使用本机地址或者设置为端口复用
	int on = 1;
	if(setsockopt(sockfd,SOL_SOCKET,SO)REUSEADDR,&on,sizeof(on) < 0){
		ERR_LOG("fail to setsockopt");
	}
	
	//第二步:将套接字与服务器网络信息结构体绑定
	struct sockaddr_in serveraddr;
	socklen_t addrlen = sizeof(serveraddr);
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));
	if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
	{
		ERR_LOG("fail to bind");
	}
	
	//第三步:将套接字设置为被动监听状态
	if(listern(sockfd,10) == -1){
		perror("fail to listen");
		exit(1);
	}
	
	//使用信号,异步的方式处理僵尸进程
	signal(SIGCHID,handler)
	
	while(1){
		//第四步:阻塞等待客户端的连接请求
		int acceptfd;
		struct sockaddr_in clientaddr;
		if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){
			perror("fail to accept");
			exit(1);
		}
		//打印连接的客户端信息
		printf("ip:%s ,port:%d\n",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port));
	
		//创建子线程与客户端进行通信
		MSG msg;
		msg.addr = clientaddr;
		msg.acceptfd = acceptfd;
		pthread_t thread;
		if(pthread_create(&thread,NULL,pthread_fun,&msg) != 0){
			ERR_LOG("fail to pthread_create");
		}
		pthread_detach(thread);	
		}
	}
	return 0;
}
  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-10-22 21:53:48  更:2022-10-22 21:55:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 16:20:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码