项目需求
实现一个http 服务器项目,服务器启动后监听80端口的tcp 连接,当用户通过任意一款浏览器访问我们的http服务器,http服务器会查找用户访问的html页面是否存在,如果存在则通过http 协议响应客户端的请求,把页面返回给浏览器,浏览器显示html页面;如果页面不存在,则按照http 协议的规定,通知浏览器此页面不存在(404 NOT FOUND)
网络协议栈中各层的功能如下:
应用层:根据特定的通信目的,对数据进行分析处理,以达到某种业务性的目的。
传输层:处理传输时遇到的问题,主要是保证数据传输的可靠性。
网络层:完成数据的转发,解决数据去哪里的问题。
链路层:负责数据真正的发生过程。
数据封装与分用的过程如下:
发送端在发生数据前,该数据需要先自顶向下贯穿网络协议栈完成数据的封装,在这个过程中,每一层协议都会为该数据添加上对应的报头信息。接收端在收到数据后,该数据需要先自底向上贯穿网络协议栈完成数据的解包和分用,在这个过程中,每一层协议都会将对应的报头信息提取出来。
需求分析
Html 页面
Html,全称 Hypertext Markup Language,也就是“超文本链接标示语言”。
HTML文本是由 HTML命令组成的描述性文本,HTML 命令可以说明文字、 图形、动画、声音、表格、链接等。
HTTP 协议
HTTP 协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
HTTP的特点:
1.客户端服务器模式(CS,BS):在一条通信线路上必定有一端是客户端,另一端是服务器端,请求从客户端发出,服务器响应请求并返回。
2.简单快速:客户端向服务器请求服务时,只需传送请求方法和请求资源路径,不需要发送额外过多的数据,并且由于HTTP协议结构较为简单,使得HTTP服务器的程序规模小,因此通信速度很快。
3.灵活:HTTP协议对数据对象没有要求,允许传输任意类型的数据对象,对于正在传输的数据类型,HTTP协议将通过报头中的Content-Type属性加以标记。
4.无连接:每次连接都只会对一个请求进行处理,当服务器对客户端的请求处理完毕并收到客户端的应答后,就会直接断开连接。HTTP协议采用这种方式可以大大节省传输时间,提高传输效率。
5.无状态:HTTP协议自身不对请求和响应之间的通信状态进行保存,每个请求都是独立的,这是为了让HTTP能更快地处理大量事务,确保协议的可伸缩性而特意设计的。
URL格式
URL(Uniform Resource Lacator)叫做统一资源定位符,也就是我们通常所说的网址,是因特网的万维网服务程序上用于指定信息位置的表示方法。
一个URL大致由如下几部分构成:
1.http://
表示的是协议名称,表示请求时需要使用的协议,通常使用的是HTTP协议或安全协议HTTPS。
2.user:pass
表示的是登录认证信息,包括登录用户的用户名和密码。(可省略)
3.www.example.jp
表示的是服务器地址,通常以域名的形式表示。
4.80
表示的是服务器的端口号。(可省略)
5./dir/index.html
表示的是要访问的资源所在的路径(/表示的是web根目录)。
6.uid=1
表示的是请求时通过URL传递的参数,这些参数以键值对的形式通过&符号分隔开。(可省略)
7.ch1
表示的是片段标识符,是对资源的部分补充。(可省略)
注意:
如果访问服务器时没有指定要访问的资源路径,那么浏览器会自动帮我们添加/,但此时仍然没有指明要访问web根目录下的哪一个资源文件,这时默认访问的是目标服务的首页。
大部分URL中的端口号都是省略的,因为常见协议对应的端口号都是固定的,比如HTTP、HTTPS和SSH对应的端口号分别是80、443和22,在使用这些常见协议时不必指明协议对应的端口号,浏览器会自动帮我们进行填充。
URI、URL、URN
URI、URL、URN的定义如下:
URI(Uniform Resource Indentifier)统一资源标识符:用来唯一标识资源。
URL(Uniform Resource Locator)统一资源定位符:用来定位唯一的资源。
URN(Uniform Resource Name)统一资源名称:通过名字来标识资源。
URI、URL、URN三者的关系 URL是URI的一种,URL不仅能唯一标识资源,还定义了该如何访问或定位该资源,URN也是URI的一种,URN通过名字来标识资源,因此URL和URN都是URI的子集。
URI有绝对和相对之分:
绝对的URI:对标识符出现的环境没有依赖,比如URL就是一种绝对的URI,同一个URL无论出现在什么地方都能唯一标识同一个资源。
相对的URI:对标识符出现的环境有依赖,比如HTTP请求行中的请求资源路径就是一种相对的URI,这个资源路径出现在不同的主机上标识的就是不同的资源。
客户端请求
客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式:
请求行:[请求方法] + [URI] + [HTTP版本]
请求头部:请求的属性,这些属性都是以key: value的形式按行陈列的。
回车符换行符:遇到空行表示请求报头结束。
请求数据:请求正文允许为空字符串,如果请求数据存在,则在请求头部中会有一个Content-Length属性来标识请求正文的长度。
服务端响应
服务器响应客户端的HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
状态行:[HTTP版本] + [状态码] + [状态码描述]。
消息报头:响应的属性,这些属性都是以key: value的形式按行陈列的。
回车符换行符:遇到空行表示请响应报头结束。
响应正文:响应正文允许为空字符串,如果响应正文存在,则在响应报头中会有一个Content-Length属性来标识响应正文的长度。
响应代号 | 代号描述 | |
---|---|---|
服务器上存在请求的内容,并可以响应给客户端 | 200 | OK |
客户端的请求有异常,方法有问题 | 501 | Method Not Implemented |
服务器收到请求后,因为自身的问题没法响应 | 500 | Internal Server Error |
请求的内容不存在 | 404 | NOT FOUND |
客户端发送的请求格式有问题等 | 400 | BAD REQUEST |
HTTP常见的Header
Content-Type:数据类型(text/html等)。
Content-Length:正文的长度。
Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上。
User-Agent:声明用户的操作系统和浏览器的版本信息。
Referer:当前页面是哪个页面跳转过来的。
Location:搭配3XX状态码使用,告诉客户端接下来要去哪里访问。
Cookie:用户在客户端存储少量信息,通常用于实现会话(session)的功能。
实战整了一个后台服务,真香!
实现 Mini 型 http 服务器
在浏览器网址搜索栏输入 xxx/fei.html 登录到我的服务器端. xxx/fei.html 是我电脑的ip地址.在 linux shell 窗口 输入 ip addr 可获取本机ip
代码实现
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>
#include <errno.h>
#include <sys/stat.h>
#include <pthread.h>#define SERVER_PORT 80static int debug = 1;using namespace std;int get_line(int socket, char *buf, int size);
void* do_http_request(void* pclient_socket);
void do_http_response(int client_sock, const char *path);
void do_http_response1(int client_sock);int headers(int client_sock, FILE *resource);
void cat(int client_sock, FILE *resource);
void inner_error(int client_sock);
void unimplemented(int client_sock);
void bad_request(int client_sock);
void not_found(int client_sock);int main(void){int sock;//代表信箱struct sockaddr_in server_addr;//1.美女创建信箱sock = socket(AF_INET, SOCK_STREAM, 0);//2.清空标签,写上地址和端口号bzero(&server_addr, sizeof(server_addr));server_addr.sin_family = AF_INET;//选择协议族IPV4server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地所有IP地址server_addr.sin_port = htons(SERVER_PORT);//绑定端口号//实现标签贴到收信得信箱上bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));//把信箱挂置到传达室,这样,就可以接收信件了listen(sock, 128);//万事俱备,只等来信printf("等待客户端的连接\n");int done =1;while(done){struct sockaddr_in client;int client_sock, len, i;char client_ip[64];char buf[256];pthread_t id;int* pclient_sock = NULL;socklen_t client_addr_len;client_addr_len = sizeof(client);printf("come here!\n");client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);//打印客服端IP地址和端口号printf("client ip: %s\t port : %d\n",inet_ntop(AF_INET, &client.sin_addr.s_addr,client_ip,sizeof(client_ip)),ntohs(client.sin_port));/*处理http请求,读取客户端的数据*/// do_http_request(client_sock);//启动线程处理 http 请求pclient_sock = (int*)malloc(sizeof(int));*pclient_sock = client_sock;pthread_create(&id, NULL, do_http_request, (void *)pclient_sock);// close(client_sock);}close(sock);return 0;
}void* do_http_request(void* pclient_socket){/*read client http request*/int len=0;char buf[256];char method[64];char url[256];char path[512];struct stat st;int client_socket = *(int*)pclient_socket;//1.read col of requestlen = get_line(client_socket, buf, sizeof(buf));if(len>0){ //read col of requestsint i=0, j=0;while(!isspace(buf[j]) && (i < sizeof(method)-1)){method[i] = buf[j];i++;j++;}method[i] = '\0';if(debug){cout << "request method: " << method << endl;}if(strncasecmp(method, "GET", i) == 0){ //Only handle get requestsif(debug){cout<<"method = GET" <<endl;}// get urlwhile(isspace(buf[j++])); //ignore spacei=0;while(!isspace(buf[j]) && (sizeof(url)-1)){url[i] = buf[j];i++;j++;}url[i] = '\0';if(debug){cout<<"url: "<<url<<endl;}// continue to read the header of httpdo{len = get_line(client_socket, buf, sizeof(buf));if(debug){cout<<"read: "<< buf<<endl;}}while(len>0);// *** Locate the local HTML address of the server***// Handle the "?" in the URL{char *pos = strchr(url, '?');if(pos){*pos = '\0';cout<<"real url: "<< url <<endl;}}sprintf(path, "./html_docs/%s", url);if(debug){cout<<"path: "<< path <<endl;}// Execute HTTP response// 判断文件是否存在,如果存在就响应200 OK,同时发送相应的html文件,如果不存在,就响应404 NOT FOUND.if(-1 == stat(path, &st)){ // if file not existfprintf(stderr, "stat %s failed. reason: %s\n", path, strerror(errno));not_found(client_socket);}else{if(S_ISDIR(st.st_mode)){strcat(path, "/index.html"); // is directory?}//do_http_response1(client_socket);do_http_response(client_socket, path); }}else{ // no get requests, read all http headers and Response server 501 "Method Not Implemented"fprintf(stderr, "warning! other request [%s]\n", method);do{len = get_line(client_socket, buf, sizeof(buf));if(debug){cout<<"read: "<< buf<<endl;}}while(len>0);unimplemented(client_socket); //}}else{ // fialed to request// bad requestbad_request(client_socket);}close(client_socket);if(pclient_socket) free(pclient_socket); //释放动态分配的内存return NULL;}void unimplemented(int client_sock){const char*reply= "HTTP/1.0 501 unimplemented\r\n\Content-Type: text/html\r\n\\r\n\<HTML lang=\"zh-CN\">\r\n\<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\<HEAD>\r\n\<TITLE>Mehod Not Implemented</TITLE>\r\n\</HEAD>\r\n\<BODY>\r\n\<P>unimplemented\r\n\<P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\</BODY>\r\n\</HTML>";int len = write(client_sock,reply,strlen(reply));if(debug) fprintf(stdout,reply);if(len<=0) fprintf(stderr,"send reply failed.reason:%s\n",strerror(errno));}void bad_request(int client_sock){const char*reply= "HTTP/1.0 400 bad_request\r\n\Content-Type: text/html\r\n\\r\n\<HTML lang=\"zh-CN\">\r\n\<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\<HEAD>\r\n\<TITLE>Bad Requst</TITLE>\r\n\</HEAD>\r\n\<BODY>\r\n\<P>Bad Request!\r\n\<P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\</BODY>\r\n\</HTML>";int len = write(client_sock,reply,strlen(reply));if(debug) fprintf(stdout,reply);if(len<=0) fprintf(stderr,"inner reply failed.reason:%s\n",strerror(errno));}void do_http_response(int client_sock, const char *path){FILE *resource = NULL;int ret = 0;resource = fopen(path,"r");if(resource == NULL){not_found(client_sock);return;}// 1.send http headerret = headers(client_sock, resource);// 2.send http bodyif(!ret){cat(client_sock, resource);}fclose(resource);}/*
返回关于响应文件信息的 http 头部
输入:
client_sock:客服端socket的句柄
resource:文件的句柄
返回值:success->0 fail->-1
*/int headers(int client_sock, FILE *resource){struct stat st;int fileid = 0;char buf[1024]={0};char tmp[64];strcpy(buf,"HTTP/1.0 200 OK\r\n");strcat(buf,"Server: Martin Server\r\n");strcat(buf,"Content-Type: text/html\r\n");strcat(buf,"Connection: Close\r\n");fileid = fileno(resource);if(fstat(fileid, &st) == -1){inner_error(client_sock);return -1;}snprintf(tmp, 64, "Content-Length: %d\r\n\r\n", st.st_size);strcat(buf,tmp);if(debug){fprintf(stdout, "header: %s\n", buf);}if(send(client_sock, buf, strlen(buf), 0)<0){fprintf(stderr, "send failed. data: %s, reason: %s\n", buf, strerror(errno));return -1;}return 0;}// 说明:实现将html文件的内容按照行读取并发送客户端
void cat(int client_sock, FILE *resource){char buf[1024];fgets(buf, sizeof(buf), resource);while(!feof(resource)){int len = write(client_sock, buf, strlen(buf));if(len<0){fprintf(stderr, "send body error. reason: %s\n",strerror(errno));break;}if(debug) fprintf(stdout, "%s", buf);fgets(buf, sizeof(buf), resource);}}void do_http_response1(int client_sock){//响应httpconst char *main_header = "HTTP/1.0 200 OK\r\nServer: Martin Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";const char *welcome_content = "\<html lang=\"zh-CN\">\n\<head>\n\<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\<title>This is a test</title>\n\</head>\n\<body>\n\<div align=center height=\"500px\" >\n\<br/><br/><br/>\n\<h2>大家好!</h2><br/><br/>\n\<form action=\"commit\" method=\"post\">\n\尊姓大名: <input type=\"text\" name=\"name\" />\n\<br/>芳龄几何: <input type=\"password\" name=\"age\" />\n\<br/><br/><br/><input type=\"submit\" value=\"提交\" />\n\<input type=\"reset\" value=\"重置\" />\n\</form>\n\</div>\n\</body>\n\</html>";//1.送main_headerint len = write(client_sock, main_header, sizeof(main_header));if(debug) fprintf(stdout, "...do http response...\n");if(debug) fprintf(stdout, "write[%d]:%s", len, main_header);//2.生成Content-Lengthchar send_buf[64];int wc_len = strlen(welcome_content);len = snprintf(send_buf,64,"Content-Length: %d\r\n\r\n",wc_len);len = write(client_sock,send_buf,len);if(debug) fprintf(stdout, "write[%d]: %s", len, send_buf);len = write(client_sock,welcome_content,wc_len);if(debug) fprintf(stdout, "write[%d]: %s", len, welcome_content);}int get_line(int socket, char *buf, int size){int count = 0;char ch = '\0';int len = 0;while ((count < size -1)&& ch != '\n'){len = read(socket, &ch, 1);if(1 == len){ // normalif('\r' == ch){continue;}else if('\n' == ch){buf[count] = '\0';break;}buf[count] = ch;count++;}else if(-1 == len){ // abnormalperror("read failed");count = -1;break;}else{ //read return 0: client has closed socketfprintf(stderr, "client close\n");count = -1;break;}}if(count>=0) buf[count] = '\0';return count; //count=-1:error!! count=0:read a Blank row; count > 0: read a row success!}void not_found(int client_sock){const char*reply= "HTTP/1.0 404 NOT FOUND\r\n\Content-Type: text/html\r\n\\r\n\<HTML lang=\"zh-CN\">\r\n\<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\<HEAD>\r\n\<TITLE>NOT FOUND</TITLE>\r\n\</HEAD>\r\n\<BODY>\r\n\<P>文件不存在!\r\n\<P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\</BODY>\r\n\</HTML>";int len = write(client_sock, reply, strlen(reply));if(debug) fprintf(stdout, reply);if(len<=0) fprintf(stderr,"send reply failed.reason:%s\n",strerror(errno));
}void inner_error(int client_sock){const char*reply= "HTTP/1.0 500 Internal Sever Error\r\n\Content-Type: text/html\r\n\\r\n\<HTML lang=\"zh-CN\">\r\n\<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\<HEAD>\r\n\<TITLE>NOT FOUND</TITLE>\r\n\</HEAD>\r\n\<BODY>\r\n\<P>服务器内部出错!\r\n\<P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\</BODY>\r\n\</HTML>";int len = write(client_sock,reply,strlen(reply));if(debug) fprintf(stdout,reply);if(len<=0) fprintf(stderr,"inner reply failed.reason:%s\n",strerror(errno));}
html 文件
览器访问服务器成功后看到的页面效果
在当前文件夹建立文件 目录 mkdir
html_docs
进入这个目录 建立一个名为 fei.html
的文件将下面的 html
文本放入
<html lang=\"zh-CN\">
<head>
<meta content=\"text/html; charset=utf-8 bom\" http-equiv=\"Content-Type\">
<title>This is a test</title>
</head>
<body>
<div align=center height=\"500px\" >
<br/><br/><br/>
<h2>Good guys! Welcome to China Service!</h2><br/><br/>
<form action="commit" method="post">
尊姓大名: <input type="text" name="name" />
<br/>芳龄几何: <input type="password" name="age" />
<br/><br/><br/><input type="submit" value="提交" />
<input type="reset" value="重置" />
</form>
</div>
</body>
</html>
通过c/c++在linux系统实现Mini型http服务器的并发访问
相关函数
bezro()
函数原型:extern void bzero(void *s, int n)
头文件:<string.h>
功能:置字符串s
的前n
个字节为零且包括‘\0
’
说明:无返回值
memset()
函数原型:extern void *memset(void *buffer, int c, int count)
头文件:<string.h>
功能:把buffer
所指内存区域的前count
个字节设置成c
的值。
stat 函数与 struct stat 结构体
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
作用:获取文件信息
path:文件的路径;
buf:传入的保存文件状态的指针,用于保存文件的状态;
返回值:成功返回0
,失败返回-1
,设置errno
。
struct stat
{dev_t st_dev; /* ID of device containing file */文件使用的设备号ino_t st_ino; /* inode number */ 索引节点号 mode_t st_mode; /* protection */ 文件对应的模式,文件,目录等nlink_t st_nlink; /* number of hard links */ 文件的硬连接数 uid_t st_uid; /* user ID of owner */ 所有者用户识别号gid_t st_gid; /* group ID of owner */ 组识别号 dev_t st_rdev; /* device ID (if special file) */ 设备文件的设备号off_t st_size; /* total size, in bytes */ 以字节为单位的文件容量 blksize_t st_blksize; /* blocksize for file system I/O */ 包含该文件的磁盘块的大小 blkcnt_t st_blocks; /* number of 512B blocks allocated */ 该文件所占的磁盘块 time_t st_atime; /* time of last access */ 最后一次访问该文件的时间 time_t st_mtime; /* time of last modification */ /最后一次修改该文件的时间 time_t st_ctime; /* time of last status change */ 最后一次改变该文件状态的时间
};
S_IFMT 0170000 文件类型的位遮罩S_IFSOCK 0140000 套接字S_IFLNK 0120000 符号连接S_IFREG 0100000 一般文件S_IFBLK 0060000 区块装置S_IFDIR 0040000 目录S_IFCHR 0020000 字符装置S_IFIFO 0010000 先进先出
S_ISUID 04000 文件的(set user-id on execution)位S_ISGID 02000 文件的(set group-id on execution)位S_ISVTX 01000 文件的sticky位
S_IRUSR(S_IREAD) 00400 文件所有者具可读取权限S_IWUSR(S_IWRITE)00200 文件所有者具可写入权限S_IXUSR(S_IEXEC) 00100 文件所有者具可执行权限
S_IRGRP 00040 用户组具可读取权限S_IWGRP 00020 用户组具可写入权限S_IXGRP 00010 用户组具可执行权限
S_IROTH 00004 其他用户具可读取权限S_IWOTH 00002 其他用户具可写入权限S_IXOTH 00001 其他用户具可执行权限
上述的文件类型在POSIX中定义了检查这些类型的宏定义:S_ISLNK (st_mode) 判断是否为符号连接S_ISREG (st_mode) 是否为一般文件S_ISDIR (st_mode) 是否为目录S_ISCHR (st_mode) 是否为字符装置文件S_ISBLK (s3e) 是否为先进先出S_ISSOCK (st_mode) 是否为socket若一目录具有sticky位(S_ISVTX),则表示在此目录下的文件只能被该文件所有者、此目录所有者或root来删除或改名,在linux中,最典型的就是这个/tmp目录啦。
st_mode
主要包含了 3 部分信息:
15-12
位保存文件类型
11-9
位保存执行文件时设置的信息
8-0
位保存文件访问权限
pthread
每个线程都有一个在进程中唯一的线程标识符,用一个数据类型 pthread_t
表示,该数据类型在 Linux 中就是一个无符号长整型数据。
int pthread_create (pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
作用:创建新的线程
返回值:若创建成功,返回0
;若出错,则返回错误编号。
参数:
thread
是线程标识符,但这个参数不是由用户指定的,而是由 pthread_create
函数在创建时将新的线程的标识符放到这个变量中。
attr
指定线程的属性,可以用 NULL
表示默认属性。
start_routine
指定线程开始运行的函数。
arg
是 start_routine
所需要的参数,是一个无类型指针。
结束线程
当发生以下情形之一时,线程就会结束:
1.线程运行的函数return
了,也就是线程的任务已经完成;
2.线程调用了 pthread_exit
函数;
3.其他线程调用 pthread_cancel
结束这个线程;
4.进程调用 exec()
或 exit()
,结束了;
5.main()
函数先结束了,而且 main()
自己没有调用 pthread_exit
来等所有线程完成任务。
当然,一个线程结束,并不意味着它的所有信息都已经消失------僵尸线程的问题。
malloc
#include<stdlib.h>
//或者 #include<malloc.h>
extern void * malloc(unsigned int num_byte);
功能:分配长度为num_byte
字节的内存块
参数:需要分配的内存字节数,如果内存池中的可用内存可以满足这个需求,malloc就返回一个指向被分配的内存块起始位置的指针
返回值:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL
。
注意:
1.malloc
函数返回的是void *
类型,如果写成:p = malloc(sizeof(int));
则程序无法通过编译,报错:“不能将void *
赋值给int *
类型变量”。所以必须通过(int
*)来强制类型转换;
2.函数的实参为sizeof(int)
,用于指明一个整形数据需要的大小,如果写成: int * p =(int *)malloc(1);
代码也能通过编译,但事实上只分配了1个字节大小的内存空间
malloc所分配的是一块连续的内存。
当内存不使用时,应使用free()
函数将内存块释放:void free( void * pointer);
功能:释放内存
参数:free
函数的参数要么是NULL,要么是一个先前从malloc
、calloc
或realloc
返回的值。向free
传递一个NULL
参数不会产生任何效果。
返回值:无
常见的动态内存错误
1.对NULL指针进行解引用操作(忘记检查所请求的内存是否分配成功)
2.对分配的内存进行操作时越过了分配内存的边界
3.释放并非动态分配的内存(传递给free的指针必须是一个从malloc、calloc或realloc函数返回的指针)
4.试图释放一块动态分配的内存的一部分(释放一块内存的一部分是不允许的,动态分配的内存必须整块一起释放)
5.一块动态内存被释放之后被继续使用等。(不要访问已经被free函数释放了的内存,注意指针的复制)
C语言——malloc函数详解
转义字符
转义字符 | 转义功能 | ASCII 码值 |
---|---|---|
\0 | 空字符 | 0 |
\a | 响铃 | 7 |
\b | 退格(Backspace) | 8 |
\t | 水平制表符(即横向跳格) | 9 |
\n | 换行(Enter) | 10 |
\v | 竖向跳格 | 11 |
\f | 换页 | 12 |
\r | 回车 | 13 |
isspace()
int isspace(int c);
作用:检查所传的字符是否是空白字符
返回值:如果 c
是一个空白字符,则该函数返回非零值(true
),否则返回 0
(false
)
strncasecmp()
strcasecmp() 函数对字符串s1和s2执行逐字节比较,忽略字符的大小写
#include <strings.h>
int strcasecmp(const char *s1, const char *s2);
int strncasecmp(const char *s1, const char *s2, size_t n);
作用:函数对字符串s1
和s2
执行逐字节比较,忽略字符的大小写。如果发现s1
分别小于、匹配或大于s2
,则返回一个小于、等于或大于0
的整数
函数输入值:
s1 / s2
:对比字符串
n
:对比个数
函数返回值:
strcasecmp()
函数在忽略大小写后
如果发现s1
分别小于、匹配或大于s2
,则返回一个小于、等于或大于零的整数。
(1)若参数s1
和s2
字符串相同则返回0
(2)若参数s1
大于s2
,则返回大于0
的值
(3)若参数s1
小于s2
,则返回小于0
的值
strcat
#include <string.h>
char *strcat(char *_Destination,const char *_Source)
strcat函数是字符串追加函数,也就是在字符串后面追加另一个字符串。
在自己实现的时候就需要提前找到被追加的字符串的尾部,也就是找到'\0'
char* my_strcat(char* dest, const char* str)
{char* ret = dest;assert(*dest != NULL);assert(*str);//找到目的字符串里的'\0'while (*dest != '\0'){dest++;}//追加while (*dest++ = *str++){;}return ret;
}
fileno()
#include <stdio.h>
int fileno(FILE *stream)
函数说明:fileno()用来取得参数stream指定的文件流所使用的文件描述词
返回值:返回文件描述词
fgets()
# include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
功能:是从 stream
流中读取 size
个字符存储到字符指针变量 s
所指向的内存空间。它的返回值是一个指针,指向字符串中第一个字符的地址。
参数:
s
代表要保存到的内存空间的首地址,可以是字符数组名,也可以是指向字符数组的字符指针变量名。
size
代表的是读取字符串的长度。
stream
表示从何种流中读取,可以是标准输入流 stdin,也可以是文件流,即从某个文件中读取。
feof()
作用:用于判断文件指针是否指向文件末尾
返回值:当指向文件尾,返回1;否则返回0;
在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束!!!
feof的正确使用场景:feof应当用于,在文件读取已经结束的时候,判断是读取失败导致的结束还是遇到文件尾正常的结束(与ferror搭配)
编译文件
实现并发访问使用到线程函数 pthread_create
因此编译时要在 -o
的前面加上 pthread
这个函数的库否则会报错。