c语言实现http协议服务器端和客户端

本代码参考https://blog.csdn.net/ymxyld/article/details/124812731思路,并添加了客户端代码。
代码各个函数注释写的非常详细,仅供学习参考,有任何疑问请留言。

服务器端

代码流程:
请添加图片描述

#define SERVER_PORT 8080 //设置端口号
#include<stdio.h>
#include<string.h>
#include<iostream>
#include <winsock2.h>
#include <ws2tcpip.h> 
#include<string.h>
#ifdef _WIN32
#include <Windows.h>
// Windows 文件操作相关代码
#else
#include <sys/stat.h>
// Unix/Linux 文件操作相关代码
#endif
#pragma comment(lib, "Ws2_32.lib")
struct client_mes {//客户端请求信息结构体
	char IP[20];	//客户ip地址
	int PORT;	//客户端口号
	char method[10];//请求方法
	char url[1024];	 //请求url
	char version[10];//协议及版本信息
}c_mes;
struct kay_and_value {//每一个键值对结构体
	char key[10];
	char value[100];
};
struct url_mes {
	char path[100];//请求路径
	//采用结构体数组来存储键值对
	struct kay_and_value k_v[10];
	int k_v_len;//实际键值对个数
}u_mes;
//定义http响应行全局变量
char u200[] = "HTTP/1.0 200 OK\r\n";
char u400[] = "HTTP/1.0 400 BAD REQUEST\r\n";
char u404[] = "HTTP/1.0 404 NOT FOUND\r\n";
char u500[] = "HTTP/1.0 500 INTERNAL SERVER ERROR\r\n";
char u501[] = "HTTP/1.0 501 METHOD NOT IMPLEMENTED\r\n";
int main() {
	void do_http_request(char buf[1024]);//对缓冲区接受到的客户请求信息进行解析
	
	int do_http_resolve(char url[1024], int clnt_sock);//对客户端请求进行响应
	void do_http_url_process(char url[1024]);//对客户端的url进行解析
	// 初始化键值对结构体数组
	for (int k = 0; k < 10; k++) {
		strcpy_s(u_mes.k_v[k].key, "");
		strcpy_s(u_mes.k_v[k].value, "");
	}
	memset(&c_mes, 0, sizeof(c_mes));//将结构体里面的数据清零
	memset(&u_mes, 0, sizeof(url_mes));

	// 初始化 Winsock 库
	WSADATA wsaData;
	int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (result != 0) {
		fprintf(stderr, "WSAStartup failed with error code: %d\n", result);
		return 1;
	}
	/*创建TCP套接字(通过IPv4族进行面向连接的通信)
	 返回值是新创建套接字的文件描述符,调用成功返回一个非负整数,如果调用失败,返回-1
	第一个参数:地址族			AF_INET表示IPv4地址族
	第二个参数:套接字类型		通过TCP连接传输
	第三个参数:传输协议		0默认情况,根据上面两个参数自动选择*/
	int serv_sock;
	if ((serv_sock = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
		perror("socket failed");
		exit(EXIT_FAILURE);
	}

	/*
	sockaddr_in这是一个存储IPv4地址信息的结构体
	struct sockaddr_in {
	short            sin_family;   // 地址族 (AF_INET)
	unsigned short   sin_port;     // 端口号
	struct in_addr   sin_addr;     // IPv4 地址
	char             sin_zero[8];  // 填充 0,保持与 sockaddr 结构体大小的兼容性
	};

	*/
	struct sockaddr_in serv_addr;
	/*
	将结构体里面的数据清零,后续再次赋值
	*/
	memset(&serv_addr, 0, sizeof(serv_addr));
	/*指定地址族为IPv4*/
	serv_addr.sin_family = AF_INET;
	/*设置了服务器的IP地址
	INADDR_ANY 是一个特殊的常量,它表示服务器将接受来自任何网络接口的连接请求
	htonl()函数用于将主机字节序转换成网络字节序,确保在不同架构的计算机上数据的正确的传输
	主机字节序:大端或者小端
	网络字节序:默认为大端*/
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	/*设置了服务器的端口号,SERVER_PORT为代码顶部设置的宏
	*/
	serv_addr.sin_port = htons(SERVER_PORT);
	/*绑定
	将一个套接字与特定的ip地址和端口号关联起来,使服务器能够在该地址上监听来自客户端的请求
	第一个参数:服务器套接字的文件描述符,通过此文件描述符对服务器套接字进行操作
	第二个参数:&serv_addr为要绑定到套接字的结构体的指针,由于bind()函数要求的参数类型,所以进行类型转换
	第三个参数:指定了serv_addr结构体的大小*/
	int valbind;
	if ((valbind = bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))) < 0) {
		perror("bind failed");
		fprintf(stderr, "Bind failed with error code: %d\n", WSAGetLastError());
		exit(EXIT_FAILURE);
	}

	//进入监听状态,等待用户发起请求
	int vallisten;
	if ((vallisten = listen(serv_sock, 3)) < 0) {
		perror("listen failed");
		exit(EXIT_FAILURE);
	}
	printf("等待客户端连接...\n");
	printf("——————————————————————————\n");

	//接收客户端请求
	/*定义一个结构体,用于存储客户端的地址信息,包括IP地址和端口号
	*/
	struct sockaddr_in clnt_addr;
	/*定义变量clnt_addr_size用来存储结构体clnt_addr的大小
	socklen_t 被设计用来表示套接字地址长度的类型
	遇到问题:若无#include <ws2tcpip.h> socklen_t 会报错
	*/
	socklen_t clnt_addr_size = sizeof(clnt_addr);
	/*accept()函数用于接受客户端的请求,并创建一个新的套接字用于与客户端通信
	第一个参数:服务器套接字的文件描述符
	第二个参数:函数调用成功后,客户端的信息将保存在结构体clnt_addr中
	第三个参数:指定了clnt_addr结构体的大小
	函数调用成功后返回一个新的文件描述符clnt_sock,代表与客户端通信的套接字
	*/
	int clnt_sock;
	if ((clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size)) < 0) {
		perror("accept failed");
		exit(EXIT_FAILURE);
	}
	printf("接收客户端请求成功\n");
	char client_ip[64];
	char buf[1024] = {0};
	/*打印客户端ip地址和端口号
	inet_ntop()函数用于将网络字节序的ip地址转换为可读的字符串格式,
	它被用来将客户端的 IP 地址从 clnt_addr.sin_addr.s_addr 转换为一个字符串,并将结果存储在 client_ip 数组中
	ntohs()用来将网络字节序的端口号转换为主机字节序的端口号
	*/
	printf("ip地址:%s\t port:%d\n",
		inet_ntop(AF_INET, &clnt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)),
		ntohs(clnt_addr.sin_port)
	);
	//设置发出请求的客户端的ip地址和端口号
	strcat_s(c_mes.IP,inet_ntop(AF_INET, &clnt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)));
	c_mes.PORT=ntohs(clnt_addr.sin_port);
	/*读取客户端请求
	用套接字从clnt_sock中接收数据,并将数据存储到缓冲区buf中,最多接受1024字节的数据
	如果调用成功返回读取的字节数,否则返回-1
	*/
	int valread;
	if ((valread = recv(clnt_sock, buf, 1024, 0)) < 0) {
		perror("read failed");
		exit(EXIT_FAILURE);
	}
	printf("——————————————————————————\n");
	printf("读取数据成功\n");
	printf("客户端请求为:\n%s\n", buf);

	//解析客户端请求
	do_http_request(buf);
	//检验解析成果
	printf("——————————————————————————\n");
	printf("客户端信息解析成功\n");
	printf("客户端信息解析如下:\nip地址:%s\n端口号:%d\n请求方法:%s  长度:%d\n请求url:%s  长度:%d\n请求协议和方法:%s  长度:%d\n",c_mes.IP, c_mes.PORT, c_mes.method,strlen(c_mes.method), c_mes.url, strlen(c_mes.url), c_mes.version, strlen(c_mes.version));
	printf("——————————————————————————\n");
	//实现http响应
	printf("对客户端请求进行响应\n");
	do_http_resolve(c_mes.url,clnt_sock);
	/*发送响应给客户端
	向已连接的套接字clnt_sock(即客户端)发送数据
	发送成功返回成功发送的字节数,发送失败返回-1
	*/
	//关闭套接字
	closesocket(clnt_sock);
	//关闭套接字
	closesocket(serv_sock);
	// 释放 Winsock 资源
	WSACleanup();
	return 0;
}
//对客户端请求进行解析
void do_http_request(char buf[1024]) {//对缓冲区接受到的客户请求信息进行解析
	int i=0;
	int j = 0;
		//获取方法
	j = 0;
	while (buf[i]!=' ') {
		c_mes.method[j++] = buf[i++];
	}
	i++;
	//获取url
	if (buf[i] == '/'&&buf[i+1]==' ') {//无url
		int j = 0;
		c_mes.url[j++] = '/';
		i = i + 1;
	}
	else {//有url  
		i++;//跳过/
		j = 0;
		while (buf[i] != ' ') {
			c_mes.url[j++] = buf[i++];
		}
			
	}
	i++;
	//获取协议及版本信息
	j = 0;
	while (buf[i] != '\r') {
			c_mes.version[j++] = buf[i++];

	}
		
}
//对客户端进行url数据解析并反应(重写get方法)
int do_http_resolve(char url[1024],int clnt_sock) {
	void not_found(int clnt_sock);
	void unimplemented(int clnt_sock);
	int do_http_response(int clnt_sock, const char* path);
	void do_http_url_process(char url[1024]);//对客户端的url进行解析
	printf("进入响应函数\n");
	//判断http请求是get方法
	if ((strcmp(c_mes.method,"GET"))==0) {
		printf("客户请求是get方法\n");
		//对url中的路径和参数进行解析
		//计算ip地址和端口号的长度
		int lens = strlen(c_mes.IP) + 4;
		lens = lens + 1;//冒号长度
		//http://192.168.10.124:8080/index.html?name=123&psd=1234
		//解析客户端路径和键值对参数
		do_http_url_process(url);
		//输出处理后的数据
		for (int i = 0; i < u_mes.k_v_len; i++) {
			printf("客户端path:%s\t键值对数组u_mes[%d].key=%s,u_mes[%d].value=%s\n", u_mes.path, i, u_mes.k_v[i].key, i, u_mes.k_v[i].value);
		}
		char paths[20] = {};
		int i = 0;
		paths[0] = '.'; paths[1] = '/';
		while (u_mes.path[i] != '\0') {
			//printf("u_mes.path[i]=%c,i=%d\n",u_mes.path[i],i);
			paths[i + 2] = u_mes.path[i];
			i++;
		}
		paths[i + 2] = '\0';
		printf("拼接后的文件目录地址paths=%s\n", paths);	
		/*判断文件信息
		int stat(const char *pathname, struct stat *buf);
		第一个参数为文件路径名,第二个参数是一个关于文件信息的结构体
		struct stat {
			dev_t     st_dev;         // 文件的设备编号
			ino_t     st_ino;         // 文件的 inode 编号
			mode_t    st_mode;        // 文件的类型和权限
			nlink_t   st_nlink;       // 连接数
			uid_t     st_uid;         // 文件所有者的用户 ID
			gid_t     st_gid;         // 文件所有者的组 ID
			dev_t     st_rdev;        // 如果是特殊文件,设备编号
			off_t     st_size;        // 文件大小(以字节为单位)
			blksize_t st_blksize;     // 文件系统 I/O 缓冲区大小
			blkcnt_t  st_blocks;      // 分配的块数
			time_t    st_atime;       // 最后一次访问时间
			time_t    st_mtime;       // 最后一次修改时间
			time_t    st_ctime;       // 最后一次更改时间
		}
		*/
		struct stat filebuf;
		memset(&filebuf, 0, sizeof(struct stat));
		if (stat(paths, &filebuf)==-1) {//获取文件信息失败
			printf("stat %s find fail\n",paths);
			not_found(clnt_sock);//无法获取文件信息
		}
		else {//获取文件信息成功,发送响应
			//拼接相对文件路径
			printf("获取文件信息成功\n");			
			//正常入口
			do_http_response(clnt_sock,paths);
		}
	}
	else {
		printf("不是get方法,暂时无法响应!\n");
		unimplemented(clnt_sock);//服务器不支持的请求方法
	}
	return 1;
}
void do_http_url_process(char url[1024]) {
	int i = 0;
	int j = 0;
	//提取path
	while (url[i] != '?'&& url[i] != '\0') {
		u_mes.path[j++] = url[i++];
	}
	u_mes.path[i] = '\0';//加上结束标志
	i++;
	j = 0;
	int temp = 0;//用来标识是否出现等号
	int k = 0;//控制键值对数组下标
	while (url[i] != '\0') {//对键值对进行赋值
		if (url[i] != '&') {//具体每队键值对的赋值
			if (url[i] == '=') {
				temp = 1;//切换到value的赋值
				j = 0;
				i++;//跳过=
			}
			else if (url[i] != '=') {
				if (temp == 0) {//对key赋值
					u_mes.k_v[k].key[j++] = url[i++];
				}
				else if (temp == 1) {//对value赋值
					u_mes.k_v[k].value[j++] = url[i++];
				}
			}
		}
		else {
			k++;
			i++;
			j = 0;
			temp = 0;
		}
	}
	u_mes.k_v_len = k;//记录实际参数个数
}
//对客户端请求进行具体响应 char*path和char path[100]等价
int do_http_response(int clnt_sock,const char *path) {//传入请求网页和具体参数	
	//发送头部
	int send_message(int clnt_sock, FILE * resource, const char* header);
	//发送主体
	printf("进入do_http_response函数\tpath=%s\n",path);
	//确定http响应状态行
	char* header = u200;
	//声明一个文件指针并将其初始化为null
	errno_t err;
	FILE *resource = NULL;
	//尝试打开文件,成功后返回文件指针,后续可通过文件指针来操作这个文件
	err = fopen_s(&resource,path, "r");
	if (resource == NULL) {
		printf("找不到请求资源%s\n", path);
		header = u404;
		return 0;
	}
	if (strcmp(path, "./error.html") == 0) {
		header = u404;
	}
	else if (strcmp(path, "./inner.html") == 0) {
		header = u500;
	}
	else if (strcmp(path, "./unimplemented.html") == 0) {
		header = u501;
	}
	else if (strcmp(path, "./bad_request.html") == 0) {
		header = u400;
	}
	//发送头部及主体内容
	send_message(clnt_sock, resource, header);
	printf("——————————————————————————\n");
	printf("客户端返回数据成功!\n");
	//关闭文件描述符
	fclose(resource);
}
//定义函数来实现状态的响应
void not_found(int clnt_sock) {
	do_http_response(clnt_sock,"./error.html");
}
void inner_error(int clnt_sock) {
	do_http_response(clnt_sock, "./inner.html");
}
void unimplemented(int clnt_sock) {
	do_http_response(clnt_sock, "./unimplemented.html");
}
void bad_request(int clnt_sock) {
	do_http_response(clnt_sock, "./bad_request.html");
}

//发送头部及主体内容
int send_message(int clnt_sock, FILE* resource, const char* header) {
	printf("进入send_headers函数\n");
	char buff[100] = { 0 };//存放主体信息
	char mess[1024] = { 0 };
	char clent_mes[2048] = { 0 };
	struct stat st;
	int fileId = _fileno(resource);//获取文件描述符相关的文件标识符
	if (fstat(fileId,&st)==-1) {
		printf("inner error\n");
		inner_error(clnt_sock);
		return -1;
	}
	char buf[1024] = { 0 };//存放头部信息
	char temp[64];
	//写入状态行
	strcat_s(buf,header);
	//消息报头
	strcat_s(buf,"Server:Martin Server\r\n");
	strcat_s(buf,"Content_Type:text/htmll\r\n");
	strcat_s(buf,"Connection:Close\r\n");
	/*
	sprintf()则将数据输出到指定的字符串中
	*/
	sprintf_s(temp, "Contene-Length:%d\r\n\r\n", st.st_size);
	strcat_s(buf,temp);
	printf("头部信息为:\n%s\n",buf);

	while (fgets(buff, sizeof(buff), resource) != NULL) {//处理文件内容
		size_t buf_len = strlen(buff);
		if (buf_len > 0) {
			size_t remain_space = sizeof(mess) - sizeof(int) * (strlen(mess) / sizeof(int));
			if (remain_space >= buf_len) {
				memcpy((char*)mess + strlen((char*)mess), buff, buf_len);
			}
			else {
				printf("mess中没有剩余空间!\n");
				break;
			}
		}
	}
	strcpy_s(clent_mes, buf);
	strcat_s(clent_mes, mess);
	printf("发送给客户的信息是:%s\n", clent_mes);
	//发送给客户端
	if (send(clnt_sock, clent_mes,strlen(clent_mes),0)<0) {
		printf("send failed\n");
		return -1;
	}
	return 0;
}

客户端代码

客户端流程:
在这里插入图片描述

#include<stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h> 
#pragma comment(lib, "Ws2_32.lib")
int main() {
	// 初始化 Winsock
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
		printf("WSAStartup failed\n");
		return 1;
	}
	//创建客户端对象
	int socket_fd;
	if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0))== 0){
		perror("socket failed");
		exit(EXIT_FAILURE);
	}
	struct sockaddr_in sock_addr;	//定义结构体
	sock_addr.sin_family = AF_INET; //设置为ipv4
	sock_addr.sin_port = htons(8080); //设置端口号为8080
	//设置网络ip
	if (inet_pton(AF_INET, "192.168.10.124", &sock_addr.sin_addr) <= 0) {
		perror("inet_pton() failed");
		exit(EXIT_FAILURE);
	}

	//连接服务器
	if ((connect(socket_fd, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) < 0)) {
		perror("connect failed");
		exit(EXIT_FAILURE);
	}
	printf("对服务器发起连接\n");

	//准备http请求
	char http[] = "GET /index.html HTTP/1.1\r\nHost: 192.168.10.124:8080\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nMozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36\r\ntext/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\n";
	
	/*char http[1024];
	memset(&http, 0, sizeof(http));
	//你不能直接将字符串赋值给字符数组,需要使用strcpy函数
	strcpy_s(http, "hello server!");*/
	//发送http请求协议
	if ((send(socket_fd, http, strlen(http),0)) < 0) {
		perror("send failed");
		exit(EXIT_FAILURE);
	}
	printf("发送成功\n");
	char buf[2048] = {0};
		//读取http回应
	if (recv(socket_fd, buf, sizeof(buf),0)<0) {
			perror("read failed");
				exit(EXIT_FAILURE);
	}
	printf("读取数据成功\n");
	printf("服务器端给出的回应是%s\n", buf);
		//关闭套接字
		closesocket(socket_fd);
		WSACleanup();
		return 0;
}
  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是基于UDP协议的简易聊天机器人客户端服务器端C语言代码。 客户端代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #define SERVER_IP "127.0.0.1" #define SERVER_PORT 8080 #define BUFFER_SIZE 1024 int main() { // 创建UDP套接字 int client_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (client_socket < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } // 设置服务器地址 struct sockaddr_in server_address; memset(&server_address, 0, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_port = htons(SERVER_PORT); if (inet_pton(AF_INET, SERVER_IP, &server_address.sin_addr) <= 0) { perror("invalid address"); exit(EXIT_FAILURE); } // 向服务器发送消息 char buffer[BUFFER_SIZE]; while (1) { printf("Enter message: "); fgets(buffer, BUFFER_SIZE, stdin); if (strcmp(buffer, "exit\n") == 0) { break; } sendto(client_socket, buffer, strlen(buffer), 0, (struct sockaddr*)&server_address, sizeof(server_address)); // 从服务器接收响应 memset(buffer, 0, BUFFER_SIZE); socklen_t server_address_len = sizeof(server_address); ssize_t received_bytes = recvfrom(client_socket, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&server_address, &server_address_len); if (received_bytes < 0) { perror("receive failed"); exit(EXIT_FAILURE); } printf("Server response: %s\n", buffer); } // 关闭套接字并退出 close(client_socket); return 0; } ``` 服务器端代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #define SERVER_IP "127.0.0.1" #define SERVER_PORT 8080 #define BUFFER_SIZE 1024 int main() { // 创建UDP套接字 int server_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (server_socket < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } // 绑定服务器地址 struct sockaddr_in server_address; memset(&server_address, 0, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = htonl(INADDR_ANY); server_address.sin_port = htons(SERVER_PORT); if (bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } // 接收来自客户端的消息并回复 char buffer[BUFFER_SIZE]; struct sockaddr_in client_address; socklen_t client_address_len = sizeof(client_address); while (1) { memset(buffer, 0, BUFFER_SIZE); ssize_t received_bytes = recvfrom(server_socket, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&client_address, &client_address_len); if (received_bytes < 0) { perror("receive failed"); exit(EXIT_FAILURE); } printf("Client message: %s", buffer); // 向客户端发送响应 char* response = "Hello, I am a chatbot!"; sendto(server_socket, response, strlen(response), 0, (struct sockaddr*)&client_address, client_address_len); } // 关闭套接字并退出 close(server_socket); return 0; } ``` 请注意,本代码仅作为示例,未进行异常处理和数据验证。在实际应用中,需要根据需求进行相应的改进。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nan_black

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值