项目需求
实现一个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 80 static 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;//选择协议族IPV4 server_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 request len = get_line(client_socket, buf, sizeof(buf)); if(len>0){ //read col of requests int 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 requests if(debug){ cout<<"method = GET" <<endl; } // get url while(isspace(buf[j++])); //ignore space i=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 http do{ 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 exist fprintf(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 request bad_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 header ret = headers(client_sock, resource); // 2.send http body if(!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){ //响应http const 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_header int 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-Length char 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){ // normal if('\r' == ch){ continue; }else if('\n' == ch){ buf[count] = '\0'; break; } buf[count] = ch; count++; }else if(-1 == len){ // abnormal perror("read failed"); count = -1; break; }else{ //read return 0: client has closed socket fprintf(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
这个函数的库否则会报错。